Files
godot-demo-projects/gui/accessibility/custom_control.gd
2025-10-11 05:03:59 -07:00

127 lines
5.2 KiB
GDScript

extends Control
var item_aes: Array[RID] = [RID(), RID(), RID()]
var item_names: Array[String] = ["Item 1", "Item 2", "Item 3"]
var item_values: Array[int] = [0, 0, 0]
var item_rects: Array[Rect2] = [Rect2(0, 0, 40, 40), Rect2(40, 0, 40, 40), Rect2(80, 0, 40, 40)]
var selected: int = 0
# Input:
func _gui_input(input_event: InputEvent) -> void:
if input_event.is_action_pressed(&"ui_left"):
selected = (selected - 1) % item_aes.size()
queue_redraw()
queue_accessibility_update() # Request node accessibility information update. Similar to "queue_redraw" for drawing.
accept_event()
if input_event.is_action_pressed(&"ui_right"):
selected = (selected + 1) % item_aes.size()
queue_redraw()
queue_accessibility_update()
accept_event()
if input_event.is_action_pressed(&"ui_up"):
item_values[selected] = clampi(item_values[selected] - 1, -100, 100)
queue_redraw()
queue_accessibility_update()
accept_event()
if input_event.is_action_pressed(&"ui_down"):
item_values[selected] = clampi(item_values[selected] + 1, -100, 100)
queue_redraw()
queue_accessibility_update()
accept_event()
# Accessibility actions and focus callback:
func _accessibility_action_dec(_data: Variant, item: int) -> void:
# Numeric value decrement, "data" is not used for this action.
item_values[item] = clampi(item_values[item] - 1, -100, 100)
queue_redraw()
queue_accessibility_update()
func _accessibility_action_inc(_data: Variant, item: int) -> void:
# Numeric value increment, "data" is not used for this action.
item_values[item] = clampi(item_values[item] + 1, -100, 100)
queue_redraw()
queue_accessibility_update()
func _accessibility_action_set_num_value(data: Variant, item: int) -> void:
# Numeric value set, "data" is a new value.
item_values[item] = clampi(data, -100, 100)
queue_redraw()
queue_accessibility_update()
func _get_focused_accessibility_element() -> RID:
# Return focused sub-element, if no sub-element is focused, return base element (value returned by "get_accessibility_element") instead.
return item_aes[selected]
# Notifications handler:
func _notification(what: int) -> void:
if what == NOTIFICATION_ACCESSIBILITY_INVALIDATE:
# Accessibility cleanup:
#
# Called when existing main element is destroyed.
# Note: since item sub-elements are children of the main element, there's no need to destroy them manually. But we should keep track when handles are invalidated.
for i in range(item_aes.size()):
item_aes[i] = RID()
if what == NOTIFICATION_ACCESSIBILITY_UPDATE:
# Accessibility update handler:
#
# This function acts as an alternative "draw" for the screen reader, and provides information about this node.
var ae: RID = get_accessibility_element() # Get handle to the accessibilty element, accessibilty element is created and destroyed automatically.
# Set role of the element.
DisplayServer.accessibility_update_set_role(ae, DisplayServer.ROLE_LIST_BOX)
# Set other properties.
DisplayServer.accessibility_update_set_list_item_count(ae, item_aes.size())
DisplayServer.accessibility_update_set_name(ae, "List")
for i in range(item_aes.size()):
# Create a new sub-element for the item if it doesn't exist.
if not item_aes[i].is_valid():
item_aes[i] = DisplayServer.accessibility_create_sub_element(ae, DisplayServer.ROLE_LIST_BOX_OPTION)
# Sub-element properties.
DisplayServer.accessibility_update_set_list_item_index(item_aes[i], i)
DisplayServer.accessibility_update_set_list_item_selected(item_aes[i], selected == i)
DisplayServer.accessibility_update_set_name(item_aes[i], item_names[i]) # Readable name.
DisplayServer.accessibility_update_set_value(item_aes[i], str(item_values[i])) # Readable value.
# Numeric value info for the actions.
DisplayServer.accessibility_update_set_num_value(item_aes[i], item_values[i]);
DisplayServer.accessibility_update_set_num_range(item_aes[i], -100.0, 100.0);
DisplayServer.accessibility_update_set_num_step(item_aes[i], 1.0)
# Sub-element bounding box, relative to the parent element.
DisplayServer.accessibility_update_set_bounds(item_aes[i], item_rects[i])
# Set supported actions for the item, actions can be invoked directly by the screen-reader (e.g, via global keyboard shortcuts).
DisplayServer.accessibility_update_add_action(item_aes[i], DisplayServer.ACTION_DECREMENT, _accessibility_action_dec.bind(i))
DisplayServer.accessibility_update_add_action(item_aes[i], DisplayServer.ACTION_INCREMENT, _accessibility_action_inc.bind(i))
DisplayServer.accessibility_update_add_action(item_aes[i], DisplayServer.ACTION_SET_VALUE, _accessibility_action_set_num_value.bind(i))
if what == NOTIFICATION_FOCUS_ENTER or what == NOTIFICATION_FOCUS_EXIT:
queue_redraw()
# Draw:
func _draw() -> void:
# Draw, provided for convenience and NOT required for screen-reader support.
for i in range(item_aes.size()):
draw_rect(item_rects[selected], Color(0.8, 0.8, 0.8, 0.5), false, 1.0)
draw_string(get_theme_font(&"font"), item_rects[i].position + Vector2(0, 30), str(item_values[i]), HORIZONTAL_ALIGNMENT_CENTER, 40.0)
if has_focus():
draw_rect(Rect2(Vector2(), get_size()), Color(0, 0, 1, 0.5), false, 3.0)
draw_rect(item_rects[selected], Color(0, 1, 0, 0.5), false, 2.0)