Răsfoiți Sursa

Base accessibility API.

Pāvels Nadtočajevs 4 luni în urmă
părinte
comite
b106dfd4f9
100 a modificat fișierele cu 5162 adăugiri și 137 ștergeri
  1. 2 0
      .pre-commit-config.yaml
  2. 3 0
      core/config/project_settings.cpp
  3. 49 1
      core/input/input_map.cpp
  4. 2 0
      core/input/input_map.h
  5. 19 8
      core/string/ustring.cpp
  6. 1 1
      core/string/ustring.h
  7. 24 0
      doc/classes/Control.xml
  8. 894 0
      doc/classes/DisplayServer.xml
  9. 6 0
      doc/classes/GraphEdit.xml
  10. 7 0
      doc/classes/GraphNode.xml
  11. 7 0
      doc/classes/InputMap.xml
  12. 4 0
      doc/classes/Label.xml
  13. 1 1
      doc/classes/LinkButton.xml
  14. 1 0
      doc/classes/MenuBar.xml
  15. 1 1
      doc/classes/MenuButton.xml
  16. 60 0
      doc/classes/Node.xml
  17. 32 0
      doc/classes/ProjectSettings.xml
  18. 13 1
      doc/classes/RichTextLabel.xml
  19. 12 0
      doc/classes/SceneTree.xml
  20. 1 0
      doc/classes/ScrollBar.xml
  21. 3 0
      doc/classes/TextEdit.xml
  22. 6 0
      doc/classes/TextLine.xml
  23. 12 0
      doc/classes/TextParagraph.xml
  24. 86 0
      doc/classes/TextServer.xml
  25. 97 0
      doc/classes/TextServerExtension.xml
  26. 26 1
      doc/classes/TreeItem.xml
  27. 13 0
      doc/classes/Viewport.xml
  28. 6 0
      doc/classes/Window.xml
  29. 49 6
      main/main.cpp
  30. 10 0
      misc/extension_api_validation/4.4-stable.expected
  31. 220 0
      modules/text_server_adv/text_server_adv.cpp
  32. 24 0
      modules/text_server_adv/text_server_adv.h
  33. 217 0
      modules/text_server_fb/text_server_fb.cpp
  34. 23 0
      modules/text_server_fb/text_server_fb.h
  35. 11 0
      scene/2d/animated_sprite_2d.cpp
  36. 2 2
      scene/2d/gpu_particles_2d.cpp
  37. 7 0
      scene/2d/node_2d.cpp
  38. 19 0
      scene/2d/physics/touch_screen_button.cpp
  39. 2 0
      scene/2d/physics/touch_screen_button.h
  40. 11 0
      scene/2d/sprite_2d.cpp
  41. 1 1
      scene/3d/decal.cpp
  42. 2 2
      scene/3d/gpu_particles_3d.cpp
  43. 2 2
      scene/3d/light_3d.cpp
  44. 7 0
      scene/3d/node_3d.cpp
  45. 2 0
      scene/3d/voxel_gi.cpp
  46. 8 1
      scene/audio/audio_stream_player.cpp
  47. 67 5
      scene/gui/base_button.cpp
  48. 1 0
      scene/gui/base_button.h
  49. 22 0
      scene/gui/button.cpp
  50. 12 1
      scene/gui/check_box.cpp
  51. 1 1
      scene/gui/check_box.h
  52. 7 0
      scene/gui/check_button.cpp
  53. 47 3
      scene/gui/color_picker.cpp
  54. 8 0
      scene/gui/color_rect.cpp
  55. 7 0
      scene/gui/container.cpp
  56. 187 7
      scene/gui/control.cpp
  57. 16 3
      scene/gui/control.h
  58. 6 0
      scene/gui/dialogs.cpp
  59. 22 2
      scene/gui/file_dialog.cpp
  60. 242 4
      scene/gui/graph_edit.cpp
  61. 15 0
      scene/gui/graph_edit.h
  62. 358 0
      scene/gui/graph_node.cpp
  63. 16 0
      scene/gui/graph_node.h
  64. 205 3
      scene/gui/item_list.cpp
  65. 16 0
      scene/gui/item_list.h
  66. 24 2
      scene/gui/label.cpp
  67. 1 0
      scene/gui/label.h
  68. 142 1
      scene/gui/line_edit.cpp
  69. 6 0
      scene/gui/line_edit.h
  70. 18 2
      scene/gui/link_button.cpp
  71. 20 0
      scene/gui/menu_bar.cpp
  72. 9 1
      scene/gui/menu_button.cpp
  73. 8 0
      scene/gui/option_button.cpp
  74. 7 0
      scene/gui/panel.cpp
  75. 3 3
      scene/gui/popup.cpp
  76. 2 0
      scene/gui/popup.h
  77. 199 11
      scene/gui/popup_menu.cpp
  78. 9 0
      scene/gui/popup_menu.h
  79. 8 0
      scene/gui/progress_bar.cpp
  80. 50 0
      scene/gui/range.cpp
  81. 5 0
      scene/gui/range.h
  82. 11 1
      scene/gui/rich_text_label.compat.inc
  83. 623 35
      scene/gui/rich_text_label.cpp
  84. 54 10
      scene/gui/rich_text_label.h
  85. 9 0
      scene/gui/scroll_bar.cpp
  86. 35 0
      scene/gui/scroll_container.cpp
  87. 6 0
      scene/gui/scroll_container.h
  88. 6 0
      scene/gui/slider.cpp
  89. 45 1
      scene/gui/spin_box.cpp
  90. 16 1
      scene/gui/spin_box.h
  91. 55 0
      scene/gui/split_container.cpp
  92. 6 0
      scene/gui/split_container.h
  93. 115 2
      scene/gui/tab_bar.cpp
  94. 9 0
      scene/gui/tab_bar.h
  95. 47 0
      scene/gui/tab_container.cpp
  96. 4 0
      scene/gui/tab_container.h
  97. 273 9
      scene/gui/text_edit.cpp
  98. 29 1
      scene/gui/text_edit.h
  99. 7 0
      scene/gui/texture_progress_bar.cpp
  100. 41 0
      scene/gui/tree.compat.inc

+ 2 - 0
.pre-commit-config.yaml

@@ -5,6 +5,8 @@ exclude: |
   (?x)^(
     .*thirdparty/.*|
     .*-so_wrap\.(h|c)|
+    .*-dll_wrap\.(h|c)|
+    .*-dylib_wrap\.(h|c)|
     platform/android/java/editor/src/main/java/com/android/.*|
     platform/android/java/lib/src/com/google/.*
   )$

+ 3 - 0
core/config/project_settings.cpp

@@ -1491,6 +1491,9 @@ ProjectSettings::ProjectSettings() {
 	GLOBAL_DEF("application/config/auto_accept_quit", true);
 	GLOBAL_DEF("application/config/quit_on_go_back", true);
 
+	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "accessibility/general/accessibility_support", PROPERTY_HINT_ENUM, "Auto (When Screen Reader is Running),Always Active,Disabled"), 0);
+	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "accessibility/general/updates_per_second", PROPERTY_HINT_RANGE, "1,100,1"), 60);
+
 	// The default window size is tuned to:
 	// - Have a 16:9 aspect ratio,
 	// - Have both dimensions divisible by 8 to better play along with video recording,

+ 49 - 1
core/input/input_map.cpp

@@ -47,6 +47,8 @@ void InputMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(DEFAULT_DEADZONE));
 	ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);
 
+	ClassDB::bind_method(D_METHOD("get_action_description", "action"), &InputMap::get_action_description);
+
 	ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone);
 	ClassDB::bind_method(D_METHOD("action_get_deadzone", "action"), &InputMap::action_get_deadzone);
 	ClassDB::bind_method(D_METHOD("action_add_event", "action", "event"), &InputMap::action_add_event);
@@ -181,6 +183,25 @@ bool InputMap::has_action(const StringName &p_action) const {
 	return input_map.has(p_action);
 }
 
+String InputMap::get_action_description(const StringName &p_action) const {
+	ERR_FAIL_COND_V_MSG(!input_map.has(p_action), String(), suggest_actions(p_action));
+
+	String ret;
+	const List<Ref<InputEvent>> &inputs = input_map[p_action].inputs;
+	for (Ref<InputEventKey> iek : inputs) {
+		if (iek.is_valid()) {
+			if (!ret.is_empty()) {
+				ret += RTR(" or ");
+			}
+			ret += iek->as_text();
+		}
+	}
+	if (ret.is_empty()) {
+		ret = RTR("Action has no bound inputs");
+	}
+	return ret;
+}
+
 float InputMap::action_get_deadzone(const StringName &p_action) {
 	ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, suggest_actions(p_action));
 
@@ -344,6 +365,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
     { "ui_cut",                                        TTRC("Cut") },
     { "ui_copy",                                       TTRC("Copy") },
     { "ui_paste",                                      TTRC("Paste") },
+	{ "ui_focus_mode",                                 TTRC("Toggle Tab Focus Mode") },
     { "ui_undo",                                       TTRC("Undo") },
     { "ui_redo",                                       TTRC("Redo") },
     { "ui_text_completion_query",                      TTRC("Completion Query") },
@@ -397,12 +419,15 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
     { "ui_text_submit",                                TTRC("Submit Text") },
     { "ui_graph_duplicate",                            TTRC("Duplicate Nodes") },
     { "ui_graph_delete",                               TTRC("Delete Nodes") },
+	{ "ui_graph_follow_left",                          TTRC("Follow Input Port Connection") },
+	{ "ui_graph_follow_right",                         TTRC("Follow Output Port Connection") },
     { "ui_filedialog_up_one_level",                    TTRC("Go Up One Level") },
     { "ui_filedialog_refresh",                         TTRC("Refresh") },
     { "ui_filedialog_show_hidden",                     TTRC("Show Hidden") },
     { "ui_swap_input_direction ",                      TTRC("Swap Input Direction") },
     { "ui_unicode_start",                              TTRC("Start Unicode Character Input") },
-    { "ui_colorpicker_delete_preset",               TTRC("Toggle License Notices") },
+    { "ui_colorpicker_delete_preset",                  TTRC("Toggle License Notices") },
+	{ "ui_accessibility_drag_and_drop",                TTRC("Accessibility: Keyboard Drag and Drop") },
     { "",                                              ""}
 	/* clang-format on */
 };
@@ -488,6 +513,9 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	inputs.push_back(InputEventKey::create_reference(Key::END));
 	default_builtin_cache.insert("ui_end", inputs);
 
+	inputs = List<Ref<InputEvent>>();
+	default_builtin_cache.insert("ui_accessibility_drag_and_drop", inputs);
+
 	// ///// UI basic Shortcuts /////
 
 	inputs = List<Ref<InputEvent>>();
@@ -500,6 +528,10 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::CMD_OR_CTRL));
 	default_builtin_cache.insert("ui_copy", inputs);
 
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::M | KeyModifierMask::CTRL));
+	default_builtin_cache.insert("ui_focus_mode", inputs);
+
 	inputs = List<Ref<InputEvent>>();
 	inputs.push_back(InputEventKey::create_reference(Key::V | KeyModifierMask::CMD_OR_CTRL));
 	inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::SHIFT));
@@ -773,6 +805,22 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE));
 	default_builtin_cache.insert("ui_graph_delete", inputs);
 
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
+	default_builtin_cache.insert("ui_graph_follow_left", inputs);
+
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::ALT));
+	default_builtin_cache.insert("ui_graph_follow_left.macos", inputs);
+
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
+	default_builtin_cache.insert("ui_graph_follow_right", inputs);
+
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::ALT));
+	default_builtin_cache.insert("ui_graph_follow_right.macos", inputs);
+
 	// ///// UI File Dialog Shortcuts /////
 	inputs = List<Ref<InputEvent>>();
 	inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE));

+ 2 - 0
core/input/input_map.h

@@ -85,6 +85,8 @@ public:
 	void add_action(const StringName &p_action, float p_deadzone = DEFAULT_DEADZONE);
 	void erase_action(const StringName &p_action);
 
+	String get_action_description(const StringName &p_action) const;
+
 	float action_get_deadzone(const StringName &p_action);
 	void action_set_deadzone(const StringName &p_action, float p_deadzone);
 	void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event);

+ 19 - 8
core/string/ustring.cpp

@@ -2073,34 +2073,45 @@ Error String::append_utf8(const char *p_utf8, int p_len, bool p_skip_cr) {
 	return result;
 }
 
-CharString String::utf8() const {
+CharString String::utf8(Vector<uint8_t> *r_ch_length_map) const {
 	int l = length();
 	if (!l) {
 		return CharString();
 	}
 
+	uint8_t *map_ptr = nullptr;
+	if (r_ch_length_map) {
+		r_ch_length_map->resize(l);
+		map_ptr = r_ch_length_map->ptrw();
+	}
+
 	const char32_t *d = &operator[](0);
 	int fl = 0;
 	for (int i = 0; i < l; i++) {
 		uint32_t c = d[i];
+		int ch_w = 1;
 		if (c <= 0x7f) { // 7 bits.
-			fl += 1;
+			ch_w = 1;
 		} else if (c <= 0x7ff) { // 11 bits
-			fl += 2;
+			ch_w = 2;
 		} else if (c <= 0xffff) { // 16 bits
-			fl += 3;
+			ch_w = 3;
 		} else if (c <= 0x001fffff) { // 21 bits
-			fl += 4;
+			ch_w = 4;
 		} else if (c <= 0x03ffffff) { // 26 bits
-			fl += 5;
+			ch_w = 5;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 		} else if (c <= 0x7fffffff) { // 31 bits
-			fl += 6;
+			ch_w = 6;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 		} else {
-			fl += 1;
+			ch_w = 1;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as UTF-8", c), true);
 		}
+		fl += ch_w;
+		if (map_ptr) {
+			map_ptr[i] = ch_w;
+		}
 	}
 
 	CharString utf8s;

+ 1 - 1
core/string/ustring.h

@@ -511,7 +511,7 @@ public:
 		return string;
 	}
 
-	CharString utf8() const;
+	CharString utf8(Vector<uint8_t> *r_ch_length_map = nullptr) const;
 	Error append_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false);
 	Error append_utf8(const Span<char> &p_range, bool p_skip_cr = false) {
 		return append_utf8(p_range.ptr(), p_range.size(), p_skip_cr);

+ 24 - 0
doc/classes/Control.xml

@@ -24,6 +24,12 @@
 		<link title="All GUI Demos">https://github.com/godotengine/godot-demo-projects/tree/master/gui</link>
 	</tutorials>
 	<methods>
+		<method name="_accessibility_get_contextual_info" qualifiers="virtual const">
+			<return type="String" />
+			<description>
+				Return the description of the keyboard shortcuts and other contextual help for this control.
+			</description>
+		</method>
 		<method name="_can_drop_data" qualifiers="virtual const">
 			<return type="bool" />
 			<param index="0" name="at_position" type="Vector2" />
@@ -31,6 +37,7 @@
 			<description>
 				Godot calls this method to test if [param data] from a control's [method _get_drag_data] can be dropped at [param at_position]. [param at_position] is local to this control.
 				This method should only be used to test the data. Process the data in [method _drop_data].
+				[b]Note:[/b] If drag was initiated by keyboard shortcut or [method accessibility_drag], [param at_position] is set to [code]Vector2(INFINITY, INFINITY)[/code] and the currently selected item/text position should be used as drop position.
 				[codeblocks]
 				[gdscript]
 				func _can_drop_data(position, data):
@@ -55,6 +62,7 @@
 			<param index="1" name="data" type="Variant" />
 			<description>
 				Godot calls this method to pass you the [param data] from a control's [method _get_drag_data] result. Godot first calls [method _can_drop_data] to test if [param data] is allowed to drop at [param at_position] where [param at_position] is local to this control.
+				[b]Note:[/b] If drag was initiated by keyboard shortcut or [method accessibility_drag], [param at_position] is set to [code]Vector2(INFINITY, INFINITY)[/code] and the currently selected item/text position should be used as drop position.
 				[codeblocks]
 				[gdscript]
 				func _can_drop_data(position, data):
@@ -83,6 +91,7 @@
 			<description>
 				Godot calls this method to get data that can be dragged and dropped onto controls that expect drop data. Returns [code]null[/code] if there is no data to drag. Controls that want to receive drop data should implement [method _can_drop_data] and [method _drop_data]. [param at_position] is local to this control. Drag may be forced with [method force_drag].
 				A preview that will follow the mouse that should represent the data can be set with [method set_drag_preview]. A good time to set the preview is in this method.
+				[b]Note:[/b] If drag was initiated by keyboard shortcut or [method accessibility_drag], [param at_position] is set to [code]Vector2(INFINITY, INFINITY)[/code] and the currently selected item/text position should be used as drop position.
 				[codeblocks]
 				[gdscript]
 				func _get_drag_data(position):
@@ -223,6 +232,18 @@
 				[b]Note:[/b] This does not affect the methods in [Input], only the way events are propagated.
 			</description>
 		</method>
+		<method name="accessibility_drag">
+			<return type="void" />
+			<description>
+				Starts drag-and-drop operation without using a mouse.
+			</description>
+		</method>
+		<method name="accessibility_drop">
+			<return type="void" />
+			<description>
+				Ends drag-and-drop operation without using a mouse.
+			</description>
+		</method>
 		<method name="add_theme_color_override">
 			<return type="void" />
 			<param index="0" name="name" type="StringName" />
@@ -1174,6 +1195,9 @@
 		<constant name="FOCUS_ALL" value="2" enum="FocusMode">
 			The node can grab focus on mouse click, using the arrows and the Tab keys on the keyboard, or using the D-pad buttons on a gamepad. Use with [member focus_mode].
 		</constant>
+		<constant name="FOCUS_ACCESSIBILITY" value="3" enum="FocusMode">
+			The node can grab focus only when screen reader is active. Use with [member focus_mode].
+		</constant>
 		<constant name="RECURSIVE_BEHAVIOR_INHERITED" value="0" enum="RecursiveBehavior">
 			Inherits the associated behavior from the control's parent. This is the default for any newly created control.
 		</constant>

+ 894 - 0
doc/classes/DisplayServer.xml

@@ -10,6 +10,630 @@
 	<tutorials>
 	</tutorials>
 	<methods>
+		<method name="accessibility_create_element">
+			<return type="RID" />
+			<param index="0" name="window_id" type="int" />
+			<param index="1" name="role" type="int" enum="DisplayServer.AccessibilityRole" />
+			<description>
+				Creates a new, empty accessibility element resource.
+				[b]Note:[/b] An accessibility element is created and freed automatically for each [Node]. In general, this function should not be called manually.
+			</description>
+		</method>
+		<method name="accessibility_create_sub_element">
+			<return type="RID" />
+			<param index="0" name="parent_rid" type="RID" />
+			<param index="1" name="role" type="int" enum="DisplayServer.AccessibilityRole" />
+			<param index="2" name="insert_pos" type="int" default="-1" />
+			<description>
+				Creates a new, empty accessibility sub-element resource. Sub-elements can be used to provide accessibility information for objects which are not [Node]s, such as list items, table cells, or menu items. Sub-elements are freed automatically when the parent element is freed, or can be freed early using the [method accessibility_free_element] method.
+			</description>
+		</method>
+		<method name="accessibility_create_sub_text_edit_elements">
+			<return type="RID" />
+			<param index="0" name="parent_rid" type="RID" />
+			<param index="1" name="shaped_text" type="RID" />
+			<param index="2" name="min_height" type="float" />
+			<param index="3" name="insert_pos" type="int" default="-1" />
+			<description>
+				Creates a new, empty accessibility sub-element from the shaped text buffer. Sub-elements are freed automatically when the parent element is freed, or can be freed early using the [method accessibility_free_element] method.
+			</description>
+		</method>
+		<method name="accessibility_element_get_meta" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="id" type="RID" />
+			<description>
+				Returns the metadata of the accessibility element.
+			</description>
+		</method>
+		<method name="accessibility_element_set_meta">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="meta" type="Variant" />
+			<description>
+				Sets the metadata of the accessibility element.
+			</description>
+		</method>
+		<method name="accessibility_free_element">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<description>
+				Frees an object created by [method accessibility_create_element], [method accessibility_create_sub_element], or [method accessibility_create_sub_text_edit_elements].
+			</description>
+		</method>
+		<method name="accessibility_get_window_root" qualifiers="const">
+			<return type="RID" />
+			<param index="0" name="window_id" type="int" />
+			<description>
+				Returns the main accessibility element of the OS native window.
+			</description>
+		</method>
+		<method name="accessibility_has_element" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="id" type="RID" />
+			<description>
+				Returns [code]true[/code] if [param id] is a valid accessibility element.
+			</description>
+		</method>
+		<method name="accessibility_screen_reader_active" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns [code]1[/code] if a screen reader, Braille display or other assistive app is active, [code]0[/code] otherwise. Returns [code]-1[/code] if status is unknown.
+				[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
+				[b]Note:[/b] Accessibility debugging tools, such as Accessibility Insights for Windows, macOS Accessibility Inspector, or AT-SPI Browser do not count as assistive apps and will not affect this value. To test your app with these tools, set [member ProjectSettings.accessibility/general/accessibility_support] to [code]1[/code].
+			</description>
+		</method>
+		<method name="accessibility_set_window_focused">
+			<return type="void" />
+			<param index="0" name="window_id" type="int" />
+			<param index="1" name="focused" type="bool" />
+			<description>
+				Sets the window focused state for assistive apps.
+				[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
+				[b]Note:[/b] Advanced users only! [Window] objects call this method automatically.
+			</description>
+		</method>
+		<method name="accessibility_set_window_rect">
+			<return type="void" />
+			<param index="0" name="window_id" type="int" />
+			<param index="1" name="rect_out" type="Rect2" />
+			<param index="2" name="rect_in" type="Rect2" />
+			<description>
+				Sets window outer (with decorations) and inner (without decorations) bounds for assistive apps.
+				[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
+				[b]Note:[/b] Advanced users only! [Window] objects call this method automatically.
+			</description>
+		</method>
+		<method name="accessibility_should_increase_contrast" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns [code]1[/code] if a high-contrast user interface theme should be used, [code]0[/code] otherwise. Returns [code]-1[/code] if status is unknown.
+				[b]Note:[/b] This method is implemented on Linux (X11/Wayland, GNOME), macOS, and Windows.
+			</description>
+		</method>
+		<method name="accessibility_should_reduce_animation" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns [code]1[/code] if flashing, blinking, and other moving content that can cause seizures in users with photosensitive epilepsy should be disabled, [code]0[/code] otherwise. Returns [code]-1[/code] if status is unknown.
+				[b]Note:[/b] This method is implemented on macOS and Windows.
+			</description>
+		</method>
+		<method name="accessibility_should_reduce_transparency" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns [code]1[/code] if background images, transparency, and other features that can reduce the contrast between the foreground and background should be disabled, [code]0[/code] otherwise. Returns [code]-1[/code] if status is unknown.
+				[b]Note:[/b] This method is implemented on macOS and Windows.
+			</description>
+		</method>
+		<method name="accessibility_update_add_action">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="action" type="int" enum="DisplayServer.AccessibilityAction" />
+			<param index="2" name="callable" type="Callable" />
+			<description>
+				Adds a callback for the accessibility action (action which can be performed by using a special screen reader command or buttons on the Braille display), and marks this action as supported. The action callback receives one [Variant] argument, which value depends on action type.
+			</description>
+		</method>
+		<method name="accessibility_update_add_child">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="child_id" type="RID" />
+			<description>
+				Adds a child accessibility element.
+				[b]Note:[/b] [Node] children and sub-elements are added to the child list automatically.
+			</description>
+		</method>
+		<method name="accessibility_update_add_custom_action">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="action_id" type="int" />
+			<param index="2" name="action_description" type="String" />
+			<description>
+				Adds support for a custom accessibility action. [param action_id] is passed as an argument to the callback of [constant ACTION_CUSTOM] action.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_controls">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that is controlled by this element.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_described_by">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that describes this element.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_details">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that details this element.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_flow_to">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that this element flow into.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_labeled_by">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that labels this element.
+			</description>
+		</method>
+		<method name="accessibility_update_add_related_radio_group">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="related_id" type="RID" />
+			<description>
+				Adds an element that is part of the same radio group.
+				[b]Note:[/b] This method should be called on each element of the group, using all other elements as [param related_id].
+			</description>
+		</method>
+		<method name="accessibility_update_set_active_descendant">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="other_id" type="RID" />
+			<description>
+				Adds an element that is an active descendant of this element.
+			</description>
+		</method>
+		<method name="accessibility_update_set_background_color">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="color" type="Color" />
+			<description>
+				Sets element background color.
+			</description>
+		</method>
+		<method name="accessibility_update_set_bounds">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="p_rect" type="Rect2" />
+			<description>
+				Sets element bounding box, relative to the node position.
+			</description>
+		</method>
+		<method name="accessibility_update_set_checked">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="checekd" type="bool" />
+			<description>
+				Sets element checked state.
+			</description>
+		</method>
+		<method name="accessibility_update_set_classname">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="classname" type="String" />
+			<description>
+				Sets element class name.
+			</description>
+		</method>
+		<method name="accessibility_update_set_color_value">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="color" type="Color" />
+			<description>
+				Sets element color value.
+			</description>
+		</method>
+		<method name="accessibility_update_set_description">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="description" type="String" />
+			<description>
+				Sets element accessibility description.
+			</description>
+		</method>
+		<method name="accessibility_update_set_error_message">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="other_id" type="RID" />
+			<description>
+				Sets an element which contains an error message for this element.
+			</description>
+		</method>
+		<method name="accessibility_update_set_extra_info">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="name" type="String" />
+			<description>
+				Sets element accessibility extra information added to the element name.
+			</description>
+		</method>
+		<method name="accessibility_update_set_flag">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="flag" type="int" enum="DisplayServer.AccessibilityFlags" />
+			<param index="2" name="value" type="bool" />
+			<description>
+				Sets element flag, see [enum DisplayServer.AccessibilityFlags].
+			</description>
+		</method>
+		<method name="accessibility_update_set_focus">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<description>
+				Sets currently focused element.
+			</description>
+		</method>
+		<method name="accessibility_update_set_foreground_color">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="color" type="Color" />
+			<description>
+				Sets element foreground color.
+			</description>
+		</method>
+		<method name="accessibility_update_set_in_page_link_target">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="other_id" type="RID" />
+			<description>
+				Sets target element for the link.
+			</description>
+		</method>
+		<method name="accessibility_update_set_language">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="language" type="String" />
+			<description>
+				Sets element text language.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_item_count">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="size" type="int" />
+			<description>
+				Sets number of items in the list.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_item_expanded">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="expanded" type="bool" />
+			<description>
+				Sets list/tree item expanded status.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_item_index">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Sets the position of the element in the list.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_item_level">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="level" type="int" />
+			<description>
+				Sets the hierarchical level of the element in the list.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_item_selected">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="selected" type="bool" />
+			<description>
+				Sets list/tree item selected status.
+			</description>
+		</method>
+		<method name="accessibility_update_set_list_orientation">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="vertical" type="bool" />
+			<description>
+				Sets the orientation of the list elements.
+			</description>
+		</method>
+		<method name="accessibility_update_set_live">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="live" type="int" enum="DisplayServer.AccessibilityLiveMode" />
+			<description>
+				Sets the priority of the live region updates.
+			</description>
+		</method>
+		<method name="accessibility_update_set_member_of">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="group_id" type="RID" />
+			<description>
+				Sets the element to be a member of the group.
+			</description>
+		</method>
+		<method name="accessibility_update_set_name">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="name" type="String" />
+			<description>
+				Sets element accessibility name.
+			</description>
+		</method>
+		<method name="accessibility_update_set_next_on_line">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="other_id" type="RID" />
+			<description>
+				Sets next element on the line.
+			</description>
+		</method>
+		<method name="accessibility_update_set_num_jump">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="jump" type="float" />
+			<description>
+				Sets numeric value jump.
+			</description>
+		</method>
+		<method name="accessibility_update_set_num_range">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="min" type="float" />
+			<param index="2" name="max" type="float" />
+			<description>
+				Sets numeric value range.
+			</description>
+		</method>
+		<method name="accessibility_update_set_num_step">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="step" type="float" />
+			<description>
+				Sets numeric value step.
+			</description>
+		</method>
+		<method name="accessibility_update_set_num_value">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="position" type="float" />
+			<description>
+				Sets numeric value.
+			</description>
+		</method>
+		<method name="accessibility_update_set_placeholder">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="placeholder" type="String" />
+			<description>
+				Sets placeholder text.
+			</description>
+		</method>
+		<method name="accessibility_update_set_popup_type">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="popup" type="int" enum="DisplayServer.AccessibilityPopupType" />
+			<description>
+				Sets popup type for popup buttons.
+			</description>
+		</method>
+		<method name="accessibility_update_set_previous_on_line">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="other_id" type="RID" />
+			<description>
+				Sets previous element on the line.
+			</description>
+		</method>
+		<method name="accessibility_update_set_role">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="role" type="int" enum="DisplayServer.AccessibilityRole" />
+			<description>
+				Sets element accessibility role.
+			</description>
+		</method>
+		<method name="accessibility_update_set_role_description">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="description" type="String" />
+			<description>
+				Sets element accessibility role description text.
+			</description>
+		</method>
+		<method name="accessibility_update_set_scroll_x">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="position" type="float" />
+			<description>
+				Sets scroll bar x position.
+			</description>
+		</method>
+		<method name="accessibility_update_set_scroll_x_range">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="min" type="float" />
+			<param index="2" name="max" type="float" />
+			<description>
+				Sets scroll bar x range.
+			</description>
+		</method>
+		<method name="accessibility_update_set_scroll_y">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="position" type="float" />
+			<description>
+				Sets scroll bar y position.
+			</description>
+		</method>
+		<method name="accessibility_update_set_scroll_y_range">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="min" type="float" />
+			<param index="2" name="max" type="float" />
+			<description>
+				Sets scroll bar y range.
+			</description>
+		</method>
+		<method name="accessibility_update_set_shortcut">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="shortcut" type="String" />
+			<description>
+				Sets the list of keyboard shortcuts used by element.
+			</description>
+		</method>
+		<method name="accessibility_update_set_state_description">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="description" type="String" />
+			<description>
+				Sets human-readable description of the current checked state.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_cell_position">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="row_index" type="int" />
+			<param index="2" name="column_index" type="int" />
+			<description>
+				Sets cell position in the table.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_cell_span">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="row_span" type="int" />
+			<param index="2" name="column_span" type="int" />
+			<description>
+				Sets cell row/column span.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_column_count">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="count" type="int" />
+			<description>
+				Sets number of columns in the table.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_column_index">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Sets position of the column.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_row_count">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="count" type="int" />
+			<description>
+				Sets number of rows in the table.
+			</description>
+		</method>
+		<method name="accessibility_update_set_table_row_index">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Sets position of the row in the table.
+			</description>
+		</method>
+		<method name="accessibility_update_set_text_align">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="align" type="int" enum="HorizontalAlignment" />
+			<description>
+				Sets element text alignment.
+			</description>
+		</method>
+		<method name="accessibility_update_set_text_decorations">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="underline" type="bool" />
+			<param index="2" name="strikethrough" type="bool" />
+			<param index="3" name="overline" type="bool" />
+			<description>
+				Sets text underline/overline/strikethrough.
+			</description>
+		</method>
+		<method name="accessibility_update_set_text_orientation">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="vertical" type="bool" />
+			<description>
+				Sets text orientation.
+			</description>
+		</method>
+		<method name="accessibility_update_set_text_selection">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="text_start_id" type="RID" />
+			<param index="2" name="start_char" type="int" />
+			<param index="3" name="text_end_id" type="RID" />
+			<param index="4" name="end_char" type="int" />
+			<description>
+				Sets text selection to the text field. [param text_start_id] and [param text_end_id] should be elements created by [method accessibility_create_sub_text_edit_elements]. Character offsets are relative to the corresponding element.
+			</description>
+		</method>
+		<method name="accessibility_update_set_tooltip">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="tooltip" type="String" />
+			<description>
+				Sets tooltip text.
+			</description>
+		</method>
+		<method name="accessibility_update_set_transform">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="transform" type="Transform2D" />
+			<description>
+				Sets element 2D transform.
+			</description>
+		</method>
+		<method name="accessibility_update_set_url">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="url" type="String" />
+			<description>
+				Sets link URL.
+			</description>
+		</method>
+		<method name="accessibility_update_set_value">
+			<return type="void" />
+			<param index="0" name="id" type="RID" />
+			<param index="1" name="value" type="String" />
+			<description>
+				Sets element text value.
+			</description>
+		</method>
 		<method name="beep" qualifiers="const">
 			<return type="void" />
 			<description>
@@ -1969,6 +2593,276 @@
 		<constant name="FEATURE_SELF_FITTING_WINDOWS" value="33" enum="Feature">
 			Display server automatically fits popups according to the screen boundaries. Window nodes should not attempt to do that themselves.
 		</constant>
+		<constant name="FEATURE_ACCESSIBILITY_SCREEN_READER" value="34" enum="Feature">
+			Display server supports interaction with screen reader or Braille display. [b]Linux (X11/Wayland), macOS, Windows[/b]
+		</constant>
+		<constant name="ROLE_UNKNOWN" value="0" enum="AccessibilityRole">
+			Unknown or custom role.
+		</constant>
+		<constant name="ROLE_DEFAULT_BUTTON" value="1" enum="AccessibilityRole">
+			Default dialog button element.
+		</constant>
+		<constant name="ROLE_AUDIO" value="2" enum="AccessibilityRole">
+			Audio player element.
+		</constant>
+		<constant name="ROLE_VIDEO" value="3" enum="AccessibilityRole">
+			Video player element.
+		</constant>
+		<constant name="ROLE_STATIC_TEXT" value="4" enum="AccessibilityRole">
+			Non-editable text label.
+		</constant>
+		<constant name="ROLE_CONTAINER" value="5" enum="AccessibilityRole">
+			Container element. Elements with this role are used for internal structure and ignored by screen readers.
+		</constant>
+		<constant name="ROLE_PANEL" value="6" enum="AccessibilityRole">
+			Panel container element.
+		</constant>
+		<constant name="ROLE_BUTTON" value="7" enum="AccessibilityRole">
+			Button element.
+		</constant>
+		<constant name="ROLE_LINK" value="8" enum="AccessibilityRole">
+			Link element.
+		</constant>
+		<constant name="ROLE_CHECK_BOX" value="9" enum="AccessibilityRole">
+			Check box element.
+		</constant>
+		<constant name="ROLE_RADIO_BUTTON" value="10" enum="AccessibilityRole">
+			Radio button element.
+		</constant>
+		<constant name="ROLE_CHECK_BUTTON" value="11" enum="AccessibilityRole">
+			Check button element.
+		</constant>
+		<constant name="ROLE_SCROLL_BAR" value="12" enum="AccessibilityRole">
+			Scroll bar element.
+		</constant>
+		<constant name="ROLE_SCROLL_VIEW" value="13" enum="AccessibilityRole">
+			Scroll container element.
+		</constant>
+		<constant name="ROLE_SPLITTER" value="14" enum="AccessibilityRole">
+			Container splitter handle element.
+		</constant>
+		<constant name="ROLE_SLIDER" value="15" enum="AccessibilityRole">
+			Slider element.
+		</constant>
+		<constant name="ROLE_SPIN_BUTTON" value="16" enum="AccessibilityRole">
+			Spin box element.
+		</constant>
+		<constant name="ROLE_PROGRESS_INDICATOR" value="17" enum="AccessibilityRole">
+			Progress indicator element.
+		</constant>
+		<constant name="ROLE_TEXT_FIELD" value="18" enum="AccessibilityRole">
+			Editable text field element.
+		</constant>
+		<constant name="ROLE_MULTILINE_TEXT_FIELD" value="19" enum="AccessibilityRole">
+			Multiline editable text field element.
+		</constant>
+		<constant name="ROLE_COLOR_PICKER" value="20" enum="AccessibilityRole">
+			Color picker element.
+		</constant>
+		<constant name="ROLE_TABLE" value="21" enum="AccessibilityRole">
+			Table element.
+		</constant>
+		<constant name="ROLE_CELL" value="22" enum="AccessibilityRole">
+			Table/tree cell element.
+		</constant>
+		<constant name="ROLE_ROW" value="23" enum="AccessibilityRole">
+			Table/tree row element.
+		</constant>
+		<constant name="ROLE_ROW_GROUP" value="24" enum="AccessibilityRole">
+			Table/tree row group element.
+		</constant>
+		<constant name="ROLE_ROW_HEADER" value="25" enum="AccessibilityRole">
+			Table/tree row header element.
+		</constant>
+		<constant name="ROLE_COLUMN_HEADER" value="26" enum="AccessibilityRole">
+			Table/tree column header element.
+		</constant>
+		<constant name="ROLE_TREE" value="27" enum="AccessibilityRole">
+			Tree view element.
+		</constant>
+		<constant name="ROLE_TREE_ITEM" value="28" enum="AccessibilityRole">
+			Tree view item element.
+		</constant>
+		<constant name="ROLE_LIST" value="29" enum="AccessibilityRole">
+			List element.
+		</constant>
+		<constant name="ROLE_LIST_ITEM" value="30" enum="AccessibilityRole">
+			List item element.
+		</constant>
+		<constant name="ROLE_LIST_BOX" value="31" enum="AccessibilityRole">
+			List view element.
+		</constant>
+		<constant name="ROLE_LIST_BOX_OPTION" value="32" enum="AccessibilityRole">
+			List view item element.
+		</constant>
+		<constant name="ROLE_TAB_BAR" value="33" enum="AccessibilityRole">
+			Tab bar element.
+		</constant>
+		<constant name="ROLE_TAB" value="34" enum="AccessibilityRole">
+			Tab bar item element.
+		</constant>
+		<constant name="ROLE_TAB_PANEL" value="35" enum="AccessibilityRole">
+			Tab panel element.
+		</constant>
+		<constant name="ROLE_MENU_BAR" value="36" enum="AccessibilityRole">
+			Menu bar element.
+		</constant>
+		<constant name="ROLE_MENU" value="37" enum="AccessibilityRole">
+			Popup menu element.
+		</constant>
+		<constant name="ROLE_MENU_ITEM" value="38" enum="AccessibilityRole">
+			Popup menu item element.
+		</constant>
+		<constant name="ROLE_MENU_ITEM_CHECK_BOX" value="39" enum="AccessibilityRole">
+			Popup menu check button item element.
+		</constant>
+		<constant name="ROLE_MENU_ITEM_RADIO" value="40" enum="AccessibilityRole">
+			Popup menu radio button item element.
+		</constant>
+		<constant name="ROLE_IMAGE" value="41" enum="AccessibilityRole">
+			Image element.
+		</constant>
+		<constant name="ROLE_WINDOW" value="42" enum="AccessibilityRole">
+			Window element.
+		</constant>
+		<constant name="ROLE_TITLE_BAR" value="43" enum="AccessibilityRole">
+			Embedded window title bar element.
+		</constant>
+		<constant name="ROLE_DIALOG" value="44" enum="AccessibilityRole">
+			Dialog window element.
+		</constant>
+		<constant name="ROLE_TOOLTIP" value="45" enum="AccessibilityRole">
+			Tooltip element.
+		</constant>
+		<constant name="POPUP_UNKNOWN" value="0" enum="AccessibilityPopupType">
+			Other/unknown popup type.
+		</constant>
+		<constant name="POPUP_MENU" value="1" enum="AccessibilityPopupType">
+			Popup menu.
+		</constant>
+		<constant name="POPUP_LIST" value="2" enum="AccessibilityPopupType">
+			Popup list.
+		</constant>
+		<constant name="POPUP_TREE" value="3" enum="AccessibilityPopupType">
+			Popup tree view.
+		</constant>
+		<constant name="POPUP_DIALOG" value="4" enum="AccessibilityPopupType">
+			Popup dialog.
+		</constant>
+		<constant name="FLAG_HIDDEN" value="0" enum="AccessibilityFlags">
+			Element is hidden for accessibility tools.
+		</constant>
+		<constant name="FLAG_LINKED" value="1" enum="AccessibilityFlags">
+		</constant>
+		<constant name="FLAG_MULTISELECTABLE" value="2" enum="AccessibilityFlags">
+			Element is support multiple item selection.
+		</constant>
+		<constant name="FLAG_REQUIRED" value="3" enum="AccessibilityFlags">
+			Element require user input.
+		</constant>
+		<constant name="FLAG_VISITED" value="4" enum="AccessibilityFlags">
+			Element is a visited link.
+		</constant>
+		<constant name="FLAG_BUSY" value="5" enum="AccessibilityFlags">
+			Element content is not ready (e.g. loading).
+		</constant>
+		<constant name="FLAG_MODAL" value="6" enum="AccessibilityFlags">
+			Element is modal window.
+		</constant>
+		<constant name="FLAG_TOUCH_PASSTHROUGH" value="7" enum="AccessibilityFlags">
+			Element allows touches to be passed through when a screen reader is in touch exploration mode.
+		</constant>
+		<constant name="FLAG_READONLY" value="8" enum="AccessibilityFlags">
+			Element is text field with selectable but read-only text.
+		</constant>
+		<constant name="FLAG_DISABLED" value="9" enum="AccessibilityFlags">
+			Element is disabled.
+		</constant>
+		<constant name="FLAG_CLIPS_CHILDREN" value="10" enum="AccessibilityFlags">
+			Element clips children.
+		</constant>
+		<constant name="ACTION_CLICK" value="0" enum="AccessibilityAction">
+			Single click action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_FOCUS" value="1" enum="AccessibilityAction">
+			Focus action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_BLUR" value="2" enum="AccessibilityAction">
+			Blur action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_COLLAPSE" value="3" enum="AccessibilityAction">
+			Collapse action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_EXPAND" value="4" enum="AccessibilityAction">
+			Expand action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_DECREMENT" value="5" enum="AccessibilityAction">
+			Decrement action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_INCREMENT" value="6" enum="AccessibilityAction">
+			Increment action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_HIDE_TOOLTIP" value="7" enum="AccessibilityAction">
+			Hide tooltip action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SHOW_TOOLTIP" value="8" enum="AccessibilityAction">
+			Show tooltip action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SET_TEXT_SELECTION" value="9" enum="AccessibilityAction">
+			Set text selection action, callback argument is set to [Dictionary] with the following keys:
+			- [code]"start_element"[/code] accessibility element of the selection start.
+			- [code]"start_char"[/code] character offset relative to the accessibility element of the selection start.
+			- [code]"end_element"[/code] accessibility element of the selection end.
+			- [code]"end_char"[/code] character offset relative to the accessibility element of the selection end.
+		</constant>
+		<constant name="ACTION_REPLACE_SELECTED_TEXT" value="10" enum="AccessibilityAction">
+			Replace text action, callback argument is set to [String] with the replacement text.
+		</constant>
+		<constant name="ACTION_SCROLL_BACKWARD" value="11" enum="AccessibilityAction">
+			Scroll backward action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_DOWN" value="12" enum="AccessibilityAction">
+			Scroll down action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_FORWARD" value="13" enum="AccessibilityAction">
+			Scroll forward action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_LEFT" value="14" enum="AccessibilityAction">
+			Scroll left action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_RIGHT" value="15" enum="AccessibilityAction">
+			Scroll right action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_UP" value="16" enum="AccessibilityAction">
+			Scroll up action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_INTO_VIEW" value="17" enum="AccessibilityAction">
+			Scroll into view action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_SCROLL_TO_POINT" value="18" enum="AccessibilityAction">
+			Scroll to point action, callback argument is set to [Vector2] with the relative point coordinates.
+		</constant>
+		<constant name="ACTION_SET_SCROLL_OFFSET" value="19" enum="AccessibilityAction">
+			Set scroll offset action, callback argument is set to [Vector2] with the scroll offset.
+		</constant>
+		<constant name="ACTION_SET_VALUE" value="20" enum="AccessibilityAction">
+			Set value action action, callback argument is set to [String] or number with the new value.
+		</constant>
+		<constant name="ACTION_SHOW_CONTEXT_MENU" value="21" enum="AccessibilityAction">
+			Show context menu action, callback argument is not set.
+		</constant>
+		<constant name="ACTION_CUSTOM" value="22" enum="AccessibilityAction">
+			Custom action, callback argument is set to the integer action id.
+		</constant>
+		<constant name="LIVE_OFF" value="0" enum="AccessibilityLiveMode">
+			Indicates that updates to the live region should not be presented.
+		</constant>
+		<constant name="LIVE_POLITE" value="1" enum="AccessibilityLiveMode">
+			Indicates that updates to the live region should be presented at the next opportunity (for example at the end of speaking the current sentence).
+		</constant>
+		<constant name="LIVE_ASSERTIVE" value="2" enum="AccessibilityLiveMode">
+			Indicates that updates to the live region have the highest priority and should be presented immediately.
+		</constant>
 		<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
 			Makes the mouse cursor visible if it is hidden.
 		</constant>

+ 6 - 0
doc/classes/GraphEdit.xml

@@ -391,6 +391,9 @@
 		<member name="snapping_enabled" type="bool" setter="set_snapping_enabled" getter="is_snapping_enabled" default="true">
 			If [code]true[/code], enables snapping.
 		</member>
+		<member name="type_names" type="Dictionary" setter="set_type_names" getter="get_type_names" default="{}">
+			[Dictionary] of human readable port type names.
+		</member>
 		<member name="zoom" type="float" setter="set_zoom" getter="get_zoom" default="1.0">
 			The current zoom value.
 		</member>
@@ -603,5 +606,8 @@
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			The background drawn under the grid.
 		</theme_item>
+		<theme_item name="panel_focus" data_type="style" type="StyleBox">
+			[StyleBox] used when the [GraphEdit] is focused (when used with assistive apps).
+		</theme_item>
 	</theme_items>
 </class>

+ 7 - 0
doc/classes/GraphNode.xml

@@ -267,6 +267,7 @@
 		</method>
 	</methods>
 	<members>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="ignore_invalid_connection_type" type="bool" setter="set_ignore_invalid_connection_type" getter="is_ignoring_valid_connection_type" default="false">
 			If [code]true[/code], you can connect ports with different types, even if the connection was not explicitly allowed in the parent [GraphEdit].
 		</member>
@@ -299,12 +300,18 @@
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			The default background for the slot area of the [GraphNode].
 		</theme_item>
+		<theme_item name="panel_focus" data_type="style" type="StyleBox">
+			[StyleBox] used when the [GraphNode] is focused (when used with assistive apps).
+		</theme_item>
 		<theme_item name="panel_selected" data_type="style" type="StyleBox">
 			The [StyleBox] used for the slot area when selected.
 		</theme_item>
 		<theme_item name="slot" data_type="style" type="StyleBox">
 			The [StyleBox] used for each slot of the [GraphNode].
 		</theme_item>
+		<theme_item name="slot_selected" data_type="style" type="StyleBox">
+			[StyleBox] used when the slot is focused (when used with assistive apps).
+		</theme_item>
 		<theme_item name="titlebar" data_type="style" type="StyleBox">
 			The [StyleBox] used for the title bar of the [GraphNode].
 		</theme_item>

+ 7 - 0
doc/classes/InputMap.xml

@@ -90,6 +90,13 @@
 				If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
 			</description>
 		</method>
+		<method name="get_action_description" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="action" type="StringName" />
+			<description>
+				Returns the human-readable description of the given action.
+			</description>
+		</method>
 		<method name="get_actions">
 			<return type="StringName[]" />
 			<description>

+ 4 - 0
doc/classes/Label.xml

@@ -58,6 +58,7 @@
 		<member name="ellipsis_char" type="String" setter="set_ellipsis_char" getter="get_ellipsis_char" default="&quot;…&quot;">
 			Ellipsis character used for text clipping.
 		</member>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
 			Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
 		</member>
@@ -153,6 +154,9 @@
 		<theme_item name="font_size" data_type="font_size" type="int">
 			Font size of the [Label]'s text.
 		</theme_item>
+		<theme_item name="focus" data_type="style" type="StyleBox">
+			[StyleBox] used when the [Label] is focused (when used with assistive apps).
+		</theme_item>
 		<theme_item name="normal" data_type="style" type="StyleBox">
 			Background [StyleBox] for the [Label].
 		</theme_item>

+ 1 - 1
doc/classes/LinkButton.xml

@@ -10,7 +10,7 @@
 	<tutorials>
 	</tutorials>
 	<members>
-		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="0" />
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
 			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
 		</member>

+ 1 - 0
doc/classes/MenuBar.xml

@@ -100,6 +100,7 @@
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
 			Flat [MenuBar] don't display item decoration.
 		</member>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" />
 		<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
 			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
 		</member>

+ 1 - 1
doc/classes/MenuButton.xml

@@ -34,7 +34,7 @@
 	<members>
 		<member name="action_mode" type="int" setter="set_action_mode" getter="get_action_mode" overrides="BaseButton" enum="BaseButton.ActionMode" default="0" />
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" overrides="Button" default="true" />
-		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="0" />
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="item_count" type="int" setter="set_item_count" getter="get_item_count" default="0">
 			The number of items currently in the list.
 		</member>

+ 60 - 0
doc/classes/Node.xml

@@ -36,6 +36,20 @@
 				Corresponds to the [constant NOTIFICATION_EXIT_TREE] notification in [method Object._notification] and signal [signal tree_exiting]. To get notified when the node has already left the active tree, connect to the [signal tree_exited].
 			</description>
 		</method>
+		<method name="_get_accessibility_configuration_warnings" qualifiers="virtual const">
+			<return type="PackedStringArray" />
+			<description>
+				The elements in the array returned from this method are displayed as warnings in the Scene dock if the script that overrides it is a [code]tool[/code] script, and accessibility warnings are enabled in the editor settings.
+				Returning an empty array produces no warnings.
+			</description>
+		</method>
+		<method name="_get_accessibility_container_name" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="node" type="Node" />
+			<description>
+				Return a human-readable description of the position of [param node] child in the custom container, added to the node name.
+			</description>
+		</method>
 		<method name="_get_configuration_warnings" qualifiers="virtual const">
 			<return type="PackedStringArray" />
 			<description>
@@ -56,6 +70,12 @@
 				[/codeblock]
 			</description>
 		</method>
+		<method name="_get_focused_accessibility_element" qualifiers="virtual const">
+			<return type="RID" />
+			<description>
+				Called during accessibility information updates to determine the currently focused sub-element, should return a sub-element RID or the value returned by [method get_accessibility_element].
+			</description>
+		</method>
 		<method name="_input" qualifiers="virtual">
 			<return type="void" />
 			<param index="0" name="event" type="InputEvent" />
@@ -299,6 +319,13 @@
 				[b]Note:[/b] As this method walks upwards in the scene tree, it can be slow in large, deeply nested nodes. Consider storing a reference to the found node in a variable. Alternatively, use [method get_node] with unique names (see [member unique_name_in_owner]).
 			</description>
 		</method>
+		<method name="get_accessibility_element" qualifiers="const">
+			<return type="RID" />
+			<description>
+				Returns main accessibility element RID.
+				[b]Note:[/b] This method should be called only during accessibility information updates ([constant NOTIFICATION_ACCESSIBILITY_UPDATE]).
+			</description>
+		</method>
 		<method name="get_child" qualifiers="const">
 			<return type="Node" />
 			<param index="0" name="idx" type="int" />
@@ -777,6 +804,12 @@
 				Calls [method Object.notification] with [param what] on this node and all of its children, recursively.
 			</description>
 		</method>
+		<method name="queue_accessibility_update">
+			<return type="void" />
+			<description>
+				Queues an accessibility information update for this node.
+			</description>
+		</method>
 		<method name="queue_free" keywords="delete, remove, kill, die">
 			<return type="void" />
 			<description>
@@ -994,6 +1027,27 @@
 		</method>
 	</methods>
 	<members>
+		<member name="accessibility_controls_nodes" type="NodePath[]" setter="set_accessibility_controls_nodes" getter="get_accessibility_controls_nodes" default="[]">
+			The list of nodes which are controlled by this node.
+		</member>
+		<member name="accessibility_described_by_nodes" type="NodePath[]" setter="set_accessibility_described_by_nodes" getter="get_accessibility_described_by_nodes" default="[]">
+			The list of nodes which are describing this node.
+		</member>
+		<member name="accessibility_description" type="String" setter="set_accessibility_description" getter="get_accessibility_description" default="&quot;&quot;">
+			The human-readable node description that is reported to assistive apps.
+		</member>
+		<member name="accessibility_flow_to_nodes" type="NodePath[]" setter="set_accessibility_flow_to_nodes" getter="get_accessibility_flow_to_nodes" default="[]">
+			The list of nodes which this node flows into.
+		</member>
+		<member name="accessibility_labeled_by_nodes" type="NodePath[]" setter="set_accessibility_labeled_by_nodes" getter="get_accessibility_labeled_by_nodes" default="[]">
+			The list of nodes which label this node.
+		</member>
+		<member name="accessibility_live" type="int" setter="set_accessibility_live" getter="get_accessibility_live" enum="DisplayServer.AccessibilityLiveMode" default="0">
+			Live region update mode, a live region is [Node] that is updated as a result of an external event when user focus may be elsewhere.
+		</member>
+		<member name="accessibility_name" type="String" setter="set_accessibility_name" getter="get_accessibility_name" default="&quot;&quot;">
+			The human-readable node name that is reported to assistive apps.
+		</member>
 		<member name="auto_translate_mode" type="int" setter="set_auto_translate_mode" getter="get_auto_translate_mode" enum="Node.AutoTranslateMode" default="0">
 			Defines if any text should automatically change to its translated version depending on the current locale (for nodes such as [Label], [RichTextLabel], [Window], etc.). Also decides if the node's strings should be parsed for POT generation.
 			[b]Note:[/b] For the root node, auto translate mode can also be set via [member ProjectSettings.internationalization/rendering/root_node_auto_translate].
@@ -1276,6 +1330,12 @@
 		<constant name="NOTIFICATION_TEXT_SERVER_CHANGED" value="2018">
 			Notification received when the [TextServer] is changed.
 		</constant>
+		<constant name="NOTIFICATION_ACCESSIBILITY_UPDATE" value="3000">
+			Notification received when an accessibility information update is required.
+		</constant>
+		<constant name="NOTIFICATION_ACCESSIBILITY_INVALIDATE" value="3001">
+			Notification received when accessibility elements are invalidated. All node accessibility elements are automatically deleted after receiving this message, therefore all existing references to such elements should be discarded.
+		</constant>
 		<constant name="PROCESS_MODE_INHERIT" value="0" enum="ProcessMode">
 			Inherits [member process_mode] from the node's parent. This is the default for any newly created node.
 		</constant>

+ 32 - 0
doc/classes/ProjectSettings.xml

@@ -235,6 +235,16 @@
 		</method>
 	</methods>
 	<members>
+		<member name="accessibility/general/accessibility_support" type="int" setter="" getter="" default="0">
+			Accessibility support mode:
+			- [b]Auto[/b] ([code]0[/code]): accessibility support is enabled, but accessibility information updates are processed only if an assistive app (e.g. screen reader or Braille display) is active (default).
+			- [b]Always Active[/b] ([code]1[/code]): accessibility support is enabled, and accessibility information updates are processed regardless of current assistive apps' status.
+			- [b]Disabled[/b] ([code]2[/code]): accessibility support is fully disabled.
+			[b]Note:[/b] Accessibility debugging tools, such as Accessibility Insights for Windows, macOS Accessibility Inspector, or AT-SPI Browser do not count as assistive apps. To test your app with these tools, use [code]1[/code].
+		</member>
+		<member name="accessibility/general/updates_per_second" type="int" setter="" getter="" default="60">
+			The number of accessibility information updates per second.
+		</member>
 		<member name="animation/warnings/check_angle_interpolation_type_conflicting" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], [AnimationMixer] prints the warning of interpolation being forced to choose the shortest rotation path due to multiple angle interpolation types being mixed in the [AnimationMixer] cache.
 		</member>
@@ -1191,6 +1201,10 @@
 			Default [InputEventAction] to confirm a focused button, menu or list item, or validate input.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
 		</member>
+		<member name="input/ui_accessibility_drag_and_drop" type="Dictionary" setter="" getter="">
+			Default [InputEventAction] to start or end a drag-and-drop operation without using mouse.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
 		<member name="input/ui_cancel" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to discard a modal or pending input.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
@@ -1227,6 +1241,10 @@
 			Default [InputEventAction] to go up one directory in a [FileDialog].
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
 		</member>
+		<member name="input/ui_focus_mode" type="Dictionary" setter="" getter="">
+			Default [InputEventAction] to switch [TextEdit] [member input/ui_text_indent] between moving keyboard focus to the next [Control] in the scene and inputting a [code]Tab[/code] character.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
 		<member name="input/ui_focus_next" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to focus the next [Control] in the scene. The focus behavior can be configured via [member Control.focus_next].
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
@@ -1243,6 +1261,20 @@
 			Default [InputEventAction] to duplicate a [GraphNode] in a [GraphEdit].
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
 		</member>
+		<member name="input/ui_graph_follow_left" type="Dictionary" setter="" getter="">
+			Default [InputEventAction] to follow a [GraphNode] input port connection.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
+		<member name="input/ui_graph_follow_left.macos" type="Dictionary" setter="" getter="">
+			macOS specific override for the shortcut to follow a [GraphNode] input port connection.
+		</member>
+		<member name="input/ui_graph_follow_right" type="Dictionary" setter="" getter="">
+			Default [InputEventAction] to follow a [GraphNode] output port connection.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
+		<member name="input/ui_graph_follow_right.macos" type="Dictionary" setter="" getter="">
+			macOS specific override for the shortcut to follow a [GraphNode] output port connection.
+		</member>
 		<member name="input/ui_home" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to go to the start position of a [Control] (e.g. first item in an [ItemList] or a [Tree]), matching the behavior of [constant KEY_HOME] on typical desktop UI systems.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.

+ 13 - 1
doc/classes/RichTextLabel.xml

@@ -29,6 +29,7 @@
 			<param index="7" name="pad" type="bool" default="false" />
 			<param index="8" name="tooltip" type="String" default="&quot;&quot;" />
 			<param index="9" name="size_in_percent" type="bool" default="false" />
+			<param index="10" name="alt_text" type="String" default="&quot;&quot;" />
 			<description>
 				Adds an image's opening and closing tags to the tag stack, optionally providing a [param width] and [param height] to resize the image, a [param color] to tint the image and a [param region] to only use parts of the image.
 				If [param width] or [param height] is set to 0, the image size will be adjusted in order to keep the original aspect ratio.
@@ -36,6 +37,7 @@
 				[param key] is an optional identifier, that can be used to modify the image via [method update_image].
 				If [param pad] is set, and the image is smaller than the size specified by [param width] and [param height], the image padding is added to match the size instead of upscaling.
 				If [param size_in_percent] is set, [param width] and [param height] values are percentages of the control width instead of pixels.
+				[param alt_text] is used as the image description for assistive apps.
 			</description>
 		</method>
 		<method name="add_text">
@@ -517,8 +519,9 @@
 			<param index="0" name="columns" type="int" />
 			<param index="1" name="inline_align" type="int" enum="InlineAlignment" default="0" />
 			<param index="2" name="align_to_row" type="int" default="-1" />
+			<param index="3" name="name" type="String" default="&quot;&quot;" />
 			<description>
-				Adds a [code skip-lint][table=columns,inline_align][/code] tag to the tag stack. Use [method set_table_column_expand] to set column expansion ratio. Use [method push_cell] to add cells.
+				Adds a [code skip-lint][table=columns,inline_align][/code] tag to the tag stack. Use [method set_table_column_expand] to set column expansion ratio. Use [method push_cell] to add cells. [param name] is used as the table name for assistive apps.
 			</description>
 		</method>
 		<method name="push_underline">
@@ -612,6 +615,14 @@
 				If [param expand] is [code]false[/code], the column will not contribute to the total ratio.
 			</description>
 		</method>
+		<method name="set_table_column_name">
+			<return type="void" />
+			<param index="0" name="column" type="int" />
+			<param index="1" name="name" type="String" />
+			<description>
+				Sets table column name for assistive apps.
+			</description>
+		</method>
 		<method name="update_image">
 			<return type="void" />
 			<param index="0" name="key" type="Variant" />
@@ -658,6 +669,7 @@
 		<member name="fit_content" type="bool" setter="set_fit_content" getter="is_fit_content_enabled" default="false">
 			If [code]true[/code], the label's minimum size will be automatically updated to fit its content, matching the behavior of [Label].
 		</member>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
 			If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
 		</member>

+ 12 - 0
doc/classes/SceneTree.xml

@@ -152,6 +152,18 @@
 				Returns [code]true[/code] if a node added to the given group [param name] exists in the tree.
 			</description>
 		</method>
+		<method name="is_accessibility_enabled" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if accessibility features are enabled, and accessibility information updates are actively processed.
+			</description>
+		</method>
+		<method name="is_accessibility_supported" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if accessibility features are supported by the OS and enabled in project settings.
+			</description>
+		</method>
 		<method name="notify_group">
 			<return type="void" />
 			<param index="0" name="group" type="StringName" />

+ 1 - 0
doc/classes/ScrollBar.xml

@@ -12,6 +12,7 @@
 		<member name="custom_step" type="float" setter="set_custom_step" getter="get_custom_step" default="-1.0">
 			Overrides the step used when clicking increment and decrement buttons or when using arrow keys when the [ScrollBar] is focused.
 		</member>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="3" />
 		<member name="step" type="float" setter="set_step" getter="get_step" overrides="Range" default="0.0" />
 	</members>
 	<signals>

+ 3 - 0
doc/classes/TextEdit.xml

@@ -1380,6 +1380,9 @@
 			The syntax highlighter to use.
 			[b]Note:[/b] A [SyntaxHighlighter] instance should not be used across multiple [TextEdit] nodes.
 		</member>
+		<member name="tab_input_mode" type="bool" setter="set_tab_input_mode" getter="get_tab_input_mode" default="true">
+			If [code]true[/code], [member ProjectSettings.input/ui_text_indent] input [code]Tab[/code] character, otherwise it moves keyboard focus to the next [Control] in the scene.
+		</member>
 		<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
 			String value of the [TextEdit].
 		</member>

+ 6 - 0
doc/classes/TextLine.xml

@@ -56,6 +56,12 @@
 				Draw text into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box.
 			</description>
 		</method>
+		<method name="get_inferred_direction" qualifiers="const">
+			<return type="int" enum="TextServer.Direction" />
+			<description>
+				Returns the text writing direction inferred by the BiDi algorithm.
+			</description>
+		</method>
 		<method name="get_line_ascent" qualifiers="const">
 			<return type="float" />
 			<description>

+ 12 - 0
doc/classes/TextParagraph.xml

@@ -122,6 +122,12 @@
 				Returns drop cap bounding box size.
 			</description>
 		</method>
+		<method name="get_inferred_direction" qualifiers="const">
+			<return type="int" enum="TextServer.Direction" />
+			<description>
+				Returns the text writing direction inferred by the BiDi algorithm.
+			</description>
+		</method>
 		<method name="get_line_ascent" qualifiers="const">
 			<return type="float" />
 			<param index="0" name="line" type="int" />
@@ -205,6 +211,12 @@
 				Returns the size of the bounding box of the paragraph, without line breaks.
 			</description>
 		</method>
+		<method name="get_range" qualifiers="const">
+			<return type="Vector2i" />
+			<description>
+				Returns the character range of the paragraph.
+			</description>
+		</method>
 		<method name="get_rid" qualifiers="const">
 			<return type="RID" />
 			<description>

+ 86 - 0
doc/classes/TextServer.xml

@@ -1215,6 +1215,69 @@
 				[b]Note:[/b] This function is used by during project export, to include TextServer database.
 			</description>
 		</method>
+		<method name="shaped_get_run_count" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="shaped" type="RID" />
+			<description>
+				Returns the number of uniform text runs in the buffer.
+			</description>
+		</method>
+		<method name="shaped_get_run_direction" qualifiers="const">
+			<return type="int" enum="TextServer.Direction" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the direction of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_font_rid" qualifiers="const">
+			<return type="RID" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the font RID of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_font_size" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the font size of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_language" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the language of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_object" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the embedded object of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_range" qualifiers="const">
+			<return type="Vector2i" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the source text range of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="shaped_get_run_text" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the source text of the [param index] text run (in visual order).
+			</description>
+		</method>
 		<method name="shaped_get_span_count" qualifiers="const">
 			<return type="int" />
 			<param index="0" name="shaped" type="RID" />
@@ -1238,6 +1301,29 @@
 				Returns text span metadata.
 			</description>
 		</method>
+		<method name="shaped_get_span_object" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the text span embedded object key.
+			</description>
+		</method>
+		<method name="shaped_get_span_text" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				Returns the text span source text.
+			</description>
+		</method>
+		<method name="shaped_get_text" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<description>
+				Returns the text buffer source text, including object replacement characters.
+			</description>
+		</method>
 		<method name="shaped_set_span_update_font">
 			<return type="void" />
 			<param index="0" name="shaped" type="RID" />

+ 97 - 0
doc/classes/TextServerExtension.xml

@@ -1323,6 +1323,77 @@
 				Saves optional TextServer database (e.g. ICU break iterators and dictionaries) to the file.
 			</description>
 		</method>
+		<method name="_shaped_get_run_count" qualifiers="virtual const">
+			<return type="int" />
+			<param index="0" name="shaped" type="RID" />
+			<description>
+				[b]Required.[/b]
+				Returns the number of uniform text runs in the buffer.
+			</description>
+		</method>
+		<method name="_shaped_get_run_direction" qualifiers="virtual const">
+			<return type="int" enum="TextServer.Direction" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the direction of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_font_rid" qualifiers="virtual const">
+			<return type="RID" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the font RID of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_font_size" qualifiers="virtual const">
+			<return type="int" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the font size of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_language" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the language of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_object" qualifiers="virtual const">
+			<return type="Variant" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the embedded object of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_range" qualifiers="virtual const">
+			<return type="Vector2i" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the source text range of the [param index] text run (in visual order).
+			</description>
+		</method>
+		<method name="_shaped_get_run_text" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the source text of the [param index] text run (in visual order).
+			</description>
+		</method>
 		<method name="_shaped_get_span_count" qualifiers="virtual const">
 			<return type="int" />
 			<param index="0" name="shaped" type="RID" />
@@ -1349,6 +1420,32 @@
 				Returns text span metadata.
 			</description>
 		</method>
+		<method name="_shaped_get_span_object" qualifiers="virtual const">
+			<return type="Variant" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the text span embedded object key.
+			</description>
+		</method>
+		<method name="_shaped_get_span_text" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="index" type="int" />
+			<description>
+				[b]Required.[/b]
+				Returns the text span source text.
+			</description>
+		</method>
+		<method name="_shaped_get_text" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="shaped" type="RID" />
+			<description>
+				[b]Required.[/b]
+				Returns the text buffer source text, including object replacement characters.
+			</description>
+		</method>
 		<method name="_shaped_set_span_update_font" qualifiers="virtual">
 			<return type="void" />
 			<param index="0" name="shaped" type="RID" />

+ 26 - 1
doc/classes/TreeItem.xml

@@ -18,8 +18,9 @@
 			<param index="2" name="id" type="int" default="-1" />
 			<param index="3" name="disabled" type="bool" default="false" />
 			<param index="4" name="tooltip_text" type="String" default="&quot;&quot;" />
+			<param index="5" name="alt_text" type="String" default="&quot;&quot;" />
 			<description>
-				Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text].
+				Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text]. [param alt_text] is used as the button description for assistive apps.
 			</description>
 		</method>
 		<method name="add_child">
@@ -79,6 +80,13 @@
 				Removes the button at index [param button_index] in column [param column].
 			</description>
 		</method>
+		<method name="get_alt_text" qualifiers="const">
+			<return type="String" />
+			<param index="0" name="column" type="int" />
+			<description>
+				Returns the given column's alternative text.
+			</description>
+		</method>
 		<method name="get_auto_translate_mode" qualifiers="const">
 			<return type="int" enum="Node.AutoTranslateMode" />
 			<param index="0" name="column" type="int" />
@@ -506,6 +514,14 @@
 				Selects the given [param column].
 			</description>
 		</method>
+		<method name="set_alt_text">
+			<return type="void" />
+			<param index="0" name="column" type="int" />
+			<param index="1" name="text" type="String" />
+			<description>
+				Sets the given column's alternative (description) text for assistive apps.
+			</description>
+		</method>
 		<method name="set_auto_translate_mode">
 			<return type="void" />
 			<param index="0" name="column" type="int" />
@@ -532,6 +548,15 @@
 				Sets the given column's button [Texture2D] at index [param button_index] to [param button].
 			</description>
 		</method>
+		<method name="set_button_alt_text">
+			<return type="void" />
+			<param index="0" name="column" type="int" />
+			<param index="1" name="button_index" type="int" />
+			<param index="2" name="alt_text" type="String" />
+			<description>
+				Sets the given column's button alternative text (description) at index [param button_index] for assistive apps.
+			</description>
+		</method>
 		<method name="set_button_color">
 			<return type="void" />
 			<param index="0" name="column" type="int" />

+ 13 - 0
doc/classes/Viewport.xml

@@ -148,6 +148,12 @@
 				Returns the drag data from the GUI, that was previously returned by [method Control._get_drag_data].
 			</description>
 		</method>
+		<method name="gui_get_drag_description" qualifiers="const">
+			<return type="String" />
+			<description>
+				Returns the drag data human-readable description.
+			</description>
+		</method>
 		<method name="gui_get_focus_owner" qualifiers="const">
 			<return type="Control" />
 			<description>
@@ -180,6 +186,13 @@
 				Removes the focus from the currently focused [Control] within this viewport. If no [Control] has the focus, does nothing.
 			</description>
 		</method>
+		<method name="gui_set_drag_description">
+			<return type="void" />
+			<param index="0" name="description" type="String" />
+			<description>
+				Sets the drag data human-readable description.
+			</description>
+		</method>
 		<method name="is_input_handled" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/Window.xml

@@ -108,6 +108,12 @@
 				Returns [code]true[/code] if the [param flag] is set.
 			</description>
 		</method>
+		<method name="get_focused_window" qualifiers="static">
+			<return type="Window" />
+			<description>
+				Returns the focused window.
+			</description>
+		</method>
 		<method name="get_layout_direction" qualifiers="const">
 			<return type="int" enum="Window.LayoutDirection" />
 			<description>

+ 49 - 6
main/main.cpp

@@ -202,6 +202,8 @@ static int audio_driver_idx = -1;
 
 // Engine config/tools
 
+static DisplayServer::AccessibilityMode accessibility_mode = DisplayServer::AccessibilityMode::ACCESSIBILITY_AUTO;
+static bool accessibility_mode_set = false;
 static bool single_window = false;
 static bool editor = false;
 static bool project_manager = false;
@@ -616,6 +618,7 @@ void Main::print_help(const char *p_binary) {
 	print_help_option("--xr-mode <mode>", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n");
 #endif
 	print_help_option("--wid <window_id>", "Request parented to window.\n");
+	print_help_option("--accessibility <mode>", "Select accessibility mode ['auto' (when screen reader is running, default), 'always', 'disabled'].\n");
 
 	print_help_title("Debug options");
 	print_help_option("-d, --debug", "Debug (local stdout debugger).\n");
@@ -1300,6 +1303,27 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 		} else if (arg == "--single-window") { // force single window
 
 			single_window = true;
+		} else if (arg == "--accessibility") {
+			if (N) {
+				String string = N->get();
+				if (string == "auto") {
+					accessibility_mode = DisplayServer::AccessibilityMode::ACCESSIBILITY_AUTO;
+					accessibility_mode_set = true;
+				} else if (string == "always") {
+					accessibility_mode = DisplayServer::AccessibilityMode::ACCESSIBILITY_ALWAYS;
+					accessibility_mode_set = true;
+				} else if (string == "disabled") {
+					accessibility_mode = DisplayServer::AccessibilityMode::ACCESSIBILITY_DISABLED;
+					accessibility_mode_set = true;
+				} else {
+					OS::get_singleton()->print("Accessibility mode argument not recognized, aborting.\n");
+					goto error;
+				}
+				N = N->next();
+			} else {
+				OS::get_singleton()->print("Missing accessibility mode argument, aborting.\n");
+				goto error;
+			}
 		} else if (arg == "-t" || arg == "--always-on-top") { // force always-on-top window
 
 			init_always_on_top = true;
@@ -2360,14 +2384,16 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 		default_renderer_mobile = "gl_compatibility";
 	}
 #endif
-	if (renderer_hints.is_empty()) {
-		ERR_PRINT("No renderers available.");
+	if (!renderer_hints.is_empty()) {
+		renderer_hints += ",";
 	}
+	renderer_hints += "dummy";
 
 	if (!rendering_method.is_empty()) {
 		if (rendering_method != "forward_plus" &&
 				rendering_method != "mobile" &&
-				rendering_method != "gl_compatibility") {
+				rendering_method != "gl_compatibility" &&
+				rendering_method != "dummy") {
 			OS::get_singleton()->print("Unknown rendering method '%s', aborting.\nValid options are ",
 					rendering_method.utf8().get_data());
 
@@ -2435,7 +2461,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 		// Set a default renderer if none selected. Try to choose one that matches the driver.
 		if (rendering_method.is_empty()) {
-			if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle" || rendering_driver == "opengl3_es") {
+			if (rendering_driver == "dummy") {
+				rendering_method = "dummy";
+			} else if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle" || rendering_driver == "opengl3_es") {
 				rendering_method = "gl_compatibility";
 			} else {
 				rendering_method = "forward_plus";
@@ -2463,6 +2491,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 			available_drivers.push_back("opengl3_es");
 		}
 #endif
+		if (rendering_method == "dummy") {
+			available_drivers.push_back("dummy");
+		}
 		if (available_drivers.is_empty()) {
 			OS::get_singleton()->print("Unknown renderer name '%s', aborting.\n", rendering_method.utf8().get_data());
 			goto error;
@@ -2499,7 +2530,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 	}
 
 	if (rendering_driver.is_empty()) {
-		if (rendering_method == "gl_compatibility") {
+		if (rendering_method == "dummy") {
+			rendering_driver = "dummy";
+		} else if (rendering_method == "gl_compatibility") {
 			rendering_driver = GLOBAL_GET("rendering/gl_compatibility/driver");
 		} else {
 			rendering_driver = GLOBAL_GET("rendering/rendering_device/driver");
@@ -3095,6 +3128,11 @@ Error Main::setup2(bool p_show_boot_logo) {
 		}
 #endif
 
+		if (!accessibility_mode_set) {
+			accessibility_mode = (DisplayServer::AccessibilityMode)GLOBAL_GET("accessibility/general/accessibility_support").operator int64_t();
+		}
+		DisplayServer::accessibility_set_mode(accessibility_mode);
+
 		// rendering_driver now held in static global String in main and initialized in setup()
 		Error err;
 		display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, init_embed_parent_window_id, err);
@@ -4791,7 +4829,12 @@ bool Main::iteration() {
 		return exit;
 	}
 
-	OS::get_singleton()->add_frame_delay(DisplayServer::get_singleton()->window_can_draw());
+	SceneTree *scene_tree = SceneTree::get_singleton();
+	bool skip_delay = scene_tree && scene_tree->is_accessibility_enabled();
+
+	if (!skip_delay) {
+		OS::get_singleton()->add_frame_delay(DisplayServer::get_singleton()->window_can_draw());
+	}
 
 #ifdef TOOLS_ENABLED
 	if (auto_build_solutions) {

+ 10 - 0
misc/extension_api_validation/4.4-stable.expected

@@ -31,3 +31,13 @@ GH-104890
 Validate extension JSON: API was removed: classes/JSONRPC/methods/set_scope
 
 Replaced `set_scope` with `set_method`. Compatibility method registered for binary compatibility. Manual upgrade required by users to retain functionality.
+
+
+GH-76829
+--------
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 6 to 11.
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 10 to 11.
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/push_table/arguments': size changed value in new API, from 3 to 4.
+Validate extension JSON: Error: Field 'classes/TreeItem/methods/add_button/arguments': size changed value in new API, from 5 to 6.
+
+Added optional arguments. Compatibility methods registered.

+ 220 - 0
modules/text_server_adv/text_server_adv.cpp

@@ -4210,6 +4210,8 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
 	p_shaped->uthk = 0.0;
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs_logical.clear();
+	p_shaped->runs.clear();
+	p_shaped->runs_dirty = true;
 	p_shaped->overrun_trim_data = TrimData();
 	p_shaped->utf16 = Char16String();
 	for (int i = 0; i < p_shaped->bidi_iter.size(); i++) {
@@ -4492,6 +4494,213 @@ Variant TextServerAdvanced::_shaped_get_span_embedded_object(const RID &p_shaped
 	}
 }
 
+String TextServerAdvanced::_shaped_get_span_text(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	ShapedTextDataAdvanced *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, String());
+	}
+	ERR_FAIL_INDEX_V(p_index, span_sd->spans.size(), String());
+	return span_sd->text.substr(span_sd->spans[p_index].start, span_sd->spans[p_index].end - span_sd->spans[p_index].start);
+}
+
+Variant TextServerAdvanced::_shaped_get_span_object(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Variant());
+	ShapedTextDataAdvanced *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, Variant());
+	}
+	ERR_FAIL_INDEX_V(p_index, span_sd->spans.size(), Variant());
+	return span_sd->spans[p_index].embedded_key;
+}
+
+void TextServerAdvanced::_generate_runs(ShapedTextDataAdvanced *p_sd) const {
+	ERR_FAIL_NULL(p_sd);
+	p_sd->runs.clear();
+
+	ShapedTextDataAdvanced *span_sd = p_sd;
+	if (p_sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(p_sd->parent);
+		ERR_FAIL_NULL(span_sd);
+	}
+
+	int sd_size = p_sd->glyphs.size();
+	const Glyph *sd_gl = p_sd->glyphs.ptr();
+
+	int span_count = span_sd->spans.size();
+	int span = -1;
+	int span_start = -1;
+	int span_end = -1;
+
+	TextRun run;
+	for (int i = 0; i < sd_size; i += sd_gl[i].count) {
+		const Glyph &gl = sd_gl[i];
+		if (gl.start < 0 || gl.end < 0) {
+			continue;
+		}
+		if (gl.start < span_start || gl.start >= span_end) {
+			span = -1;
+			span_start = -1;
+			span_end = -1;
+			for (int j = 0; j < span_count; j++) {
+				if (gl.start >= span_sd->spans[j].start && gl.end <= span_sd->spans[j].end) {
+					span = j;
+					span_start = span_sd->spans[j].start;
+					span_end = span_sd->spans[j].end;
+					break;
+				}
+			}
+		}
+		if (run.font_rid != gl.font_rid || run.font_size != gl.font_size || run.span_index != span || run.rtl != bool(gl.flags & GRAPHEME_IS_RTL)) {
+			if (run.span_index >= 0) {
+				p_sd->runs.push_back(run);
+			}
+			run.range = Vector2i(gl.start, gl.end);
+			run.font_rid = gl.font_rid;
+			run.font_size = gl.font_size;
+			run.rtl = bool(gl.flags & GRAPHEME_IS_RTL);
+			run.span_index = span;
+		}
+		run.range.x = MIN(run.range.x, gl.start);
+		run.range.y = MAX(run.range.y, gl.end);
+	}
+	if (run.span_index >= 0) {
+		p_sd->runs.push_back(run);
+	}
+	p_sd->runs_dirty = false;
+}
+
+int64_t TextServerAdvanced::_shaped_get_run_count(const RID &p_shaped) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, 0);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	return sd->runs.size();
+}
+
+String TextServerAdvanced::_shaped_get_run_text(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), String());
+	return sd->text.substr(sd->runs[p_index].range.x - sd->start, sd->runs[p_index].range.y - sd->runs[p_index].range.x);
+}
+
+Vector2i TextServerAdvanced::_shaped_get_run_range(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Vector2i());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), Vector2i());
+	return sd->runs[p_index].range;
+}
+
+RID TextServerAdvanced::_shaped_get_run_font_rid(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, RID());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), RID());
+	return sd->runs[p_index].font_rid;
+}
+
+int TextServerAdvanced::_shaped_get_run_font_size(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, 0);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), 0);
+	return sd->runs[p_index].font_size;
+}
+
+String TextServerAdvanced::_shaped_get_run_language(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), String());
+
+	int span_idx = sd->runs[p_index].span_index;
+	ShapedTextDataAdvanced *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, String());
+	}
+	ERR_FAIL_INDEX_V(span_idx, span_sd->spans.size(), String());
+	return span_sd->spans[span_idx].language;
+}
+
+TextServer::Direction TextServerAdvanced::_shaped_get_run_direction(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), TextServer::DIRECTION_LTR);
+	return sd->runs[p_index].rtl ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
+}
+
+Variant TextServerAdvanced::_shaped_get_run_object(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Variant());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), Variant());
+
+	int span_idx = sd->runs[p_index].span_index;
+	ShapedTextDataAdvanced *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, Variant());
+	}
+	ERR_FAIL_INDEX_V(span_idx, span_sd->spans.size(), Variant());
+	return span_sd->spans[span_idx].embedded_key;
+}
+
 void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL(sd);
@@ -4575,6 +4784,13 @@ bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Vari
 	return true;
 }
 
+String TextServerAdvanced::_shaped_get_text(const RID &p_shaped) const {
+	const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+
+	return sd->text;
+}
+
 bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
@@ -4771,6 +4987,8 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
 	p_new_sd->sort_valid = false;
 	p_new_sd->upos = p_sd->upos;
 	p_new_sd->uthk = p_sd->uthk;
+	p_new_sd->runs.clear();
+	p_new_sd->runs_dirty = true;
 
 	if (p_length > 0) {
 		p_new_sd->text = p_sd->text.substr(p_start - p_sd->start, p_length);
@@ -6560,6 +6778,8 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
 	} else {
 		bidi_ranges = sd->bidi_override;
 	}
+	sd->runs.clear();
+	sd->runs_dirty = true;
 
 	for (int ov = 0; ov < bidi_ranges.size(); ov++) {
 		// Create BiDi iterator.

+ 24 - 0
modules/text_server_adv/text_server_adv.h

@@ -458,6 +458,14 @@ class TextServerAdvanced : public TextServerExtension {
 		Vector<Glyph> ellipsis_glyph_buf;
 	};
 
+	struct TextRun {
+		Vector2i range;
+		RID font_rid;
+		int font_size = 0;
+		bool rtl = false;
+		int64_t span_index = -1;
+	};
+
 	struct ShapedTextDataAdvanced {
 		Mutex mutex;
 
@@ -489,6 +497,9 @@ class TextServerAdvanced : public TextServerExtension {
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int last_span = 0;
 
+		Vector<TextRun> runs;
+		bool runs_dirty = true;
+
 		struct EmbeddedObject {
 			int start = -1;
 			int end = -1;
@@ -664,6 +675,7 @@ class TextServerAdvanced : public TextServerExtension {
 	mutable HashMap<String, PackedByteArray> system_font_data;
 
 	void _update_chars(ShapedTextDataAdvanced *p_sd) const;
+	void _generate_runs(ShapedTextDataAdvanced *p_sd) const;
 	void _realign(ShapedTextDataAdvanced *p_sd) const;
 	int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const;
 	int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const;
@@ -956,12 +968,24 @@ public:
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
+	MODBIND1RC(String, shaped_get_text, const RID &);
 
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t);
+	MODBIND2RC(String, shaped_get_span_text, const RID &, int64_t);
+	MODBIND2RC(Variant, shaped_get_span_object, const RID &, int64_t);
 	MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
 
+	MODBIND1RC(int64_t, shaped_get_run_count, const RID &);
+	MODBIND2RC(String, shaped_get_run_text, const RID &, int64_t);
+	MODBIND2RC(Vector2i, shaped_get_run_range, const RID &, int64_t);
+	MODBIND2RC(RID, shaped_get_run_font_rid, const RID &, int64_t);
+	MODBIND2RC(int, shaped_get_run_font_size, const RID &, int64_t);
+	MODBIND2RC(String, shaped_get_run_language, const RID &, int64_t);
+	MODBIND2RC(Direction, shaped_get_run_direction, const RID &, int64_t);
+	MODBIND2RC(Variant, shaped_get_run_object, const RID &, int64_t);
+
 	MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
 	MODBIND1RC(RID, shaped_text_get_parent, const RID &);
 

+ 217 - 0
modules/text_server_fb/text_server_fb.cpp

@@ -3105,6 +3105,8 @@ void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
 	p_shaped->uthk = 0.0;
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs_logical.clear();
+	p_shaped->runs.clear();
+	p_shaped->runs_dirty = true;
 }
 
 void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
@@ -3339,6 +3341,212 @@ Variant TextServerFallback::_shaped_get_span_embedded_object(const RID &p_shaped
 	}
 }
 
+String TextServerFallback::_shaped_get_span_text(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	ShapedTextDataFallback *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, String());
+	}
+	ERR_FAIL_INDEX_V(p_index, span_sd->spans.size(), String());
+	return span_sd->text.substr(span_sd->spans[p_index].start, span_sd->spans[p_index].end - span_sd->spans[p_index].start);
+}
+
+Variant TextServerFallback::_shaped_get_span_object(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Variant());
+	ShapedTextDataFallback *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, Variant());
+	}
+	ERR_FAIL_INDEX_V(p_index, span_sd->spans.size(), Variant());
+	return span_sd->spans[p_index].embedded_key;
+}
+
+void TextServerFallback::_generate_runs(ShapedTextDataFallback *p_sd) const {
+	ERR_FAIL_NULL(p_sd);
+	p_sd->runs.clear();
+
+	ShapedTextDataFallback *span_sd = p_sd;
+	if (p_sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(p_sd->parent);
+		ERR_FAIL_NULL(span_sd);
+	}
+
+	int sd_size = p_sd->glyphs.size();
+	Glyph *sd_gl = p_sd->glyphs.ptrw();
+
+	int span_count = span_sd->spans.size();
+	int span = -1;
+	int span_start = -1;
+	int span_end = -1;
+
+	TextRun run;
+	for (int i = 0; i < sd_size; i += sd_gl[i].count) {
+		const Glyph &gl = sd_gl[i];
+		if (gl.start < 0 || gl.end < 0) {
+			continue;
+		}
+		if (gl.start < span_start || gl.start >= span_end) {
+			span = -1;
+			span_start = -1;
+			span_end = -1;
+			for (int j = 0; j < span_count; j++) {
+				if (gl.start >= span_sd->spans[j].start && gl.end <= span_sd->spans[j].end) {
+					span = j;
+					span_start = span_sd->spans[j].start;
+					span_end = span_sd->spans[j].end;
+					break;
+				}
+			}
+		}
+		if (run.font_rid != gl.font_rid || run.font_size != gl.font_size || run.span_index != span) {
+			if (run.span_index >= 0) {
+				p_sd->runs.push_back(run);
+			}
+			run.range = Vector2i(gl.start, gl.end);
+			run.font_rid = gl.font_rid;
+			run.font_size = gl.font_size;
+			run.span_index = span;
+		}
+		run.range.x = MIN(run.range.x, gl.start);
+		run.range.y = MAX(run.range.y, gl.end);
+	}
+	if (run.span_index >= 0) {
+		p_sd->runs.push_back(run);
+	}
+	p_sd->runs_dirty = false;
+}
+
+int64_t TextServerFallback::_shaped_get_run_count(const RID &p_shaped) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, 0);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	return sd->runs.size();
+}
+
+String TextServerFallback::_shaped_get_run_text(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), String());
+	return sd->text.substr(sd->runs[p_index].range.x - sd->start, sd->runs[p_index].range.y - sd->runs[p_index].range.x);
+}
+
+Vector2i TextServerFallback::_shaped_get_run_range(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Vector2i());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), Vector2i());
+	return sd->runs[p_index].range;
+}
+
+RID TextServerFallback::_shaped_get_run_font_rid(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, RID());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), RID());
+	return sd->runs[p_index].font_rid;
+}
+
+int TextServerFallback::_shaped_get_run_font_size(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, 0);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), 0);
+	return sd->runs[p_index].font_size;
+}
+
+String TextServerFallback::_shaped_get_run_language(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), String());
+
+	int span_idx = sd->runs[p_index].span_index;
+	ShapedTextDataFallback *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, String());
+	}
+	ERR_FAIL_INDEX_V(span_idx, span_sd->spans.size(), String());
+	return span_sd->spans[span_idx].language;
+}
+
+TextServer::Direction TextServerFallback::_shaped_get_run_direction(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR);
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), TextServer::DIRECTION_LTR);
+	return TextServer::DIRECTION_LTR;
+}
+
+Variant TextServerFallback::_shaped_get_run_object(const RID &p_shaped, int64_t p_index) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, Variant());
+	MutexLock lock(sd->mutex);
+	if (!sd->valid.is_set()) {
+		const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+	}
+	if (sd->runs_dirty) {
+		_generate_runs(sd);
+	}
+	ERR_FAIL_INDEX_V(p_index, sd->runs.size(), Variant());
+
+	int span_idx = sd->runs[p_index].span_index;
+	ShapedTextDataFallback *span_sd = sd;
+	if (sd->parent.is_valid()) {
+		span_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_NULL_V(span_sd, Variant());
+	}
+	ERR_FAIL_INDEX_V(span_idx, span_sd->spans.size(), Variant());
+	return span_sd->spans[span_idx].embedded_key;
+}
+
 void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL(sd);
@@ -3450,6 +3658,13 @@ bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Vari
 	return true;
 }
 
+String TextServerFallback::_shaped_get_text(const RID &p_shaped) const {
+	const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, String());
+
+	return sd->text;
+}
+
 bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
@@ -4385,6 +4600,8 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
 	sd->descent = 0.0;
 	sd->width = 0.0;
 	sd->glyphs.clear();
+	sd->runs.clear();
+	sd->runs_dirty = true;
 
 	if (sd->text.length() == 0) {
 		sd->valid.set();

+ 23 - 0
modules/text_server_fb/text_server_fb.h

@@ -400,6 +400,13 @@ class TextServerFallback : public TextServerExtension {
 		Vector<Glyph> ellipsis_glyph_buf;
 	};
 
+	struct TextRun {
+		Vector2i range;
+		RID font_rid;
+		int font_size = 0;
+		int64_t span_index = -1;
+	};
+
 	struct ShapedTextDataFallback {
 		Mutex mutex;
 
@@ -431,6 +438,9 @@ class TextServerFallback : public TextServerExtension {
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int last_span = 0;
 
+		Vector<TextRun> runs;
+		bool runs_dirty = true;
+
 		struct EmbeddedObject {
 			int start = -1;
 			int end = -1;
@@ -575,6 +585,7 @@ class TextServerFallback : public TextServerExtension {
 	mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
 	mutable HashMap<String, PackedByteArray> system_font_data;
 
+	void _generate_runs(ShapedTextDataFallback *p_sd) const;
 	void _realign(ShapedTextDataFallback *p_sd) const;
 	_FORCE_INLINE_ RID _find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text);
 
@@ -820,12 +831,24 @@ public:
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
+	MODBIND1RC(String, shaped_get_text, const RID &);
 
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t);
+	MODBIND2RC(String, shaped_get_span_text, const RID &, int64_t);
+	MODBIND2RC(Variant, shaped_get_span_object, const RID &, int64_t);
 	MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
 
+	MODBIND1RC(int64_t, shaped_get_run_count, const RID &);
+	MODBIND2RC(String, shaped_get_run_text, const RID &, int64_t);
+	MODBIND2RC(Vector2i, shaped_get_run_range, const RID &, int64_t);
+	MODBIND2RC(RID, shaped_get_run_font_rid, const RID &, int64_t);
+	MODBIND2RC(int, shaped_get_run_font_size, const RID &, int64_t);
+	MODBIND2RC(String, shaped_get_run_language, const RID &, int64_t);
+	MODBIND2RC(Direction, shaped_get_run_direction, const RID &, int64_t);
+	MODBIND2RC(Variant, shaped_get_run_object, const RID &, int64_t);
+
 	MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
 	MODBIND1RC(RID, shaped_text_get_parent, const RID &);
 

+ 11 - 0
scene/2d/animated_sprite_2d.cpp

@@ -167,6 +167,17 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const {
 
 void AnimatedSprite2D::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			Rect2 dst_rect = _get_rect();
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_IMAGE);
+			DisplayServer::get_singleton()->accessibility_update_set_transform(ae, get_transform());
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(ae, dst_rect);
+		} break;
+
 		case NOTIFICATION_READY: {
 			if (!Engine::get_singleton()->is_editor_hint() && frames.is_valid() && frames->has_animation(autoplay)) {
 				play(autoplay);

+ 2 - 2
scene/2d/gpu_particles_2d.cpp

@@ -388,11 +388,11 @@ PackedStringArray GPUParticles2D::get_configuration_warnings() const {
 		}
 	}
 
-	if (trail_enabled && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (trail_enabled && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
 		warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile renderers."));
 	}
 
-	if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (sub_emitter != NodePath() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
 		warnings.push_back(RTR("Particle sub-emitters are not available when using the Compatibility renderer."));
 	}
 

+ 7 - 0
scene/2d/node_2d.cpp

@@ -429,6 +429,13 @@ Point2 Node2D::to_global(Point2 p_local) const {
 
 void Node2D::_notification(int p_notification) {
 	switch (p_notification) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			ERR_MAIN_THREAD_GUARD;
 

+ 19 - 0
scene/2d/physics/touch_screen_button.cpp

@@ -112,8 +112,27 @@ bool TouchScreenButton::is_shape_centered() const {
 	return shape_centered;
 }
 
+void TouchScreenButton::_accessibility_action_click(const Variant &p_data) {
+	_press(0);
+	_release();
+}
+
 void TouchScreenButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			Rect2 dst_rect(Point2(), texture_normal.is_valid() ? texture_normal->get_size() : Size2());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &TouchScreenButton::_accessibility_action_click));
+
+			DisplayServer::get_singleton()->accessibility_update_set_transform(ae, get_transform());
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(ae, dst_rect);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			if (!is_inside_tree()) {
 				return;

+ 2 - 0
scene/2d/physics/touch_screen_button.h

@@ -74,6 +74,8 @@ protected:
 	bool _set(const StringName &p_name, const Variant &p_value);
 #endif // DISABLE_DEPRECATED
 
+	void _accessibility_action_click(const Variant &p_data);
+
 public:
 #ifdef DEBUG_ENABLED
 	virtual Rect2 _edit_get_rect() const override;

+ 11 - 0
scene/2d/sprite_2d.cpp

@@ -115,6 +115,17 @@ void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_c
 
 void Sprite2D::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			Rect2 dst_rect = get_rect();
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_IMAGE);
+			DisplayServer::get_singleton()->accessibility_update_set_transform(ae, get_transform());
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(ae, dst_rect);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			if (texture.is_null()) {
 				return;

+ 1 - 1
scene/3d/decal.cpp

@@ -179,7 +179,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const {
 PackedStringArray Decal::get_configuration_warnings() const {
 	PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
 
-	if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy") {
 		warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile renderers."));
 		return warnings;
 	}

+ 2 - 2
scene/3d/gpu_particles_3d.cpp

@@ -421,12 +421,12 @@ PackedStringArray GPUParticles3D::get_configuration_warnings() const {
 		if ((dp_count || skin.is_valid()) && (missing_trails || no_materials)) {
 			warnings.push_back(RTR("Trails enabled, but one or more mesh materials are either missing or not set for trails rendering."));
 		}
-		if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+		if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy") {
 			warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile renderers."));
 		}
 	}
 
-	if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (sub_emitter != NodePath() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
 		warnings.push_back(RTR("Particle sub-emitters are only available when using the Forward+ or Mobile renderers."));
 	}
 

+ 2 - 2
scene/3d/light_3d.cpp

@@ -632,7 +632,7 @@ PackedStringArray OmniLight3D::get_configuration_warnings() const {
 		warnings.push_back(RTR("Projector texture only works with shadows active."));
 	}
 
-	if (get_projector().is_valid() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (get_projector().is_valid() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
 		warnings.push_back(RTR("Projector textures are not supported when using the Compatibility renderer yet. Support will be added in a future release."));
 	}
 
@@ -668,7 +668,7 @@ PackedStringArray SpotLight3D::get_configuration_warnings() const {
 		warnings.push_back(RTR("Projector texture only works with shadows active."));
 	}
 
-	if (get_projector().is_valid() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
+	if (get_projector().is_valid() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
 		warnings.push_back(RTR("Projector textures are not supported when using the Compatibility renderer yet. Support will be added in a future release."));
 	}
 

+ 7 - 0
scene/3d/node_3d.cpp

@@ -131,6 +131,13 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
 
 void Node3D::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			ERR_MAIN_THREAD_GUARD;
 			ERR_FAIL_NULL(get_tree());

+ 2 - 0
scene/3d/voxel_gi.cpp

@@ -542,6 +542,8 @@ PackedStringArray VoxelGI::get_configuration_warnings() const {
 
 	if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
 		warnings.push_back(RTR("VoxelGI nodes are not supported when using the Compatibility renderer yet. Support will be added in a future release."));
+	} else if (OS::get_singleton()->get_current_rendering_method() == "dummy") {
+		warnings.push_back(RTR("VoxelGI nodes are not supported when using the Dummy renderer."));
 	} else if (probe_data.is_null()) {
 		warnings.push_back(RTR("No VoxelGI data set, so this node is disabled. Bake static objects to enable GI."));
 	}

+ 8 - 1
scene/audio/audio_stream_player.cpp

@@ -35,7 +35,14 @@
 #include "servers/audio/audio_stream.h"
 
 void AudioStreamPlayer::_notification(int p_what) {
-	internal->notification(p_what);
+	if (p_what == NOTIFICATION_ACCESSIBILITY_UPDATE) {
+		RID ae = get_accessibility_element();
+		ERR_FAIL_COND(ae.is_null());
+
+		DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_AUDIO);
+	} else {
+		internal->notification(p_what);
+	}
 }
 
 void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) {

+ 67 - 5
scene/gui/base_button.cpp

@@ -41,6 +41,7 @@ void BaseButton::_unpress_group() {
 
 	if (toggle_mode && !button_group->is_allow_unpress()) {
 		status.pressed = true;
+		queue_accessibility_update();
 	}
 
 	for (BaseButton *E : button_group->buttons) {
@@ -83,15 +84,66 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) {
 	}
 }
 
+void BaseButton::_accessibility_action_click(const Variant &p_data) {
+	if (toggle_mode) {
+		status.pressed = !status.pressed;
+
+		if (status.pressed) {
+			_unpress_group();
+			if (button_group.is_valid()) {
+				button_group->emit_signal(SceneStringName(pressed), this);
+			}
+		}
+
+		_toggled(status.pressed);
+		_pressed();
+	} else {
+		_pressed();
+	}
+	queue_accessibility_update();
+	queue_redraw();
+}
+
 void BaseButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &BaseButton::_accessibility_action_click));
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_DISABLED, status.disabled);
+			if (toggle_mode) {
+				DisplayServer::get_singleton()->accessibility_update_set_checked(ae, status.pressed);
+			}
+			if (button_group.is_valid()) {
+				for (const BaseButton *btn : button_group->buttons) {
+					if (btn->is_part_of_edited_scene()) {
+						continue;
+					}
+					DisplayServer::get_singleton()->accessibility_update_add_related_radio_group(ae, btn->get_accessibility_element());
+				}
+			}
+			if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) {
+				String text = atr(shortcut->get_name()) + " (" + shortcut->get_as_text() + ")";
+				String tooltip = get_tooltip_text();
+				if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) {
+					text += "\n" + atr(tooltip);
+				}
+				DisplayServer::get_singleton()->accessibility_update_set_tooltip(ae, text);
+			}
+		} break;
+
 		case NOTIFICATION_MOUSE_ENTER: {
 			status.hovering = true;
+			queue_accessibility_update();
 			queue_redraw();
 		} break;
 
 		case NOTIFICATION_MOUSE_EXIT: {
 			status.hovering = false;
+			queue_accessibility_update();
 			queue_redraw();
 		} break;
 
@@ -175,6 +227,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
 				}
 				_toggled(status.pressed);
 				_pressed();
+				queue_accessibility_update();
 			}
 		} else {
 			if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
@@ -214,6 +267,7 @@ void BaseButton::set_disabled(bool p_disabled) {
 		status.press_attempt = false;
 		status.pressing_inside = false;
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 }
@@ -247,7 +301,7 @@ void BaseButton::set_pressed_no_signal(bool p_pressed) {
 		return;
 	}
 	status.pressed = p_pressed;
-
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -303,6 +357,7 @@ void BaseButton::set_toggle_mode(bool p_on) {
 	if (!p_on) {
 		set_pressed(false);
 	}
+	queue_accessibility_update();
 
 	toggle_mode = p_on;
 	update_configuration_warnings();
@@ -313,7 +368,10 @@ bool BaseButton::is_toggle_mode() const {
 }
 
 void BaseButton::set_shortcut_in_tooltip(bool p_on) {
-	shortcut_in_tooltip = p_on;
+	if (shortcut_in_tooltip != p_on) {
+		shortcut_in_tooltip = p_on;
+		queue_accessibility_update();
+	}
 }
 
 bool BaseButton::is_shortcut_in_tooltip_enabled() const {
@@ -353,8 +411,11 @@ bool BaseButton::is_shortcut_feedback() const {
 }
 
 void BaseButton::set_shortcut(const Ref<Shortcut> &p_shortcut) {
-	shortcut = p_shortcut;
-	set_process_shortcut_input(shortcut.is_valid());
+	if (shortcut != p_shortcut) {
+		shortcut = p_shortcut;
+		set_process_shortcut_input(shortcut.is_valid());
+		queue_accessibility_update();
+	}
 }
 
 Ref<Shortcut> BaseButton::get_shortcut() const {
@@ -380,7 +441,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
 
 			_toggled(status.pressed);
 			_pressed();
-
+			queue_accessibility_update();
 		} else {
 			_pressed();
 		}
@@ -440,6 +501,7 @@ void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) {
 		button_group->buttons.insert(this);
 	}
 
+	queue_accessibility_update();
 	queue_redraw(); //checkbox changes to radio if set a buttongroup
 	update_configuration_warnings();
 }

+ 1 - 0
scene/gui/base_button.h

@@ -86,6 +86,7 @@ protected:
 	void _notification(int p_what);
 
 	bool _was_pressed_by_mouse() const;
+	void _accessibility_action_click(const Variant &p_data);
 
 	GDVIRTUAL0(_pressed)
 	GDVIRTUAL1(_toggled, bool)

+ 22 - 0
scene/gui/button.cpp

@@ -30,6 +30,8 @@
 
 #include "button.h"
 
+#include "scene/gui/dialogs.h"
+
 #include "scene/theme/theme_db.h"
 
 Size2 Button::get_minimum_size() const {
@@ -185,6 +187,21 @@ Ref<StyleBox> Button::_get_current_stylebox() const {
 
 void Button::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			if (!xl_text.is_empty() && get_accessibility_name().is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, xl_text);
+			} else if (!xl_text.is_empty() && !get_accessibility_name().is_empty() && get_accessibility_name() != xl_text) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, get_accessibility_name() + ": " + xl_text);
+			}
+			AcceptDialog *dlg = Object::cast_to<AcceptDialog>(get_parent());
+			if (dlg && dlg->get_ok_button() == this) {
+				DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_DEFAULT_BUTTON);
+			}
+		} break;
+
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_redraw();
 		} break;
@@ -194,6 +211,7 @@ void Button::_notification(int p_what) {
 			_shape();
 
 			update_minimum_size();
+			queue_accessibility_update();
 			queue_redraw();
 		} break;
 
@@ -610,6 +628,7 @@ void Button::set_text(const String &p_text) {
 	xl_text = translated_text;
 	_shape();
 
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 }
@@ -649,6 +668,7 @@ void Button::set_text_direction(Control::TextDirection p_text_direction) {
 	if (text_direction != p_text_direction) {
 		text_direction = p_text_direction;
 		_shape();
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -661,6 +681,7 @@ void Button::set_language(const String &p_language) {
 	if (language != p_language) {
 		language = p_language;
 		_shape();
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -738,6 +759,7 @@ bool Button::get_clip_text() const {
 void Button::set_text_alignment(HorizontalAlignment p_alignment) {
 	if (alignment != p_alignment) {
 		alignment = p_alignment;
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }

+ 12 - 1
scene/gui/check_box.cpp

@@ -81,6 +81,17 @@ Size2 CheckBox::get_minimum_size() const {
 
 void CheckBox::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			if (is_radio()) {
+				DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_RADIO_BUTTON);
+			} else {
+				DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CHECK_BOX);
+			}
+		} break;
+
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -134,7 +145,7 @@ void CheckBox::_notification(int p_what) {
 	}
 }
 
-bool CheckBox::is_radio() {
+bool CheckBox::is_radio() const {
 	return get_button_group().is_valid();
 }
 

+ 1 - 1
scene/gui/check_box.h

@@ -60,7 +60,7 @@ protected:
 	void _notification(int p_what);
 	static void _bind_methods();
 
-	bool is_radio();
+	bool is_radio() const;
 
 public:
 	CheckBox(const String &p_text = String());

+ 7 - 0
scene/gui/check_button.cpp

@@ -85,6 +85,13 @@ Size2 CheckButton::get_minimum_size() const {
 
 void CheckButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CHECK_BUTTON);
+		} break;
+
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_TRANSLATION_CHANGED: {

+ 47 - 3
scene/gui/color_picker.cpp

@@ -56,20 +56,31 @@
 
 void ColorPicker::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_COLOR_PICKER);
+			DisplayServer::get_singleton()->accessibility_update_set_color_value(ae, color);
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			_update_color();
 		} break;
 
 		case NOTIFICATION_READY: {
-			// FIXME: The embedding check is needed to fix a bug in single-window mode (GH-93718).
 			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_COLOR_PICKER)) {
+				btn_pick->set_accessibility_name(ETR("Pick Color From Screen"));
 				btn_pick->set_tooltip_text(ETR("Pick a color from the screen."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_native));
 			} else if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE) && !get_tree()->get_root()->is_embedding_subwindows()) {
+				// FIXME: The embedding check is needed to fix a bug in single-window mode (GH-93718).
+				btn_pick->set_accessibility_name(ETR("Pick Color From Screen"));
 				btn_pick->set_tooltip_text(ETR("Pick a color from the screen."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed));
 			} else {
 				// On unsupported platforms, use a legacy method for color picking.
+				btn_pick->set_accessibility_name(ETR("Pick Color From Window"));
 				btn_pick->set_tooltip_text(ETR("Pick a color from the application window."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy));
 			}
@@ -338,8 +349,12 @@ void ColorPicker::_update_controls() {
 
 	for (int i = 0; i < current_slider_count; i++) {
 		labels[i]->set_text(modes[current_mode]->get_slider_label(i));
+		sliders[i]->set_accessibility_name(modes[current_mode]->get_slider_label(i));
+		values[i]->set_accessibility_name(modes[current_mode]->get_slider_label(i));
 	}
 	alpha_label->set_text("A");
+	alpha_slider->set_accessibility_name(ETR("Alpha"));
+	alpha_value->set_accessibility_name(ETR("Alpha"));
 
 	slider_theme_modified = modes[current_mode]->apply_theme();
 
@@ -726,6 +741,7 @@ void ColorPicker::_update_color(bool p_update_sliders) {
 	}
 	alpha_slider->queue_redraw();
 	updating = false;
+	queue_accessibility_update();
 }
 
 void ColorPicker::_update_presets() {
@@ -862,6 +878,7 @@ inline int ColorPicker::_get_preset_size() {
 void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
 	ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size));
 	btn_preset_new->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color\nRMB: Remove preset")), p_color.to_html(p_color.a < 1)));
+	btn_preset_new->set_accessibility_name(vformat(atr(ETR("Color: #%")), p_color.to_html(p_color.a < 1)));
 	SET_DRAG_FORWARDING_GCDU(btn_preset_new, ColorPicker);
 	btn_preset_new->set_button_group(preset_group);
 	preset_container->add_child(btn_preset_new);
@@ -872,6 +889,7 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
 void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
 	ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size));
 	btn_preset_new->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color")), p_color.to_html(p_color.a < 1)));
+	btn_preset_new->set_accessibility_name(vformat(atr(ETR("Color: #%s")), p_color.to_html(p_color.a < 1)));
 	btn_preset_new->set_button_group(recent_preset_group);
 	recent_preset_hbc->add_child(btn_preset_new);
 	recent_preset_hbc->move_child(btn_preset_new, 0);
@@ -2000,6 +2018,7 @@ ColorPicker::ColorPicker() {
 
 	btn_pick = memnew(Button);
 	btn_pick->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
+	btn_pick->set_accessibility_name(ETR("Pick"));
 	sample_hbc->add_child(btn_pick);
 
 	sample = memnew(TextureRect);
@@ -2012,6 +2031,7 @@ ColorPicker::ColorPicker() {
 	btn_shape->set_flat(false);
 	sample_hbc->add_child(btn_shape);
 	btn_shape->set_toggle_mode(true);
+	btn_shape->set_accessibility_name(ETR("Picker Shape"));
 	btn_shape->set_tooltip_text(ETR("Select a picker shape."));
 	btn_shape->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	btn_shape->set_focus_mode(FOCUS_ALL);
@@ -2061,6 +2081,7 @@ ColorPicker::ColorPicker() {
 	btn_mode->set_flat(false);
 	mode_hbc->add_child(btn_mode);
 	btn_mode->set_toggle_mode(true);
+	btn_mode->set_accessibility_name(ETR("Picker Mode"));
 	btn_mode->set_tooltip_text(ETR("Select a picker mode."));
 	btn_mode->set_focus_mode(FOCUS_ALL);
 
@@ -2101,7 +2122,8 @@ ColorPicker::ColorPicker() {
 	text_type = memnew(Button);
 	hex_hbc->add_child(text_type);
 	text_type->set_text("#");
-	text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values."));
+	text_type->set_accessibility_name(ETR("Hexadecimal/Code Values"));
+	text_type->set_tooltip_text(ETR("Switch between hexadecimal and code values."));
 	if (Engine::get_singleton()->is_editor_hint()) {
 		text_type->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_text_type_toggled));
 	} else {
@@ -2114,6 +2136,7 @@ ColorPicker::ColorPicker() {
 	hex_hbc->add_child(c_text);
 	c_text->set_h_size_flags(SIZE_EXPAND_FILL);
 	c_text->set_select_all_on_focus(true);
+	c_text->set_accessibility_name(ETR("Hex Code or Name"));
 	c_text->set_tooltip_text(ETR("Enter a hex code (\"#ff0000\") or named color (\"red\")."));
 	c_text->set_placeholder(ETR("Hex code or named color"));
 	c_text->connect(SceneStringName(text_submitted), callable_mp(this, &ColorPicker::_html_submitted));
@@ -2151,6 +2174,7 @@ ColorPicker::ColorPicker() {
 	menu_btn->set_flat(true);
 	menu_btn->set_focus_mode(FOCUS_ALL);
 	menu_btn->set_tooltip_text(ETR("Show all options available."));
+	menu_btn->set_accessibility_name(ETR("All Options"));
 	menu_btn->connect("about_to_popup", callable_mp(this, &ColorPicker::_update_menu_items));
 	palette_box->add_child(menu_btn);
 
@@ -2187,6 +2211,7 @@ ColorPicker::ColorPicker() {
 	btn_add_preset = memnew(Button);
 	btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	btn_add_preset->set_tooltip_text(ETR("Add current color as a preset."));
+	btn_add_preset->set_accessibility_name(ETR("Add Preset"));
 	btn_add_preset->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_add_preset_pressed));
 	preset_container->add_child(btn_add_preset);
 }
@@ -2223,6 +2248,7 @@ void ColorPickerButton::_about_to_popup() {
 
 void ColorPickerButton::_color_changed(const Color &p_color) {
 	color = p_color;
+	queue_accessibility_update();
 	queue_redraw();
 	emit_signal(SNAME("color_changed"), color);
 }
@@ -2269,6 +2295,15 @@ void ColorPickerButton::pressed() {
 
 void ColorPickerButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+			DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_DIALOG);
+			DisplayServer::get_singleton()->accessibility_update_set_color_value(ae, color);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			const Rect2 r = Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size());
 			draw_texture_rect(theme_cache.background_icon, r, true);
@@ -2302,7 +2337,7 @@ void ColorPickerButton::set_pick_color(const Color &p_color) {
 	if (picker) {
 		picker->set_pick_color(p_color);
 	}
-
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2382,6 +2417,14 @@ ColorPickerButton::ColorPickerButton(const String &p_text) :
 
 void ColorPresetButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+			DisplayServer::get_singleton()->accessibility_update_set_color_value(ae, preset_color);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			const Rect2 r = Rect2(Point2(0, 0), get_size());
 			Ref<StyleBox> sb_raw = theme_cache.foreground_style->duplicate();
@@ -2439,6 +2482,7 @@ void ColorPresetButton::_notification(int p_what) {
 
 void ColorPresetButton::set_preset_color(const Color &p_color) {
 	preset_color = p_color;
+	queue_accessibility_update();
 }
 
 Color ColorPresetButton::get_preset_color() const {

+ 8 - 0
scene/gui/color_rect.cpp

@@ -35,6 +35,7 @@ void ColorRect::set_color(const Color &p_color) {
 		return;
 	}
 	color = p_color;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -44,6 +45,13 @@ Color ColorRect::get_color() const {
 
 void ColorRect::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_color_value(ae, color);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			draw_rect(Rect2(Point2(), get_size()), color);
 		} break;

+ 7 - 0
scene/gui/container.cpp

@@ -184,6 +184,13 @@ Vector<int> Container::get_allowed_size_flags_vertical() const {
 
 void Container::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
+		} break;
+
 		case NOTIFICATION_RESIZED:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_ENTER_TREE: {

+ 187 - 7
scene/gui/control.cpp

@@ -32,8 +32,12 @@
 
 #include "container.h"
 #include "core/config/project_settings.h"
+#include "core/input/input_map.h"
+#include "core/math/geometry_2d.h"
 #include "core/os/os.h"
 #include "core/string/translation_server.h"
+#include "scene/gui/label.h"
+#include "scene/gui/panel.h"
 #include "scene/gui/scroll_container.h"
 #include "scene/main/canvas_layer.h"
 #include "scene/main/window.h"
@@ -246,6 +250,27 @@ PackedStringArray Control::get_configuration_warnings() const {
 	return warnings;
 }
 
+PackedStringArray Control::get_accessibility_configuration_warnings() const {
+	ERR_READ_THREAD_GUARD_V(PackedStringArray());
+	PackedStringArray warnings = Node::get_accessibility_configuration_warnings();
+
+	String ac_name = get_accessibility_name().strip_edges();
+	if (ac_name.is_empty()) {
+		warnings.push_back(RTR("Accessibility Name must not be empty, or contain only spaces."));
+	}
+	if (ac_name.contains(get_class_name())) {
+		warnings.push_back(RTR("Accessibility Name must not include Node class name."));
+	}
+	for (int i = 0; i < ac_name.length(); i++) {
+		if (is_control(ac_name[i])) {
+			warnings.push_back(RTR("Accessibility Name must not include control character."));
+			break;
+		}
+	}
+
+	return warnings;
+}
+
 bool Control::is_text_field() const {
 	ERR_READ_THREAD_GUARD_V(false);
 	return false;
@@ -1534,6 +1559,7 @@ void Control::set_scale(const Vector2 &p_scale) {
 	}
 	queue_redraw();
 	_notify_transform();
+	queue_accessibility_update();
 }
 
 Vector2 Control::get_scale() const {
@@ -1550,6 +1576,7 @@ void Control::set_rotation(real_t p_radians) {
 	data.rotation = p_radians;
 	queue_redraw();
 	_notify_transform();
+	queue_accessibility_update();
 }
 
 void Control::set_rotation_degrees(real_t p_degrees) {
@@ -1576,6 +1603,7 @@ void Control::set_pivot_offset(const Vector2 &p_pivot) {
 	data.pivot_offset = p_pivot;
 	queue_redraw();
 	_notify_transform();
+	queue_accessibility_update();
 }
 
 Vector2 Control::get_pivot_offset() const {
@@ -1749,6 +1777,8 @@ void Control::_size_changed() {
 		if (pos_changed && !size_changed) {
 			_update_canvas_item_transform();
 		}
+
+		queue_accessibility_update();
 	} else if (pos_changed) {
 		_notify_transform();
 	}
@@ -2018,7 +2048,40 @@ void Control::force_drag(const Variant &p_data, Control *p_control) {
 	ERR_FAIL_COND(!is_inside_tree());
 	ERR_FAIL_COND(p_data.get_type() == Variant::NIL);
 
-	get_viewport()->_gui_force_drag(this, p_data, p_control);
+	Viewport *vp = get_viewport();
+
+	vp->_gui_force_drag_start();
+	vp->_gui_force_drag(this, p_data, p_control);
+}
+
+void Control::accessibility_drag() {
+	ERR_MAIN_THREAD_GUARD;
+	ERR_FAIL_COND(!is_inside_tree());
+
+	Viewport *vp = get_viewport();
+
+	vp->_gui_force_drag_start();
+	Variant dnd_data = get_drag_data(Vector2(INFINITY, INFINITY));
+	if (dnd_data.get_type() != Variant::NIL) {
+		Window *w = Window::get_from_id(get_window()->get_window_id());
+		if (w) {
+			w->accessibility_announcement(vformat(RTR("%s grabbed. Select target and use %s to drop, use %s to cancel."), vp->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_accessibility_drag_and_drop"), InputMap::get_singleton()->get_action_description("ui_cancel")));
+		}
+		vp->_gui_force_drag(this, dnd_data, nullptr);
+		queue_accessibility_update();
+	} else {
+		vp->_gui_force_drag_cancel();
+	}
+}
+
+void Control::accessibility_drop() {
+	ERR_MAIN_THREAD_GUARD;
+	ERR_FAIL_COND(!is_inside_tree());
+	ERR_FAIL_COND(!get_viewport()->gui_is_dragging());
+
+	get_viewport()->gui_perform_drop_at(Vector2(INFINITY, INFINITY), this);
+
+	queue_accessibility_update();
 }
 
 void Control::set_drag_preview(Control *p_control) {
@@ -2037,7 +2100,7 @@ bool Control::is_drag_successful() const {
 
 void Control::set_focus_mode(FocusMode p_focus_mode) {
 	ERR_MAIN_THREAD_GUARD;
-	ERR_FAIL_INDEX((int)p_focus_mode, 3);
+	ERR_FAIL_INDEX((int)p_focus_mode, 4);
 
 	if (is_inside_tree() && p_focus_mode == FOCUS_NONE && data.focus_mode != FOCUS_NONE && has_focus()) {
 		release_focus();
@@ -2163,6 +2226,10 @@ Control *Control::find_next_valid_focus() const {
 	}
 
 	Control *from = const_cast<Control *>(this);
+	bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
+
+	// Index of the current `Control` subtree within the containing `Window`.
+	int window_next = -1;
 
 	while (true) {
 		// Find next child.
@@ -2194,6 +2261,25 @@ Control *Control::find_next_valid_focus() const {
 					}
 					next_child = next_child->data.parent_control;
 				}
+
+				Window *win = next_child == nullptr ? nullptr : next_child->data.parent_window;
+				if (win) { // Cycle through `Control` subtrees of the parent window
+					if (window_next == -1) {
+						window_next = next_child->get_index();
+						ERR_FAIL_INDEX_V(window_next, win->get_child_count(), nullptr);
+					}
+
+					for (int i = 1; i < win->get_child_count() + 1; i++) {
+						int next = Math::wrapi(window_next + i, 0, win->get_child_count());
+						Control *c = Object::cast_to<Control>(win->get_child(next));
+						if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
+							continue;
+						}
+						window_next = next;
+						next_child = c;
+						break;
+					}
+				}
 			}
 		}
 
@@ -2201,7 +2287,7 @@ Control *Control::find_next_valid_focus() const {
 			break;
 		}
 
-		if (next_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
+		if ((next_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && next_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) {
 			return next_child;
 		}
 
@@ -2244,6 +2330,10 @@ Control *Control::find_prev_valid_focus() const {
 	}
 
 	Control *from = const_cast<Control *>(this);
+	bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
+
+	// Index of the current `Control` subtree within the containing `Window`.
+	int window_prev = -1;
 
 	while (true) {
 		// Find prev child.
@@ -2253,7 +2343,28 @@ Control *Control::find_prev_valid_focus() const {
 		if (from->is_set_as_top_level() || !from->data.parent_control) {
 			// Find last of the children.
 
-			prev_child = _prev_control(from); // Wrap start here.
+			Window *win = from->data.parent_window;
+			if (win) { // Cycle through `Control` subtrees of the parent window
+				if (window_prev == -1) {
+					window_prev = from->get_index();
+					ERR_FAIL_INDEX_V(window_prev, win->get_child_count(), nullptr);
+				}
+
+				for (int i = 1; i < win->get_child_count() + 1; i++) {
+					int prev = Math::wrapi(window_prev - i, 0, win->get_child_count());
+					Control *c = Object::cast_to<Control>(win->get_child(prev));
+					if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
+						continue;
+					}
+					window_prev = prev;
+					prev_child = _prev_control(c);
+					break;
+				}
+			}
+
+			if (!prev_child) {
+				prev_child = _prev_control(from); // Wrap start here.
+			}
 
 		} else {
 			for (int i = (from->get_index() - 1); i >= 0; i--) {
@@ -2274,7 +2385,7 @@ Control *Control::find_prev_valid_focus() const {
 			}
 		}
 
-		if (prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
+		if ((prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && prev_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) {
 			return prev_child;
 		}
 
@@ -2509,11 +2620,13 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
 		return; // Bye.
 	}
 
+	bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
+
 	Control *c = Object::cast_to<Control>(p_at);
 	Container *container = Object::cast_to<Container>(p_at);
 	bool in_container = container ? container->is_ancestor_of(this) : false;
 
-	if (c && c != this && c->get_focus_mode_with_recursive() == FOCUS_ALL && !in_container && p_clamp.intersects(c->get_global_rect())) {
+	if (c && c != this && ((c->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && c->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) && !in_container && p_clamp.intersects(c->get_global_rect())) {
 		Rect2 r_c = c->get_global_rect();
 		r_c = r_c.intersection(p_clamp);
 		real_t begin_d = p_dir.dot(r_c.get_position());
@@ -3419,6 +3532,13 @@ String Control::get_tooltip(const Point2 &p_pos) const {
 	return data.tooltip;
 }
 
+String Control::accessibility_get_contextual_info() const {
+	ERR_READ_THREAD_GUARD_V(String());
+	String ret;
+	GDVIRTUAL_CALL(_accessibility_get_contextual_info, ret);
+	return ret;
+}
+
 Control *Control::make_custom_tooltip(const String &p_text) const {
 	ERR_READ_THREAD_GUARD_V(nullptr);
 	Object *ret = nullptr;
@@ -3428,6 +3548,35 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
 
 // Base object overrides.
 
+void Control::_accessibility_action_foucs(const Variant &p_data) {
+	grab_focus();
+}
+
+void Control::_accessibility_action_blur(const Variant &p_data) {
+	release_focus();
+}
+
+void Control::_accessibility_action_show_tooltip(const Variant &p_data) {
+	Viewport *vp = get_viewport();
+	if (vp) {
+		vp->show_tooltip(this);
+	}
+}
+
+void Control::_accessibility_action_hide_tooltip(const Variant &p_data) {
+	Viewport *vp = get_viewport();
+	if (vp) {
+		vp->cancel_tooltip();
+	}
+}
+
+void Control::_accessibility_action_scroll_into_view(const Variant &p_data) {
+	ScrollContainer *sc = Object::cast_to<ScrollContainer>(get_parent());
+	if (sc) {
+		sc->ensure_control_visible(this);
+	}
+}
+
 void Control::_notification(int p_notification) {
 	ERR_MAIN_THREAD_GUARD;
 	switch (p_notification) {
@@ -3439,6 +3588,31 @@ void Control::_notification(int p_notification) {
 			saving = false;
 		} break;
 #endif // TOOLS_ENABLED
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_transform(ae, get_transform());
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(ae, Rect2(Vector2(), data.size_cache));
+			DisplayServer::get_singleton()->accessibility_update_set_tooltip(ae, data.tooltip);
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_CLIPS_CHILDREN, data.clip_contents);
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_TOUCH_PASSTHROUGH, data.mouse_filter == MOUSE_FILTER_PASS);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &Control::_accessibility_action_foucs));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_BLUR, callable_mp(this, &Control::_accessibility_action_blur));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_TOOLTIP, callable_mp(this, &Control::_accessibility_action_show_tooltip));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_HIDE_TOOLTIP, callable_mp(this, &Control::_accessibility_action_hide_tooltip));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &Control::_accessibility_action_scroll_into_view));
+			if (is_inside_tree() && get_viewport()->gui_is_dragging()) {
+				if (can_drop_data(Vector2(INFINITY, INFINITY), get_viewport()->gui_get_drag_data())) {
+					DisplayServer::get_singleton()->accessibility_update_set_extra_info(ae, vformat(RTR("%s can be dropped here. Use %s to drop, use %s to cancel."), get_viewport()->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_accessibility_drag_and_drop"), InputMap::get_singleton()->get_action_description("ui_cancel")));
+				} else {
+					DisplayServer::get_singleton()->accessibility_update_set_extra_info(ae, vformat(RTR("%s can not be dropped here. Use %s to cancel."), get_viewport()->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_cancel")));
+				}
+			}
+		} break;
+
 		case NOTIFICATION_POSTINITIALIZE: {
 			data.initialized = true;
 
@@ -3773,6 +3947,9 @@ void Control::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("force_drag", "data", "preview"), &Control::force_drag);
 
+	ClassDB::bind_method(D_METHOD("accessibility_drag"), &Control::accessibility_drag);
+	ClassDB::bind_method(D_METHOD("accessibility_drop"), &Control::accessibility_drop);
+
 	ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter);
 	ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter);
 	ClassDB::bind_method(D_METHOD("get_mouse_filter_with_recursive"), &Control::get_mouse_filter_with_recursive);
@@ -3874,7 +4051,7 @@ void Control::_bind_methods() {
 	ADD_PROPERTYI(PropertyInfo(Variant::NODE_PATH, "focus_neighbor_bottom", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbor", "get_focus_neighbor", SIDE_BOTTOM);
 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next");
 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All,Accessibility"), "set_focus_mode", "get_focus_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_focus_recursive_behavior", "get_focus_recursive_behavior");
 
 	ADD_GROUP("Mouse", "mouse_");
@@ -3893,6 +4070,7 @@ void Control::_bind_methods() {
 	BIND_ENUM_CONSTANT(FOCUS_NONE);
 	BIND_ENUM_CONSTANT(FOCUS_CLICK);
 	BIND_ENUM_CONSTANT(FOCUS_ALL);
+	BIND_ENUM_CONSTANT(FOCUS_ACCESSIBILITY);
 
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED);
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED);
@@ -4003,6 +4181,8 @@ void Control::_bind_methods() {
 	GDVIRTUAL_BIND(_drop_data, "at_position", "data");
 	GDVIRTUAL_BIND(_make_custom_tooltip, "for_text");
 
+	GDVIRTUAL_BIND(_accessibility_get_contextual_info);
+
 	GDVIRTUAL_BIND(_gui_input, "event");
 }
 

+ 16 - 3
scene/gui/control.h

@@ -64,7 +64,8 @@ public:
 	enum FocusMode {
 		FOCUS_NONE,
 		FOCUS_CLICK,
-		FOCUS_ALL
+		FOCUS_ALL,
+		FOCUS_ACCESSIBILITY,
 	};
 
 	enum RecursiveBehavior {
@@ -345,8 +346,6 @@ private:
 
 	static int root_layout_direction;
 
-	String get_tooltip_text() const;
-
 protected:
 	// Dynamic properties.
 
@@ -371,6 +370,12 @@ protected:
 	void _notification(int p_notification);
 	static void _bind_methods();
 
+	void _accessibility_action_foucs(const Variant &p_data);
+	void _accessibility_action_blur(const Variant &p_data);
+	void _accessibility_action_show_tooltip(const Variant &p_data);
+	void _accessibility_action_hide_tooltip(const Variant &p_data);
+	void _accessibility_action_scroll_into_view(const Variant &p_data);
+
 	// Exposed virtual methods.
 
 	GDVIRTUAL1RC(bool, _has_point, Vector2)
@@ -383,6 +388,8 @@ protected:
 	GDVIRTUAL2(_drop_data, Vector2, Variant)
 	GDVIRTUAL1RC(Object *, _make_custom_tooltip, String)
 
+	GDVIRTUAL0RC(String, _accessibility_get_contextual_info);
+
 	GDVIRTUAL1(_gui_input, Ref<InputEvent>)
 
 public:
@@ -438,6 +445,7 @@ public:
 	static void set_root_layout_direction(int p_root_dir);
 
 	PackedStringArray get_configuration_warnings() const override;
+	PackedStringArray get_accessibility_configuration_warnings() const override;
 #ifdef TOOLS_ENABLED
 	virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
 #endif //TOOLS_ENABLED
@@ -556,6 +564,8 @@ public:
 	virtual void drop_data(const Point2 &p_point, const Variant &p_data);
 	void set_drag_preview(Control *p_control);
 	void force_drag(const Variant &p_data, Control *p_control);
+	void accessibility_drag();
+	void accessibility_drop();
 	bool is_drag_successful() const;
 
 	// Focus.
@@ -674,10 +684,13 @@ public:
 
 	// Extra properties.
 
+	String get_tooltip_text() const;
 	void set_tooltip_text(const String &text);
 	virtual String get_tooltip(const Point2 &p_pos) const;
 	virtual Control *make_custom_tooltip(const String &p_text) const;
 
+	virtual String accessibility_get_contextual_info() const;
+
 	Control();
 	~Control();
 };

+ 6 - 0
scene/gui/dialogs.cpp

@@ -51,6 +51,12 @@ void AcceptDialog::_parent_focused() {
 
 void AcceptDialog::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_DIALOG);
+		} break;
 		case NOTIFICATION_POST_ENTER_TREE: {
 			if (is_visible()) {
 				get_ok_button()->grab_focus();

+ 22 - 2
scene/gui/file_dialog.cpp

@@ -1442,9 +1442,11 @@ void FileDialog::_update_option_controls() {
 	for (const FileDialog::Option &opt : options) {
 		Label *lbl = memnew(Label);
 		lbl->set_text(opt.name);
+		lbl->set_focus_mode(Control::FOCUS_NONE);
 		grid_options->add_child(lbl);
 		if (opt.values.is_empty()) {
 			CheckBox *cb = memnew(CheckBox);
+			cb->set_accessibility_name(opt.name);
 			cb->set_pressed(opt.default_idx);
 			grid_options->add_child(cb);
 			cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
@@ -1454,6 +1456,7 @@ void FileDialog::_update_option_controls() {
 			for (const String &val : opt.values) {
 				ob->add_item(val);
 			}
+			ob->set_accessibility_name(opt.name);
 			ob->select(opt.default_idx);
 			grid_options->add_child(ob);
 			ob->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_option_changed_item_selected).bind(opt.name));
@@ -1732,11 +1735,14 @@ FileDialog::FileDialog() {
 
 	dir_prev = memnew(Button);
 	dir_prev->set_theme_type_variation(SceneStringName(FlatButton));
+	dir_prev->set_accessibility_name(ETR("Previous"));
 	dir_prev->set_tooltip_text(ETR("Go to previous folder."));
 	dir_next = memnew(Button);
+	dir_next->set_accessibility_name(ETR("Next"));
 	dir_next->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_next->set_tooltip_text(ETR("Go to next folder."));
 	dir_up = memnew(Button);
+	dir_up->set_accessibility_name(ETR("Parent Folder"));
 	dir_up->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_up->set_tooltip_text(ETR("Go to parent folder."));
 	hbc->add_child(dir_prev);
@@ -1746,22 +1752,27 @@ FileDialog::FileDialog() {
 	dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
 	dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up));
 
-	hbc->add_child(memnew(Label(ETR("Path:"))));
+	Label *lbl_path = memnew(Label(ETR("Path:")));
+	lbl_path->set_focus_mode(Control::FOCUS_NONE);
+	hbc->add_child(lbl_path);
 
 	drives_container = memnew(HBoxContainer);
 	hbc->add_child(drives_container);
 
 	drives = memnew(OptionButton);
 	drives->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_select_drive));
+	drives->set_accessibility_name(ETR("Drive"));
 	hbc->add_child(drives);
 
 	dir = memnew(LineEdit);
+	dir->set_accessibility_name(ETR("Directory Path"));
 	dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	hbc->add_child(dir);
 	dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 
 	refresh = memnew(Button);
 	refresh->set_theme_type_variation(SceneStringName(FlatButton));
+	refresh->set_accessibility_name(ETR("Refresh"));
 	refresh->set_tooltip_text(ETR("Refresh files."));
 	refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
 	hbc->add_child(refresh);
@@ -1770,6 +1781,7 @@ FileDialog::FileDialog() {
 	show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
 	show_hidden->set_toggle_mode(true);
 	show_hidden->set_pressed(is_showing_hidden_files());
+	show_hidden->set_accessibility_name(ETR("Show Hidden Files"));
 	show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
 	show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
 	hbc->add_child(show_hidden);
@@ -1778,6 +1790,7 @@ FileDialog::FileDialog() {
 	show_filename_filter_button->set_theme_type_variation(SceneStringName(FlatButton));
 	show_filename_filter_button->set_toggle_mode(true);
 	show_filename_filter_button->set_pressed(false);
+	show_filename_filter_button->set_accessibility_name(ETR("Filter File Names"));
 	show_filename_filter_button->set_tooltip_text(ETR("Toggle the visibility of the filter for file names."));
 	show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
 	hbc->add_child(show_filename_filter_button);
@@ -1787,6 +1800,7 @@ FileDialog::FileDialog() {
 
 	makedir = memnew(Button);
 	makedir->set_theme_type_variation(SceneStringName(FlatButton));
+	makedir->set_accessibility_name(ETR("Create New Folder"));
 	makedir->set_tooltip_text(ETR("Create a new folder."));
 	makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
 	hbc->add_child(makedir);
@@ -1794,6 +1808,7 @@ FileDialog::FileDialog() {
 
 	tree = memnew(Tree);
 	tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+	tree->set_accessibility_name(ETR("Directories and Files"));
 	tree->set_hide_root(true);
 	vbox->add_margin_child(ETR("Directories & Files:"), tree, true);
 
@@ -1811,13 +1826,18 @@ FileDialog::FileDialog() {
 	filename_filter->set_stretch_ratio(4);
 	filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	filename_filter->set_clear_button_enabled(true);
+	filename_filter->set_accessibility_name(ETR("Filename Filter"));
 	filename_filter_box->add_child(filename_filter);
 	filename_filter_box->set_visible(false);
 	vbox->add_child(filename_filter_box);
 
 	file_box = memnew(HBoxContainer);
-	file_box->add_child(memnew(Label(ETR("File:"))));
+	Label *lbl_file = memnew(Label(ETR("File:")));
+	lbl_file->set_focus_mode(Control::FOCUS_NONE);
+	file_box->add_child(lbl_file);
+
 	file = memnew(LineEdit);
+	file->set_accessibility_name(ETR("File Name"));
 	file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	file->set_stretch_ratio(4);
 	file->set_h_size_flags(Control::SIZE_EXPAND_FILL);

+ 242 - 4
scene/gui/graph_edit.cpp

@@ -353,6 +353,62 @@ int GraphEdit::get_connection_count(const StringName &p_node, int p_port) {
 	return count;
 }
 
+GraphNode *GraphEdit::get_input_connection_target(const StringName &p_node, int p_port) {
+	for (const Ref<Connection> &conn : connections) {
+		if (conn->to_node == p_node && conn->to_port == p_port) {
+			GraphNode *from = Object::cast_to<GraphNode>(get_node(NodePath(conn->from_node)));
+			if (from) {
+				return from;
+			}
+		}
+	}
+	return nullptr;
+}
+
+GraphNode *GraphEdit::get_output_connection_target(const StringName &p_node, int p_port) {
+	for (const Ref<Connection> &conn : connections) {
+		if (conn->from_node == p_node && conn->from_port == p_port) {
+			GraphNode *to = Object::cast_to<GraphNode>(get_node(NodePath(conn->to_node)));
+			if (to) {
+				return to;
+			}
+		}
+	}
+	return nullptr;
+}
+
+String GraphEdit::get_connections_description(const StringName &p_node, int p_port) {
+	String out;
+	for (const Ref<Connection> &conn : connections) {
+		if (conn->from_node == p_node && conn->from_port == p_port) {
+			GraphNode *to = Object::cast_to<GraphNode>(get_node(NodePath(conn->to_node)));
+			if (to) {
+				if (!out.is_empty()) {
+					out += ", ";
+				}
+				String name = to->get_accessibility_name();
+				if (name.is_empty()) {
+					name = to->get_name();
+				}
+				out += vformat(ETR("connection to %s (%s) port %d"), name, to->get_title(), conn->to_port);
+			}
+		} else if (conn->to_node == p_node && conn->to_port == p_port) {
+			GraphNode *from = Object::cast_to<GraphNode>(get_node(NodePath(conn->from_node)));
+			if (from) {
+				if (!out.is_empty()) {
+					out += ", ";
+				}
+				String name = from->get_accessibility_name();
+				if (name.is_empty()) {
+					name = from->get_name();
+				}
+				out += vformat(ETR("connection from %s (%s) port %d"), name, from->get_title(), conn->from_port);
+			}
+		}
+	}
+	return out;
+}
+
 void GraphEdit::set_scroll_offset(const Vector2 &p_offset) {
 	setting_scroll_offset = true;
 	h_scrollbar->set_value(p_offset.x);
@@ -780,6 +836,10 @@ void GraphEdit::_notification(int p_what) {
 			// Draw background fill.
 			draw_style_box(theme_cache.panel, Rect2(Point2(), get_size()));
 
+			if (has_focus()) {
+				draw_style_box(theme_cache.panel_focus, Rect2(Point2(), get_size()));
+			}
+
 			// Draw background grid.
 			if (show_grid) {
 				_draw_grid();
@@ -958,9 +1018,172 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
 	return false;
 }
 
+void GraphEdit::start_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port) {
+	if (!p_node || p_in_port == p_out_port || (p_in_port != -1 && p_out_port != -1)) {
+		return;
+	}
+	connecting_valid = false;
+	keyboard_connecting = true;
+	if (p_in_port != -1) {
+		Vector2 pos = p_node->get_input_port_position(p_in_port) * zoom + p_node->get_position();
+
+		if (right_disconnects || valid_right_disconnect_types.has(p_node->get_input_port_type(p_in_port))) {
+			// Check disconnect.
+			for (const Ref<Connection> &conn : connection_map[p_node->get_name()]) {
+				if (conn->to_node == p_node->get_name() && conn->to_port == p_in_port) {
+					Node *fr = get_node(NodePath(conn->from_node));
+					if (Object::cast_to<GraphNode>(fr)) {
+						connecting_from_node = conn->from_node;
+						connecting_from_port_index = conn->from_port;
+						connecting_from_output = true;
+						connecting_type = Object::cast_to<GraphNode>(fr)->get_output_port_type(conn->from_port);
+						connecting_color = Object::cast_to<GraphNode>(fr)->get_output_port_color(conn->from_port);
+						connecting_target_valid = false;
+						connecting_to_point = pos;
+						just_disconnected = true;
+
+						if (connecting_type >= 0) {
+							emit_signal(SNAME("disconnection_request"), conn->from_node, conn->from_port, conn->to_node, conn->to_port);
+							fr = get_node(NodePath(connecting_from_node));
+							if (Object::cast_to<GraphNode>(fr)) {
+								connecting = true;
+								emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, true);
+							}
+						}
+						return;
+					}
+				}
+			}
+		}
+
+		connecting_from_node = p_node->get_name();
+		connecting_from_port_index = p_in_port;
+		connecting_from_output = false;
+		connecting_type = p_node->get_input_port_type(p_in_port);
+		connecting_color = p_node->get_input_port_color(p_in_port);
+		connecting_target_valid = false;
+		connecting_to_point = pos;
+		if (connecting_type >= 0) {
+			connecting = true;
+			just_disconnected = false;
+			emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, false);
+		}
+		return;
+	}
+	if (p_out_port != -1) {
+		Vector2 pos = p_node->get_output_port_position(p_out_port) * zoom + p_node->get_position();
+
+		if (valid_left_disconnect_types.has(p_node->get_output_port_type(p_out_port))) {
+			// Check disconnect.
+			for (const Ref<Connection> &conn : connection_map[p_node->get_name()]) {
+				if (conn->from_node == p_node->get_name() && conn->from_port == p_out_port) {
+					Node *to = get_node(NodePath(conn->to_node));
+					if (Object::cast_to<GraphNode>(to)) {
+						connecting_from_node = conn->to_node;
+						connecting_from_port_index = conn->to_port;
+						connecting_from_output = false;
+						connecting_type = Object::cast_to<GraphNode>(to)->get_input_port_type(conn->to_port);
+						connecting_color = Object::cast_to<GraphNode>(to)->get_input_port_color(conn->to_port);
+						connecting_target_valid = false;
+						connecting_to_point = pos;
+
+						if (connecting_type >= 0) {
+							just_disconnected = true;
+
+							emit_signal(SNAME("disconnection_request"), conn->from_node, conn->from_port, conn->to_node, conn->to_port);
+							to = get_node(NodePath(connecting_from_node)); // Maybe it was erased.
+							if (Object::cast_to<GraphNode>(to)) {
+								connecting = true;
+								emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, false);
+							}
+						}
+						return;
+					}
+				}
+			}
+		}
+
+		connecting_from_node = p_node->get_name();
+		connecting_from_port_index = p_out_port;
+		connecting_from_output = true;
+		connecting_type = p_node->get_output_port_type(p_out_port);
+		connecting_color = p_node->get_output_port_color(p_out_port);
+		connecting_target_valid = false;
+		connecting_to_point = pos;
+		if (connecting_type >= 0) {
+			connecting = true;
+			just_disconnected = false;
+			emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, true);
+		}
+		return;
+	}
+}
+
+void GraphEdit::end_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port) {
+	if (!p_node) {
+		return;
+	}
+	connecting_valid = true;
+	connecting_target_valid = false;
+	if (p_in_port != -1) {
+		Vector2 pos = p_node->get_input_port_position(p_in_port) * zoom + p_node->get_position();
+
+		int type = p_node->get_input_port_type(p_in_port);
+		if (type == connecting_type || p_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(connecting_type, type))) {
+			connecting_target_valid = true;
+			connecting_to_point = pos;
+			connecting_target_node = p_node->get_name();
+			connecting_target_port_index = p_in_port;
+		}
+	}
+	if (p_out_port != -1) {
+		Vector2 pos = p_node->get_output_port_position(p_out_port) * zoom + p_node->get_position();
+
+		int type = p_node->get_output_port_type(p_out_port);
+		if (type == connecting_type || p_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(type, connecting_type))) {
+			connecting_target_valid = true;
+			connecting_to_point = pos;
+			connecting_target_node = p_node->get_name();
+			connecting_target_port_index = p_out_port;
+		}
+	}
+	if (connecting_valid) {
+		if (connecting && connecting_target_valid) {
+			if (connecting_from_output) {
+				emit_signal(SNAME("connection_request"), connecting_from_node, connecting_from_port_index, connecting_target_node, connecting_target_port_index);
+			} else {
+				emit_signal(SNAME("connection_request"), connecting_target_node, connecting_target_port_index, connecting_from_node, connecting_from_port_index);
+			}
+		} else if (!just_disconnected) {
+			if (connecting_from_output) {
+				emit_signal(SNAME("connection_to_empty"), connecting_from_node, connecting_from_port_index, Vector2());
+			} else {
+				emit_signal(SNAME("connection_from_empty"), connecting_from_node, connecting_from_port_index, Vector2());
+			}
+		}
+	}
+
+	keyboard_connecting = false;
+	if (connecting) {
+		force_connection_drag_end();
+	}
+}
+
+Dictionary GraphEdit::get_type_names() const {
+	return type_names;
+}
+
+void GraphEdit::set_type_names(const Dictionary &p_names) {
+	type_names = p_names;
+}
+
 void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
 	Ref<InputEventMouseButton> mb = p_ev;
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
+		if (keyboard_connecting) {
+			force_connection_drag_end();
+			keyboard_connecting = false;
+		}
 		connecting_valid = false;
 		click_pos = mb->get_position() / zoom;
 		for (int i = get_child_count() - 1; i >= 0; i--) {
@@ -1086,7 +1309,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
 	}
 
 	Ref<InputEventMouseMotion> mm = p_ev;
-	if (mm.is_valid() && connecting) {
+	if (mm.is_valid() && connecting && !keyboard_connecting) {
 		connecting_to_point = mm->get_position();
 		minimap->queue_redraw();
 		callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred();
@@ -2142,6 +2365,7 @@ void GraphEdit::force_connection_drag_end() {
 
 	connecting = false;
 	connecting_valid = false;
+	keyboard_connecting = false;
 	minimap->queue_redraw();
 	queue_redraw();
 	connections_layer->queue_redraw();
@@ -2793,6 +3017,9 @@ void GraphEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects);
 	ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled);
 
+	ClassDB::bind_method(D_METHOD("set_type_names", "type_names"), &GraphEdit::set_type_names);
+	ClassDB::bind_method(D_METHOD("get_type_names"), &GraphEdit::get_type_names);
+
 	GDVIRTUAL_BIND(_is_in_input_hotzone, "in_node", "in_port", "mouse_position");
 	GDVIRTUAL_BIND(_is_in_output_hotzone, "in_node", "in_port", "mouse_position");
 
@@ -2813,6 +3040,8 @@ void GraphEdit::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
 
+	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "type_names", PROPERTY_HINT_DICTIONARY_TYPE, "int;String"), "set_type_names", "get_type_names");
+
 	ADD_GROUP("Connection Lines", "connection_lines");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness", PROPERTY_HINT_RANGE, "0,100,0.1,suffix:px"), "set_connection_lines_thickness", "get_connection_lines_thickness");
@@ -2869,6 +3098,7 @@ void GraphEdit::_bind_methods() {
 	BIND_ENUM_CONSTANT(GRID_PATTERN_DOTS);
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, panel);
+	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, panel_focus);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, grid_major);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, grid_minor);
 
@@ -2977,7 +3207,8 @@ GraphEdit::GraphEdit() {
 	zoom_minus_button->set_theme_type_variation(SceneStringName(FlatButton));
 	zoom_minus_button->set_visible(show_zoom_buttons);
 	zoom_minus_button->set_tooltip_text(ETR("Zoom Out"));
-	zoom_minus_button->set_focus_mode(FOCUS_NONE);
+	zoom_minus_button->set_accessibility_name(ETR("Zoom Out"));
+	zoom_minus_button->set_focus_mode(FOCUS_ACCESSIBILITY);
 	menu_hbox->add_child(zoom_minus_button);
 	zoom_minus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_minus));
 
@@ -2985,7 +3216,8 @@ GraphEdit::GraphEdit() {
 	zoom_reset_button->set_theme_type_variation(SceneStringName(FlatButton));
 	zoom_reset_button->set_visible(show_zoom_buttons);
 	zoom_reset_button->set_tooltip_text(ETR("Zoom Reset"));
-	zoom_reset_button->set_focus_mode(FOCUS_NONE);
+	zoom_reset_button->set_accessibility_name(ETR("Zoom Reset"));
+	zoom_reset_button->set_focus_mode(FOCUS_ACCESSIBILITY);
 	menu_hbox->add_child(zoom_reset_button);
 	zoom_reset_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_reset));
 
@@ -2993,7 +3225,8 @@ GraphEdit::GraphEdit() {
 	zoom_plus_button->set_theme_type_variation(SceneStringName(FlatButton));
 	zoom_plus_button->set_visible(show_zoom_buttons);
 	zoom_plus_button->set_tooltip_text(ETR("Zoom In"));
-	zoom_plus_button->set_focus_mode(FOCUS_NONE);
+	zoom_plus_button->set_accessibility_name(ETR("Zoom In"));
+	zoom_plus_button->set_focus_mode(FOCUS_ACCESSIBILITY);
 	menu_hbox->add_child(zoom_plus_button);
 	zoom_plus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_plus));
 
@@ -3005,6 +3238,7 @@ GraphEdit::GraphEdit() {
 	toggle_grid_button->set_toggle_mode(true);
 	toggle_grid_button->set_pressed(true);
 	toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid."));
+	toggle_grid_button->set_accessibility_name(ETR("Grid"));
 	toggle_grid_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(toggle_grid_button);
 	toggle_grid_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_show_grid_toggled));
@@ -3014,6 +3248,7 @@ GraphEdit::GraphEdit() {
 	toggle_snapping_button->set_visible(show_grid_buttons);
 	toggle_snapping_button->set_toggle_mode(true);
 	toggle_snapping_button->set_tooltip_text(ETR("Toggle snapping to the grid."));
+	toggle_snapping_button->set_accessibility_name(ETR("Snap to Grid"));
 	toggle_snapping_button->set_pressed(snapping_enabled);
 	toggle_snapping_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(toggle_snapping_button);
@@ -3026,6 +3261,7 @@ GraphEdit::GraphEdit() {
 	snapping_distance_spinbox->set_step(1);
 	snapping_distance_spinbox->set_value(snapping_distance);
 	snapping_distance_spinbox->set_tooltip_text(ETR("Change the snapping distance."));
+	snapping_distance_spinbox->set_accessibility_name(ETR("Snapping Distance"));
 	menu_hbox->add_child(snapping_distance_spinbox);
 	snapping_distance_spinbox->connect(SceneStringName(value_changed), callable_mp(this, &GraphEdit::_snapping_distance_changed));
 
@@ -3036,6 +3272,7 @@ GraphEdit::GraphEdit() {
 	minimap_button->set_visible(show_minimap_button);
 	minimap_button->set_toggle_mode(true);
 	minimap_button->set_tooltip_text(ETR("Toggle the graph minimap."));
+	minimap_button->set_accessibility_name(ETR("Minimap"));
 	minimap_button->set_pressed(show_grid);
 	minimap_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(minimap_button);
@@ -3044,6 +3281,7 @@ GraphEdit::GraphEdit() {
 	arrange_button = memnew(Button);
 	arrange_button->set_theme_type_variation(SceneStringName(FlatButton));
 	arrange_button->set_visible(show_arrange_button);
+	arrange_button->set_accessibility_name(ETR("Auto Arrange"));
 	arrange_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::arrange_nodes));
 	arrange_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(arrange_button);

+ 15 - 0
scene/gui/graph_edit.h

@@ -30,6 +30,7 @@
 
 #pragma once
 
+#include "core/variant/typed_dictionary.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/graph_frame.h"
 #include "scene/gui/graph_node.h"
@@ -198,6 +199,7 @@ private:
 	bool show_grid = true;
 	GridPattern grid_pattern = GRID_PATTERN_LINES;
 
+	bool keyboard_connecting = false;
 	bool connecting = false;
 	StringName connecting_from_node;
 	bool connecting_from_output = false;
@@ -269,6 +271,7 @@ private:
 		float base_scale = 1.0;
 
 		Ref<StyleBox> panel;
+		Ref<StyleBox> panel_focus;
 		Color grid_major;
 		Color grid_minor;
 
@@ -303,6 +306,8 @@ private:
 	HashMap<StringName, HashSet<StringName>> frame_attached_nodes;
 	HashMap<StringName, StringName> linked_parent_map;
 
+	Dictionary type_names;
+
 	void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
 	void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
 
@@ -404,6 +409,9 @@ public:
 	Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, bool keep_alive = false);
 	bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
 	int get_connection_count(const StringName &p_node, int p_port);
+	GraphNode *get_input_connection_target(const StringName &p_node, int p_port);
+	GraphNode *get_output_connection_target(const StringName &p_node, int p_port);
+	String get_connections_description(const StringName &p_node, int p_port);
 	void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
 
 	void force_connection_drag_end();
@@ -413,6 +421,13 @@ public:
 	Ref<Connection> get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const;
 	List<Ref<Connection>> get_connections_intersecting_with_rect(const Rect2 &p_rect) const;
 
+	bool is_keyboard_connecting() const { return keyboard_connecting; }
+	void start_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port);
+	void end_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port);
+
+	Dictionary get_type_names() const;
+	void set_type_names(const Dictionary &p_names);
+
 	virtual bool is_node_hover_valid(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
 
 	void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity);

+ 358 - 0
scene/gui/graph_node.cpp

@@ -31,6 +31,7 @@
 #include "graph_node.h"
 
 #include "scene/gui/box_container.h"
+#include "scene/gui/graph_edit.h"
 #include "scene/gui/label.h"
 #include "scene/theme/theme_db.h"
 
@@ -196,6 +197,10 @@ void GraphNode::_resort() {
 
 		children_count++;
 	}
+	slot_count = children_count;
+	if (selected_slot >= slot_count) {
+		selected_slot = -1;
+	}
 
 	if (children_count == 0) {
 		return;
@@ -285,6 +290,7 @@ void GraphNode::_resort() {
 		valid_children_idx++;
 	}
 
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 }
@@ -306,8 +312,308 @@ void GraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Co
 	port_icon->draw(get_canvas_item(), p_pos + icon_offset, p_color);
 }
 
+void GraphNode::_accessibility_action_slot(const Variant &p_data) {
+	CustomAccessibilityAction action = (CustomAccessibilityAction)p_data.operator int();
+	switch (action) {
+		case ACTION_CONNECT_INPUT: {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_left) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < left_port_cache.size(); i++) {
+							if (left_port_cache[i].slot_index == selected_slot) {
+								if (graph->is_keyboard_connecting()) {
+									graph->end_keyboard_connecting(this, i, -1);
+								} else {
+									graph->start_keyboard_connecting(this, i, -1);
+								}
+								queue_accessibility_update();
+								queue_redraw();
+								break;
+							}
+						}
+					}
+				}
+			}
+		} break;
+
+		case ACTION_CONNECT_OUTPUT: {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_right) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < right_port_cache.size(); i++) {
+							if (right_port_cache[i].slot_index == selected_slot) {
+								if (graph->is_keyboard_connecting()) {
+									graph->end_keyboard_connecting(this, -1, i);
+								} else {
+									graph->start_keyboard_connecting(this, -1, i);
+								}
+								queue_accessibility_update();
+								queue_redraw();
+								break;
+							}
+						}
+					}
+				}
+			}
+		} break;
+
+		case ACTION_FOLLOW_INPUT: {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_left) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < left_port_cache.size(); i++) {
+							if (left_port_cache[i].slot_index == selected_slot) {
+								GraphNode *target = graph->get_input_connection_target(get_name(), i);
+								if (target) {
+									target->grab_focus();
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		} break;
+
+		case ACTION_FOLLOW_OUTPUT: {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_right) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < right_port_cache.size(); i++) {
+							if (right_port_cache[i].slot_index == selected_slot) {
+								GraphNode *target = graph->get_output_connection_target(get_name(), i);
+								if (target) {
+									target->grab_focus();
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		} break;
+	}
+}
+
+void GraphNode::gui_input(const Ref<InputEvent> &p_event) {
+	if (port_pos_dirty) {
+		_port_pos_update();
+	}
+
+	if (p_event->is_pressed() && slot_count > 0) {
+		if (p_event->is_action("ui_up", true)) {
+			selected_slot--;
+			if (selected_slot < 0) {
+				selected_slot = -1;
+			} else {
+				accept_event();
+			}
+		} else if (p_event->is_action("ui_down", true)) {
+			selected_slot++;
+			if (selected_slot >= slot_count) {
+				selected_slot = -1;
+			} else {
+				accept_event();
+			}
+		} else if (p_event->is_action("ui_cancel", true)) {
+			GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+			if (graph && graph->is_keyboard_connecting()) {
+				graph->force_connection_drag_end();
+				accept_event();
+			}
+		} else if (p_event->is_action("ui_graph_delete", true)) {
+			GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+			if (graph && graph->is_keyboard_connecting()) {
+				graph->end_keyboard_connecting(this, -1, -1);
+				accept_event();
+			}
+		} else if (p_event->is_action("ui_graph_follow_left", true)) {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_left) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < left_port_cache.size(); i++) {
+							if (left_port_cache[i].slot_index == selected_slot) {
+								GraphNode *target = graph->get_input_connection_target(get_name(), i);
+								if (target) {
+									target->grab_focus();
+									accept_event();
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		} else if (p_event->is_action("ui_graph_follow_right", true)) {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_right) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < right_port_cache.size(); i++) {
+							if (right_port_cache[i].slot_index == selected_slot) {
+								GraphNode *target = graph->get_output_connection_target(get_name(), i);
+								if (target) {
+									target->grab_focus();
+									accept_event();
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		} else if (p_event->is_action("ui_left", true)) {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_left) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < left_port_cache.size(); i++) {
+							if (left_port_cache[i].slot_index == selected_slot) {
+								if (graph->is_keyboard_connecting()) {
+									graph->end_keyboard_connecting(this, i, -1);
+								} else {
+									graph->start_keyboard_connecting(this, i, -1);
+								}
+								accept_event();
+								break;
+							}
+						}
+					}
+				}
+			}
+		} else if (p_event->is_action("ui_right", true)) {
+			if (slot_table.has(selected_slot)) {
+				const Slot &slot = slot_table[selected_slot];
+				if (slot.enable_right) {
+					GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+					if (graph) {
+						for (int i = 0; i < right_port_cache.size(); i++) {
+							if (right_port_cache[i].slot_index == selected_slot) {
+								if (graph->is_keyboard_connecting()) {
+									graph->end_keyboard_connecting(this, -1, i);
+								} else {
+									graph->start_keyboard_connecting(this, -1, i);
+								}
+								accept_event();
+								break;
+							}
+						}
+					}
+				}
+			}
+		} else if (p_event->is_action("ui_accept", true)) {
+			if (slot_table.has(selected_slot)) {
+				int idx = 0;
+				for (int i = 0; i < get_child_count(false); i++) {
+					Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
+					if (!child) {
+						continue;
+					}
+					if (idx == selected_slot) {
+						selected_slot = -1;
+						child->grab_focus();
+						break;
+					}
+					idx++;
+				}
+				accept_event();
+			}
+		}
+		queue_accessibility_update();
+		queue_redraw();
+	}
+}
+
 void GraphNode::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			String name = get_accessibility_name();
+			if (name.is_empty()) {
+				name = get_name();
+			}
+			name = vformat(ETR("graph node %s (%s)"), name, get_title());
+
+			if (slot_table.has(selected_slot)) {
+				GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
+				Dictionary type_info;
+				if (graph) {
+					type_info = graph->get_type_names();
+				}
+				const Slot &slot = slot_table[selected_slot];
+				name += ", " + vformat(ETR("slot %d of %d"), selected_slot + 1, slot_count);
+				if (slot.enable_left) {
+					if (type_info.has(slot.type_left)) {
+						name += "," + vformat(ETR("input port, type: %s"), type_info[slot.type_left]);
+					} else {
+						name += "," + vformat(ETR("input port, type: %d"), slot.type_left);
+					}
+					if (graph) {
+						for (int i = 0; i < left_port_cache.size(); i++) {
+							if (left_port_cache[i].slot_index == selected_slot) {
+								String cd = graph->get_connections_description(get_name(), i);
+								if (cd.is_empty()) {
+									name += " " + ETR("no connections");
+								} else {
+									name += " " + cd;
+								}
+								break;
+							}
+						}
+					}
+				}
+				if (slot.enable_left) {
+					if (type_info.has(slot.type_right)) {
+						name += "," + vformat(ETR("output port, type: %s"), type_info[slot.type_right]);
+					} else {
+						name += "," + vformat(ETR("output port, type: %d"), slot.type_right);
+					}
+					if (graph) {
+						for (int i = 0; i < right_port_cache.size(); i++) {
+							if (right_port_cache[i].slot_index == selected_slot) {
+								String cd = graph->get_connections_description(get_name(), i);
+								if (cd.is_empty()) {
+									name += " " + ETR("no connections");
+								} else {
+									name += " " + cd;
+								}
+								break;
+							}
+						}
+					}
+				}
+				if (graph && graph->is_keyboard_connecting()) {
+					name += ", " + ETR("currently selecting target port");
+				}
+			} else {
+				name += ", " + vformat(ETR("has %d slots"), slot_count);
+			}
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LIST);
+			DisplayServer::get_singleton()->accessibility_update_set_name(ae, name);
+			DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_INPUT, ETR("Edit Input Port Connection"));
+			DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_OUTPUT, ETR("Edit Output Port Connection"));
+			DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_FOLLOW_INPUT, ETR("Follow Input Port Connection"));
+			DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_FOLLOW_OUTPUT, ETR("Follow Output Port Connection"));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CUSTOM, callable_mp(this, &GraphNode::_accessibility_action_slot));
+		} break;
+		case NOTIFICATION_FOCUS_EXIT: {
+			selected_slot = -1;
+			queue_redraw();
+		} break;
 		case NOTIFICATION_DRAW: {
 			// Used for layout calculations.
 			Ref<StyleBox> sb_panel = theme_cache.panel;
@@ -317,6 +623,7 @@ void GraphNode::_notification(int p_what) {
 			Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : theme_cache.titlebar;
 
 			Ref<StyleBox> sb_slot = theme_cache.slot;
+			Ref<StyleBox> sb_slot_selected = theme_cache.slot_selected;
 
 			int port_h_offset = theme_cache.port_h_offset;
 
@@ -329,6 +636,10 @@ void GraphNode::_notification(int p_what) {
 			// Draw body (slots area) stylebox.
 			draw_style_box(sb_to_draw_panel, body_rect);
 
+			if (has_focus()) {
+				draw_style_box(theme_cache.panel_focus, body_rect);
+			}
+
 			// Draw title bar stylebox above.
 			draw_style_box(sb_to_draw_titlebar, titlebar_rect);
 
@@ -356,6 +667,12 @@ void GraphNode::_notification(int p_what) {
 						draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key]), false, slot.color_right);
 					}
 
+					if (slot_index == selected_slot) {
+						Size2i port_sz = theme_cache.port->get_size();
+						draw_style_box(sb_slot_selected, Rect2i(port_h_offset - port_sz.x, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP) - port_sz.y, port_sz.x * 2, port_sz.y * 2));
+						draw_style_box(sb_slot_selected, Rect2i(get_size().x - port_h_offset - port_sz.x, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP) - port_sz.y, port_sz.x * 2, port_sz.y * 2));
+					}
+
 					// Draw slot stylebox.
 					if (slot.draw_stylebox) {
 						Control *child = Object::cast_to<Control>(get_child(E.key, false));
@@ -400,6 +717,8 @@ void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left,
 	slot.custom_port_icon_right = p_custom_right;
 	slot.draw_stylebox = p_draw_stylebox;
 	slot_table[p_slot_index] = slot;
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 
@@ -408,12 +727,16 @@ void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left,
 
 void GraphNode::clear_slot(int p_slot_index) {
 	slot_table.erase(p_slot_index);
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 }
 
 void GraphNode::clear_all_slots() {
 	slot_table.clear();
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 }
@@ -433,6 +756,8 @@ void GraphNode::set_slot_enabled_left(int p_slot_index, bool p_enable) {
 	}
 
 	slot_table[p_slot_index].enable_left = p_enable;
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 
@@ -447,6 +772,8 @@ void GraphNode::set_slot_type_left(int p_slot_index, int p_type) {
 	}
 
 	slot_table[p_slot_index].type_left = p_type;
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 
@@ -517,6 +844,8 @@ void GraphNode::set_slot_enabled_right(int p_slot_index, bool p_enable) {
 	}
 
 	slot_table[p_slot_index].enable_right = p_enable;
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 
@@ -531,6 +860,8 @@ void GraphNode::set_slot_type_right(int p_slot_index, int p_type) {
 	}
 
 	slot_table[p_slot_index].type_right = p_type;
+
+	queue_accessibility_update();
 	queue_redraw();
 	port_pos_dirty = true;
 
@@ -681,6 +1012,10 @@ void GraphNode::_port_pos_update() {
 
 		slot_index++;
 	}
+	slot_count = slot_index;
+	if (selected_slot >= slot_count) {
+		selected_slot = -1;
+	}
 
 	port_pos_dirty = false;
 }
@@ -775,6 +1110,25 @@ int GraphNode::get_output_port_slot(int p_port_idx) {
 	return right_port_cache[p_port_idx].slot_index;
 }
 
+String GraphNode::get_accessibility_container_name(const Node *p_node) const {
+	int idx = 0;
+	for (int i = 0; i < get_child_count(false); i++) {
+		Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
+		if (!child) {
+			continue;
+		}
+		if (child == p_node) {
+			String name = get_accessibility_name();
+			if (name.is_empty()) {
+				name = get_name();
+			}
+			return vformat(ETR(", in slot %d of graph node %s (%s)"), idx + 1, name, get_title());
+		}
+		idx++;
+	}
+	return String();
+}
+
 void GraphNode::set_title(const String &p_title) {
 	if (title == p_title) {
 		return;
@@ -884,9 +1238,11 @@ void GraphNode::_bind_methods() {
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_selected);
+	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_focus);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar_selected);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot);
+	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot_selected);
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, separation);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset);
@@ -904,7 +1260,9 @@ GraphNode::GraphNode() {
 	title_label = memnew(Label);
 	title_label->set_theme_type_variation("GraphNodeTitleLabel");
 	title_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	title_label->set_focus_mode(Control::FOCUS_NONE);
 	titlebar_hbox->add_child(title_label);
 
 	set_mouse_filter(MOUSE_FILTER_STOP);
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 }

+ 16 - 0
scene/gui/graph_node.h

@@ -66,6 +66,14 @@ class GraphNode : public GraphElement {
 		int final_size = 0;
 	};
 
+	enum CustomAccessibilityAction {
+		ACTION_CONNECT_INPUT,
+		ACTION_CONNECT_OUTPUT,
+		ACTION_FOLLOW_INPUT,
+		ACTION_FOLLOW_OUTPUT,
+	};
+	void _accessibility_action_slot(const Variant &p_data);
+
 	HBoxContainer *titlebar_hbox = nullptr;
 	Label *title_label = nullptr;
 
@@ -77,12 +85,17 @@ class GraphNode : public GraphElement {
 	HashMap<int, Slot> slot_table;
 	Vector<int> slot_y_cache;
 
+	int slot_count = 0;
+	int selected_slot = -1;
+
 	struct ThemeCache {
 		Ref<StyleBox> panel;
 		Ref<StyleBox> panel_selected;
+		Ref<StyleBox> panel_focus;
 		Ref<StyleBox> titlebar;
 		Ref<StyleBox> titlebar_selected;
 		Ref<StyleBox> slot;
+		Ref<StyleBox> slot_selected;
 
 		int separation = 0;
 		int port_h_offset = 0;
@@ -112,6 +125,9 @@ protected:
 	void _get_property_list(List<PropertyInfo> *p_list) const;
 
 public:
+	virtual String get_accessibility_container_name(const Node *p_node) const override;
+	virtual void gui_input(const Ref<InputEvent> &p_event) override;
+
 	void set_title(const String &p_title);
 	String get_title() const;
 

+ 205 - 3
scene/gui/item_list.cpp

@@ -64,6 +64,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo
 	items.write[item_id].xl_text = _atr(item_id, p_item);
 	_shape_text(item_id);
 
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	notify_property_list_changed();
@@ -77,6 +78,7 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) {
 	items.push_back(item);
 	int item_id = items.size() - 1;
 
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	notify_property_list_changed();
@@ -96,6 +98,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) {
 	items.write[p_idx].text = p_text;
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	_shape_text(p_idx);
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 }
@@ -114,6 +117,7 @@ void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_
 	if (items[p_idx].text_direction != p_text_direction) {
 		items.write[p_idx].text_direction = p_text_direction;
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -131,6 +135,7 @@ void ItemList::set_item_language(int p_idx, const String &p_language) {
 	if (items[p_idx].language != p_language) {
 		items.write[p_idx].language = p_language;
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -149,6 +154,7 @@ void ItemList::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode)
 		items.write[p_idx].auto_translate_mode = p_mode;
 		items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -163,7 +169,11 @@ void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
 		p_idx += get_item_count();
 	}
 	ERR_FAIL_INDEX(p_idx, items.size());
-	items.write[p_idx].tooltip_enabled = p_enabled;
+	if (items[p_idx].tooltip_enabled != p_enabled) {
+		items.write[p_idx].tooltip_enabled = p_enabled;
+		items.write[p_idx].accessibility_item_dirty = true;
+		queue_accessibility_update();
+	}
 }
 
 bool ItemList::is_item_tooltip_enabled(int p_idx) const {
@@ -182,6 +192,7 @@ void ItemList::set_item_tooltip(int p_idx, const String &p_tooltip) {
 	}
 
 	items.write[p_idx].tooltip = p_tooltip;
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 }
@@ -348,6 +359,8 @@ void ItemList::set_item_selectable(int p_idx, bool p_selectable) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 
 	items.write[p_idx].selectable = p_selectable;
+	items.write[p_idx].accessibility_item_dirty = true;
+	queue_accessibility_update();
 }
 
 bool ItemList::is_item_selectable(int p_idx) const {
@@ -366,6 +379,8 @@ void ItemList::set_item_disabled(int p_idx, bool p_disabled) {
 	}
 
 	items.write[p_idx].disabled = p_disabled;
+	items.write[p_idx].accessibility_item_dirty = true;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -403,7 +418,10 @@ void ItemList::select(int p_idx, bool p_single) {
 		}
 
 		for (int i = 0; i < items.size(); i++) {
-			items.write[i].selected = p_idx == i;
+			if (items.write[i].selected != (p_idx == i)) {
+				items.write[i].selected = (p_idx == i);
+				items.write[i].accessibility_item_dirty = true;
+			}
 		}
 
 		current = p_idx;
@@ -411,8 +429,10 @@ void ItemList::select(int p_idx, bool p_single) {
 	} else {
 		if (items[p_idx].selectable && !items[p_idx].disabled) {
 			items.write[p_idx].selected = true;
+			items.write[p_idx].accessibility_item_dirty = true;
 		}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -425,6 +445,8 @@ void ItemList::deselect(int p_idx) {
 	} else {
 		items.write[p_idx].selected = false;
 	}
+	items.write[p_idx].accessibility_item_dirty = true;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -434,9 +456,13 @@ void ItemList::deselect_all() {
 	}
 
 	for (int i = 0; i < items.size(); i++) {
-		items.write[i].selected = false;
+		if (items.write[i].selected) {
+			items.write[i].selected = false;
+			items.write[i].accessibility_item_dirty = true;
+		}
 	}
 	current = -1;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -457,6 +483,7 @@ void ItemList::set_current(int p_current) {
 		select(p_current, true);
 	} else {
 		current = p_current;
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -477,6 +504,7 @@ void ItemList::move_item(int p_from_idx, int p_to_idx) {
 	items.remove_at(p_from_idx);
 	items.insert(p_to_idx, item);
 
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	notify_property_list_changed();
@@ -489,7 +517,17 @@ void ItemList::set_item_count(int p_count) {
 		return;
 	}
 
+	if (items.size() > p_count) {
+		for (int i = p_count; i < items.size(); i++) {
+			if (items[i].accessibility_item_element.is_valid()) {
+				DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
+				items.write[i].accessibility_item_element = RID();
+			}
+		}
+	}
+
 	items.resize(p_count);
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	notify_property_list_changed();
@@ -502,10 +540,15 @@ int ItemList::get_item_count() const {
 void ItemList::remove_item(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 
+	if (items[p_idx].accessibility_item_element.is_valid()) {
+		DisplayServer::get_singleton()->accessibility_free_element(items.write[p_idx].accessibility_item_element);
+		items.write[p_idx].accessibility_item_element = RID();
+	}
 	items.remove_at(p_idx);
 	if (current == p_idx) {
 		current = -1;
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	defer_select_single = -1;
@@ -513,9 +556,16 @@ void ItemList::remove_item(int p_idx) {
 }
 
 void ItemList::clear() {
+	for (int i = 0; i < items.size(); i++) {
+		if (items[i].accessibility_item_element.is_valid()) {
+			DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
+			items.write[i].accessibility_item_element = RID();
+		}
+	}
 	items.clear();
 	current = -1;
 	ensure_selected_visible = false;
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 	defer_select_single = -1;
@@ -565,6 +615,7 @@ void ItemList::set_max_text_lines(int p_lines) {
 			}
 		}
 		shape_changed = true;
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -581,6 +632,7 @@ void ItemList::set_max_columns(int p_amount) {
 	}
 
 	max_columns = p_amount;
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 }
@@ -595,6 +647,7 @@ void ItemList::set_select_mode(SelectMode p_mode) {
 	}
 
 	select_mode = p_mode;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -694,7 +747,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
 	if (mm.is_valid()) {
 		int closest = get_item_at_position(mm->get_position(), true);
 		if (closest != hovered) {
+			prev_hovered = hovered;
 			hovered = closest;
+			queue_accessibility_update();
 			queue_redraw();
 		}
 	}
@@ -834,6 +889,21 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
 	}
 
 	if (p_event->is_pressed() && items.size() > 0) {
+		if (p_event->is_action("ui_menu", true)) {
+			if (current != -1 && allow_rmb_select) {
+				int i = current;
+
+				if (items[i].disabled) {
+					// Don't emit any signal or do any action with clicked item when disabled.
+					return;
+				}
+
+				emit_signal(SNAME("item_clicked"), i, get_item_rect(i).position, MouseButton::RIGHT);
+
+				accept_event();
+				return;
+			}
+		}
 		if (p_event->is_action("ui_up", true)) {
 			if (!search_string.is_empty()) {
 				uint64_t now = OS::get_singleton()->get_ticks_msec();
@@ -1076,8 +1146,131 @@ static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) {
 	return Rect2(ofs_x, ofs_y, tex_width, tex_height);
 }
 
+RID ItemList::get_focused_accessibility_element() const {
+	if (current == -1) {
+		return get_accessibility_element();
+	} else {
+		const Item &item = items[current];
+		return item.accessibility_item_element;
+	}
+}
+
+void ItemList::_accessibility_action_scroll_set(const Variant &p_data) {
+	const Point2 &pos = p_data;
+	scroll_bar_h->set_value(pos.x);
+	scroll_bar_v->set_value(pos.y);
+}
+
+void ItemList::_accessibility_action_scroll_up(const Variant &p_data) {
+	scroll_bar_v->set_value(scroll_bar_v->get_value() - scroll_bar_v->get_page() / 4);
+}
+
+void ItemList::_accessibility_action_scroll_down(const Variant &p_data) {
+	scroll_bar_v->set_value(scroll_bar_v->get_value() + scroll_bar_v->get_page() / 4);
+}
+
+void ItemList::_accessibility_action_scroll_left(const Variant &p_data) {
+	scroll_bar_h->set_value(scroll_bar_h->get_value() - scroll_bar_h->get_page() / 4);
+}
+
+void ItemList::_accessibility_action_scroll_right(const Variant &p_data) {
+	scroll_bar_h->set_value(scroll_bar_h->get_value() + scroll_bar_h->get_page() / 4);
+}
+
+void ItemList::_accessibility_action_scroll_into_view(const Variant &p_data, int p_index) {
+	ERR_FAIL_INDEX(p_index, items.size());
+
+	Rect2 r = items[p_index].rect_cache;
+	int from_v = scroll_bar_v->get_value();
+	int to_v = from_v + scroll_bar_v->get_page();
+	int from_h = scroll_bar_h->get_value();
+	int to_h = from_h + scroll_bar_h->get_page();
+
+	if (r.position.y < from_v) {
+		scroll_bar_v->set_value(r.position.y);
+	} else if (r.position.y + r.size.y > to_v) {
+		scroll_bar_v->set_value(r.position.y + r.size.y - (to_v - from_v));
+	}
+	if (r.position.x < from_h) {
+		scroll_bar_h->set_value(r.position.x);
+	} else if (r.position.x + r.size.x > to_h) {
+		scroll_bar_h->set_value(r.position.x + r.size.x - (to_h - from_h));
+	}
+}
+
+void ItemList::_accessibility_action_focus(const Variant &p_data, int p_index) {
+	select(p_index);
+}
+
+void ItemList::_accessibility_action_blur(const Variant &p_data, int p_index) {
+	deselect(p_index);
+}
+
 void ItemList::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_EXIT_TREE:
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			for (int i = 0; i < items.size(); i++) {
+				items.write[i].accessibility_item_element = RID();
+			}
+			accessibility_scroll_element = RID();
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			force_update_list_size();
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LIST_BOX);
+			DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, items.size());
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_MULTISELECTABLE, select_mode == SELECT_MULTI);
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &ItemList::_accessibility_action_scroll_down));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_UP, callable_mp(this, &ItemList::_accessibility_action_scroll_up));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &ItemList::_accessibility_action_scroll_left));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &ItemList::_accessibility_action_scroll_right));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ItemList::_accessibility_action_scroll_set));
+
+			if (accessibility_scroll_element.is_null()) {
+				accessibility_scroll_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
+			}
+
+			Transform2D scroll_xform;
+			scroll_xform.set_origin(Vector2i(-scroll_bar_h->get_value(), -scroll_bar_v->get_value()));
+			DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_scroll_element, scroll_xform);
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(accessibility_scroll_element, Rect2(0, 0, scroll_bar_h->get_max(), scroll_bar_v->get_max()));
+
+			for (int i = 0; i < items.size(); i++) {
+				const Item &item = items.write[i];
+
+				if (item.accessibility_item_element.is_null()) {
+					item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(accessibility_scroll_element, DisplayServer::AccessibilityRole::ROLE_LIST_BOX_OPTION);
+					item.accessibility_item_dirty = true;
+				}
+				if (item.accessibility_item_dirty || i == hovered || i == prev_hovered) {
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &ItemList::_accessibility_action_scroll_into_view).bind(i));
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &ItemList::_accessibility_action_focus).bind(i));
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_BLUR, callable_mp(this, &ItemList::_accessibility_action_blur).bind(i));
+
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_level(item.accessibility_item_element, 0);
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, item.selected);
+					DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, item.xl_text);
+					DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
+					if (item.tooltip_enabled) {
+						DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
+					}
+
+					Rect2 r = get_item_rect(i);
+					DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(r.position, r.size));
+
+					item.accessibility_item_dirty = false;
+				}
+			}
+			prev_hovered = -1;
+
+		} break;
+
 		case NOTIFICATION_RESIZED: {
 			shape_changed = true;
 			queue_redraw();
@@ -1089,6 +1282,7 @@ void ItemList::_notification(int p_what) {
 				_shape_text(i);
 			}
 			shape_changed = true;
+			queue_accessibility_update();
 			queue_redraw();
 		} break;
 		case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -1097,6 +1291,7 @@ void ItemList::_notification(int p_what) {
 				_shape_text(i);
 			}
 			shape_changed = true;
+			queue_accessibility_update();
 			queue_redraw();
 		} break;
 
@@ -1537,6 +1732,8 @@ void ItemList::force_update_list_size() {
 
 		items.write[i].rect_cache.size = minsize;
 		items.write[i].min_rect_cache.size = minsize;
+
+		items.write[i].accessibility_item_dirty = true;
 	}
 
 	int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width;
@@ -1661,7 +1858,9 @@ void ItemList::_scroll_changed(double) {
 
 void ItemList::_mouse_exited() {
 	if (hovered > -1) {
+		prev_hovered = hovered;
 		hovered = -1;
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -1763,6 +1962,7 @@ String ItemList::get_tooltip(const Point2 &p_pos) const {
 
 void ItemList::sort_items_by_text() {
 	items.sort();
+	queue_accessibility_update();
 	queue_redraw();
 	shape_changed = true;
 
@@ -1872,6 +2072,7 @@ void ItemList::set_auto_width(bool p_enable) {
 
 	auto_width = p_enable;
 	shape_changed = true;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -1886,6 +2087,7 @@ void ItemList::set_auto_height(bool p_enable) {
 
 	auto_height = p_enable;
 	shape_changed = true;
+	queue_accessibility_update();
 	queue_redraw();
 }
 

+ 16 - 0
scene/gui/item_list.h

@@ -52,6 +52,9 @@ public:
 
 private:
 	struct Item {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		Ref<Texture2D> icon;
 		bool icon_transposed = false;
 		Rect2i icon_region;
@@ -87,12 +90,14 @@ private:
 
 		Item(bool p_dummy) {}
 	};
+	RID accessibility_scroll_element;
 
 	static inline PropertyListHelper base_property_helper;
 	PropertyListHelper property_helper;
 
 	int current = -1;
 	int hovered = -1;
+	int prev_hovered = -1;
 
 	bool shape_changed = true;
 
@@ -180,7 +185,18 @@ protected:
 	bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
 	static void _bind_methods();
 
+	void _accessibility_action_scroll_set(const Variant &p_data);
+	void _accessibility_action_scroll_up(const Variant &p_data);
+	void _accessibility_action_scroll_down(const Variant &p_data);
+	void _accessibility_action_scroll_left(const Variant &p_data);
+	void _accessibility_action_scroll_right(const Variant &p_data);
+	void _accessibility_action_scroll_into_view(const Variant &p_data, int p_index);
+	void _accessibility_action_focus(const Variant &p_data, int p_index);
+	void _accessibility_action_blur(const Variant &p_data, int p_index);
+
 public:
+	virtual RID get_focused_accessibility_element() const override;
+
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
 	int add_item(const String &p_item, const Ref<Texture2D> &p_texture = Ref<Texture2D>(), bool p_selectable = true);

+ 24 - 2
scene/gui/label.cpp

@@ -100,6 +100,7 @@ void Label::set_uppercase(bool p_uppercase) {
 	uppercase = p_uppercase;
 	text_dirty = true;
 
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -692,6 +693,15 @@ PackedStringArray Label::get_configuration_warnings() const {
 
 void Label::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
+			DisplayServer::get_singleton()->accessibility_update_set_value(ae, xl_text);
+			DisplayServer::get_singleton()->accessibility_update_set_text_align(ae, horizontal_alignment);
+		} break;
+
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 			String new_text = atr(text);
 			if (new_text == xl_text) {
@@ -703,6 +713,7 @@ void Label::_notification(int p_what) {
 			}
 			text_dirty = true;
 
+			queue_accessibility_update();
 			queue_redraw();
 			update_configuration_warnings();
 		} break;
@@ -749,7 +760,11 @@ void Label::_notification(int p_what) {
 			int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
 			bool rtl_layout = is_layout_rtl();
 
-			style->draw(ci, Rect2(Point2(0, 0), get_size()));
+			if (has_focus()) {
+				theme_cache.focus_style->draw(ci, Rect2(Point2(0, 0), get_size()));
+			} else {
+				theme_cache.normal_style->draw(ci, Rect2(Point2(0, 0), get_size()));
+			}
 
 			bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING);
 			bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !rtl_layout));
@@ -1070,7 +1085,7 @@ void Label::set_horizontal_alignment(HorizontalAlignment p_alignment) {
 		}
 	}
 	horizontal_alignment = p_alignment;
-
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -1103,6 +1118,7 @@ void Label::set_text(const String &p_string) {
 	if (visible_ratio < 1) {
 		visible_chars = get_total_character_count() * visible_ratio;
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 	update_configuration_warnings();
@@ -1193,6 +1209,7 @@ void Label::set_paragraph_separator(const String &p_paragraph_separator) {
 	if (paragraph_separator != p_paragraph_separator) {
 		paragraph_separator = p_paragraph_separator;
 		text_dirty = true;
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -1285,6 +1302,7 @@ void Label::set_visible_characters(int p_amount) {
 		}
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		queue_redraw();
 	}
@@ -1309,6 +1327,7 @@ void Label::set_visible_ratio(float p_ratio) {
 
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		queue_redraw();
 	}
@@ -1326,6 +1345,7 @@ void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavio
 	if (visible_chars_behavior != p_behavior) {
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING || p_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		visible_chars_behavior = p_behavior;
 		queue_redraw();
@@ -1448,6 +1468,7 @@ void Label::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
 
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Label, normal_style, "normal");
+	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Label, focus_style, "focus");
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, line_spacing);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, paragraph_spacing);
 
@@ -1463,6 +1484,7 @@ void Label::_bind_methods() {
 }
 
 Label::Label(const String &p_text) {
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_mouse_filter(MOUSE_FILTER_IGNORE);
 	set_text(p_text);
 	set_v_size_flags(SIZE_SHRINK_CENTER);

+ 1 - 0
scene/gui/label.h

@@ -90,6 +90,7 @@ private:
 
 	struct ThemeCache {
 		Ref<StyleBox> normal_style;
+		Ref<StyleBox> focus_style;
 		Ref<Font> font;
 
 		int font_size = 0;

+ 142 - 1
scene/gui/line_edit.cpp

@@ -33,6 +33,7 @@
 #include "core/input/input_map.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
+#include "core/string/translation_server.h"
 #include "scene/gui/label.h"
 #include "scene/main/window.h"
 #include "scene/theme/theme_db.h"
@@ -486,6 +487,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 						if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
 							DisplayServer::get_singleton()->clipboard_set_primary(text);
 						}
+						queue_accessibility_update();
 					} else if (b->is_double_click()) {
 						// Double-click select word.
 						last_dblclk = OS::get_singleton()->get_ticks_msec();
@@ -500,6 +502,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 								selection.creating = true;
 								selection.start_column = caret_column;
 								set_caret_column(selection.end);
+								queue_accessibility_update();
 								break;
 							}
 						}
@@ -973,7 +976,9 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	if (p_data.is_string() && is_editable()) {
 		apply_ime();
 
-		set_caret_at_pixel_pos(p_point.x);
+		if (p_point != Vector2(INFINITY, INFINITY)) {
+			set_caret_at_pixel_pos(p_point.x);
+		}
 		int caret_column_tmp = caret_column;
 		bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
 		if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
@@ -1009,6 +1014,7 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 			}
 			text_changed_dirty = true;
 		}
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -1035,6 +1041,33 @@ void LineEdit::_update_theme_item_cache() {
 	theme_cache.base_scale = get_theme_default_base_scale();
 }
 
+void LineEdit::_accessibility_action_set_selection(const Variant &p_data) {
+	Dictionary new_selection = p_data;
+	int sel_start_pos = new_selection["start_char"];
+	int sel_end_pos = new_selection["end_char"];
+	select(sel_start_pos, sel_end_pos);
+}
+
+void LineEdit::_accessibility_action_replace_selected(const Variant &p_data) {
+	String new_text = p_data;
+	insert_text_at_caret(new_text);
+}
+
+void LineEdit::_accessibility_action_set_value(const Variant &p_data) {
+	String new_text = p_data;
+	set_text(new_text);
+}
+
+void LineEdit::_accessibility_action_menu(const Variant &p_data) {
+	_update_context_menu();
+
+	Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
+	menu->set_position(get_screen_position() + pos);
+	menu->reset_size();
+	menu->popup();
+	menu->grab_focus();
+}
+
 void LineEdit::_notification(int p_what) {
 	switch (p_what) {
 #ifdef TOOLS_ENABLED
@@ -1049,6 +1082,98 @@ void LineEdit::_notification(int p_what) {
 			}
 		} break;
 #endif
+		case NOTIFICATION_EXIT_TREE:
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			accessibility_text_root_element = RID();
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TEXT_FIELD);
+			bool using_placeholder = text.is_empty() && ime_text.is_empty();
+			if (using_placeholder && !placeholder.is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_placeholder(ae, atr(placeholder));
+			}
+			if (!placeholder.is_empty() && get_accessibility_name().is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, atr(placeholder));
+			}
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_READONLY, !editable);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_TEXT_SELECTION, callable_mp(this, &LineEdit::_accessibility_action_set_selection));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_REPLACE_SELECTED_TEXT, callable_mp(this, &LineEdit::_accessibility_action_replace_selected));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &LineEdit::_accessibility_action_set_value));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &LineEdit::_accessibility_action_menu));
+			if (!language.is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_language(ae, language);
+			} else {
+				DisplayServer::get_singleton()->accessibility_update_set_language(ae, TranslationServer::get_singleton()->get_tool_locale());
+			}
+
+			bool rtl = is_layout_rtl();
+			Ref<StyleBox> style = theme_cache.normal;
+			Ref<Font> font = theme_cache.font;
+
+			Size2 size = get_size();
+
+			int x_ofs = 0;
+			float text_width = TS->shaped_text_get_size(text_rid).x;
+			float text_height = TS->shaped_text_get_size(text_rid).y;
+			int y_area = size.height - style->get_minimum_size().height;
+			int y_ofs = style->get_offset().y + (y_area - text_height) / 2;
+
+			switch (alignment) {
+				case HORIZONTAL_ALIGNMENT_FILL:
+				case HORIZONTAL_ALIGNMENT_LEFT: {
+					if (rtl) {
+						x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - Math::ceil(style->get_margin(SIDE_RIGHT) + (text_width))));
+					} else {
+						x_ofs = style->get_offset().x;
+					}
+				} break;
+				case HORIZONTAL_ALIGNMENT_CENTER: {
+					if (!Math::is_zero_approx(scroll_offset)) {
+						x_ofs = style->get_offset().x;
+					} else {
+						x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - (text_width)) / 2);
+					}
+				} break;
+				case HORIZONTAL_ALIGNMENT_RIGHT: {
+					if (rtl) {
+						x_ofs = style->get_offset().x;
+					} else {
+						x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - Math::ceil(style->get_margin(SIDE_RIGHT) + (text_width))));
+					}
+				} break;
+			}
+			bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+			if (right_icon.is_valid() || display_clear_icon) {
+				Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon;
+				if (alignment == HORIZONTAL_ALIGNMENT_CENTER) {
+					if (Math::is_zero_approx(scroll_offset)) {
+						x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2);
+					}
+				} else {
+					x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT));
+				}
+			}
+
+			float text_off_x = x_ofs + scroll_offset;
+
+			if (accessibility_text_root_element.is_null()) {
+				accessibility_text_root_element = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(ae, using_placeholder ? RID() : text_rid, text_height);
+			}
+
+			Transform2D text_xform;
+			text_xform.set_origin(Vector2i(text_off_x, y_ofs));
+			DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_text_root_element, text_xform);
+			if (selection.enabled) {
+				DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, accessibility_text_root_element, selection.begin, accessibility_text_root_element, selection.end);
+			} else {
+				DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, accessibility_text_root_element, caret_column, accessibility_text_root_element, caret_column);
+			}
+		} break;
 
 		case NOTIFICATION_RESIZED: {
 			_fit_to_width();
@@ -1209,6 +1334,7 @@ void LineEdit::_notification(int p_what) {
 					RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_color);
 				}
 			}
+
 			const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid);
 			int gl_size = TS->shaped_text_get_glyph_count(text_rid);
 
@@ -1921,6 +2047,8 @@ void LineEdit::set_caret_column(int p_column) {
 
 	caret_column = p_column;
 
+	queue_accessibility_update();
+
 	// Fit to window.
 
 	if (!is_inside_tree()) {
@@ -1989,6 +2117,7 @@ void LineEdit::set_caret_column(int p_column) {
 
 	scroll_offset = MIN(0, scroll_offset);
 
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2083,6 +2212,7 @@ void LineEdit::deselect() {
 	selection.enabled = false;
 	selection.creating = false;
 	selection.double_click = false;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2141,6 +2271,7 @@ void LineEdit::selection_fill_at_caret() {
 	}
 
 	selection.enabled = (selection.begin != selection.end);
+	queue_accessibility_update();
 }
 
 void LineEdit::select_all() {
@@ -2156,6 +2287,7 @@ void LineEdit::select_all() {
 	selection.begin = 0;
 	selection.end = text.length();
 	selection.enabled = true;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2173,6 +2305,7 @@ void LineEdit::set_editable(bool p_editable) {
 	_validate_caret_can_draw();
 
 	update_minimum_size();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2242,6 +2375,7 @@ void LineEdit::select(int p_from, int p_to) {
 	selection.end = p_to;
 	selection.creating = false;
 	selection.double_click = false;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -2635,6 +2769,13 @@ void LineEdit::_shape() {
 	if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) {
 		update_minimum_size();
 	}
+
+	if (accessibility_text_root_element.is_valid()) {
+		DisplayServer::get_singleton()->accessibility_free_element(accessibility_text_root_element);
+		accessibility_text_root_element = RID();
+	}
+
+	queue_accessibility_update();
 }
 
 void LineEdit::_fit_to_width() {

+ 6 - 0
scene/gui/line_edit.h

@@ -105,6 +105,7 @@ private:
 	Point2 ime_selection;
 
 	RID text_rid;
+	RID accessibility_text_root_element;
 	float full_width = 0.0;
 
 	bool selecting_enabled = true;
@@ -264,6 +265,11 @@ protected:
 	virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
+	void _accessibility_action_set_selection(const Variant &p_data);
+	void _accessibility_action_replace_selected(const Variant &p_data);
+	void _accessibility_action_set_value(const Variant &p_data);
+	void _accessibility_action_menu(const Variant &p_data);
+
 public:
 	void edit();
 	void unedit();

+ 18 - 2
scene/gui/link_button.cpp

@@ -44,6 +44,8 @@ void LinkButton::_shape() {
 	}
 	TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text));
 	text_buf->add_string(xl_text, font, font_size, language);
+
+	queue_accessibility_update();
 }
 
 void LinkButton::set_text(const String &p_text) {
@@ -109,7 +111,10 @@ String LinkButton::get_language() const {
 }
 
 void LinkButton::set_uri(const String &p_uri) {
-	uri = p_uri;
+	if (uri != p_uri) {
+		uri = p_uri;
+		queue_accessibility_update();
+	}
 }
 
 String LinkButton::get_uri() const {
@@ -147,6 +152,17 @@ Size2 LinkButton::get_minimum_size() const {
 
 void LinkButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LINK);
+			if (!xl_text.is_empty() && get_accessibility_name().is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, xl_text);
+			}
+			DisplayServer::get_singleton()->accessibility_update_set_url(ae, uri);
+		} break;
+
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 			xl_text = atr(text);
 			_shape();
@@ -288,7 +304,7 @@ void LinkButton::_bind_methods() {
 
 LinkButton::LinkButton(const String &p_text) {
 	text_buf.instantiate();
-	set_focus_mode(FOCUS_NONE);
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_default_cursor_shape(CURSOR_POINTING_HAND);
 
 	set_text(p_text);

+ 20 - 0
scene/gui/menu_bar.cpp

@@ -85,6 +85,15 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
 			_open_popup(selected_menu, true);
 		}
 		return;
+	} else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
+		if (focused_menu == -1) {
+			focused_menu = 0;
+		}
+		selected_menu = focused_menu;
+		if (active_menu >= 0) {
+			get_menu_popup(active_menu)->hide();
+		}
+		_open_popup(selected_menu, true);
 	}
 
 	Ref<InputEventMouseMotion> mm = p_event;
@@ -276,6 +285,12 @@ void MenuBar::unbind_global_menu() {
 
 void MenuBar::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_MENU_BAR);
+		} break;
 		case NOTIFICATION_ENTER_TREE: {
 			if (get_menu_count() > 0) {
 				_refresh_menu_names();
@@ -408,6 +423,10 @@ void MenuBar::_draw_menu_item(int p_index) {
 	bool pressed = (active_menu == p_index);
 	bool rtl = is_layout_rtl();
 
+	if (has_focus() && focused_menu == -1 && p_index == 0) {
+		hovered = true;
+	}
+
 	if (menu_cache[p_index].hidden) {
 		return;
 	}
@@ -950,6 +969,7 @@ String MenuBar::get_tooltip(const Point2 &p_pos) const {
 }
 
 MenuBar::MenuBar() {
+	set_focus_mode(FOCUS_ALL);
 	set_process_shortcut_input(true);
 }
 

+ 9 - 1
scene/gui/menu_button.cpp

@@ -126,6 +126,14 @@ int MenuButton::get_item_count() const {
 
 void MenuButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+			DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_MENU);
+		} break;
+
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
 		} break;
@@ -218,7 +226,7 @@ MenuButton::MenuButton(const String &p_text) :
 	set_toggle_mode(true);
 	set_disable_shortcuts(false);
 	set_process_shortcut_input(true);
-	set_focus_mode(FOCUS_NONE);
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_action_mode(ACTION_MODE_BUTTON_PRESS);
 
 	popup = memnew(PopupMenu);

+ 8 - 0
scene/gui/option_button.cpp

@@ -73,6 +73,14 @@ Size2 OptionButton::get_minimum_size() const {
 
 void OptionButton::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
+			DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_LIST);
+		} break;
+
 		case NOTIFICATION_POSTINITIALIZE: {
 			_refresh_size_cache();
 			if (has_theme_icon(SNAME("arrow"))) {

+ 7 - 0
scene/gui/panel.cpp

@@ -33,6 +33,13 @@
 
 void Panel::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PANEL);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			RID ci = get_canvas_item();
 			theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size()));

+ 3 - 3
scene/gui/popup.cpp

@@ -38,7 +38,7 @@
 #include "scene/theme/theme_db.h"
 
 void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
-	if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+	if ((ac_popup || get_flag(FLAG_POPUP)) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
 		hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally.
 		_close_pressed();
 	}
@@ -115,7 +115,7 @@ void Popup::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_APPLICATION_FOCUS_OUT: {
-			if (!is_in_edited_scene_root() && get_flag(FLAG_POPUP)) {
+			if (!is_in_edited_scene_root() && (get_flag(FLAG_POPUP) || ac_popup)) {
 				if (hide_reason == HIDE_REASON_NONE) {
 					hide_reason = HIDE_REASON_UNFOCUSED;
 				}
@@ -126,7 +126,7 @@ void Popup::_notification(int p_what) {
 }
 
 void Popup::_parent_focused() {
-	if (popped_up && get_flag(FLAG_POPUP)) {
+	if (popped_up && (get_flag(FLAG_POPUP) || ac_popup)) {
 		if (hide_reason == HIDE_REASON_NONE) {
 			hide_reason = HIDE_REASON_UNFOCUSED;
 		}

+ 2 - 0
scene/gui/popup.h

@@ -40,6 +40,7 @@ class Popup : public Window {
 	GDCLASS(Popup, Window);
 
 	LocalVector<Window *> visible_parents;
+	bool ac_popup = false;
 	bool popped_up = false;
 
 public:
@@ -59,6 +60,7 @@ protected:
 	void _close_pressed();
 	virtual Rect2i _popup_adjust_rect() const override;
 	virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
+	void set_ac_popup() { ac_popup = true; }
 
 	void _notification(int p_what);
 	void _validate_property(PropertyInfo &p_property) const;

+ 199 - 11
scene/gui/popup_menu.cpp

@@ -499,9 +499,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 			bool match_found = false;
 			for (int i = search_from; i < items.size(); i++) {
 				if (!items[i].separator && !items[i].disabled) {
+					prev_mouse_over = mouse_over;
 					mouse_over = i;
 					emit_signal(SNAME("id_focused"), items[i].id);
 					scroll_to_item(i);
+					queue_accessibility_update();
 					control->queue_redraw();
 					set_input_as_handled();
 					match_found = true;
@@ -513,9 +515,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 				// If the last item is not selectable, try re-searching from the start.
 				for (int i = 0; i < search_from; i++) {
 					if (!items[i].separator && !items[i].disabled) {
+						prev_mouse_over = mouse_over;
 						mouse_over = i;
 						emit_signal(SNAME("id_focused"), items[i].id);
 						scroll_to_item(i);
+						queue_accessibility_update();
 						control->queue_redraw();
 						set_input_as_handled();
 						break;
@@ -537,9 +541,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 			bool match_found = false;
 			for (int i = search_from; i >= 0; i--) {
 				if (!items[i].separator && !items[i].disabled) {
+					prev_mouse_over = mouse_over;
 					mouse_over = i;
 					emit_signal(SNAME("id_focused"), items[i].id);
 					scroll_to_item(i);
+					queue_accessibility_update();
 					control->queue_redraw();
 					set_input_as_handled();
 					match_found = true;
@@ -551,9 +557,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 				// If the first item is not selectable, try re-searching from the end.
 				for (int i = items.size() - 1; i >= search_from; i--) {
 					if (!items[i].separator && !items[i].disabled) {
+						prev_mouse_over = mouse_over;
 						mouse_over = i;
 						emit_signal(SNAME("id_focused"), items[i].id);
 						scroll_to_item(i);
+						queue_accessibility_update();
 						control->queue_redraw();
 						set_input_as_handled();
 						break;
@@ -738,9 +746,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 			}
 
 			if (items[i].text.findn(search_string) == 0) {
+				prev_mouse_over = mouse_over;
 				mouse_over = i;
 				emit_signal(SNAME("id_focused"), items[i].id);
 				scroll_to_item(i);
+				queue_accessibility_update();
 				control->queue_redraw();
 				set_input_as_handled();
 				break;
@@ -755,6 +765,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
 
 	if (id < 0) {
 		mouse_over = -1;
+		queue_accessibility_update();
 		control->queue_redraw();
 		return;
 	}
@@ -766,6 +777,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
 
 	if (over != mouse_over) {
 		mouse_over = over;
+		queue_accessibility_update();
 		control->queue_redraw();
 	}
 }
@@ -1104,8 +1116,105 @@ void PopupMenu::remove_child_notify(Node *p_child) {
 	_menu_changed();
 }
 
+void PopupMenu::_accessibility_action_click(const Variant &p_data, int p_idx) {
+	activate_item(p_idx);
+}
+
+RID PopupMenu::get_focused_accessibility_element() const {
+	if (mouse_over == -1) {
+		return get_accessibility_element();
+	} else {
+		const Item &item = items[mouse_over];
+		return item.accessibility_item_element;
+	}
+}
+
 void PopupMenu::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_EXIT_TREE: {
+			if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
+				unbind_global_menu();
+			}
+			[[fallthrough]];
+		}
+
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			for (int i = 0; i < items.size(); i++) {
+				items.write[i].accessibility_item_element = RID();
+			}
+			accessibility_scroll_element = RID();
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			if (has_meta("_menu_name")) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, get_meta("_menu_name", get_name()));
+			}
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_MENU);
+			DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, items.size());
+
+			if (accessibility_scroll_element.is_null()) {
+				accessibility_scroll_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
+			}
+
+			Transform2D scroll_xform;
+			scroll_xform.set_origin(Vector2i(0, -scroll_container->get_v_scroll_bar()->get_value()));
+			DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_scroll_element, scroll_xform);
+			DisplayServer::get_singleton()->accessibility_update_set_bounds(accessibility_scroll_element, Rect2(0, 0, get_size().x, scroll_container->get_v_scroll_bar()->get_max()));
+
+			float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0;
+			float display_width = control->get_size().width - scroll_width;
+			Point2 ofs;
+
+			for (int i = 0; i < items.size(); i++) {
+				const Item &item = items.write[i];
+
+				ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
+
+				Point2 item_ofs = ofs;
+				if (item.accessibility_item_element.is_null()) {
+					item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(accessibility_scroll_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM);
+					item.accessibility_item_dirty = true;
+				}
+
+				item_ofs.x += item.indent * theme_cache.indent;
+				float h = _get_item_height(i);
+
+				if (item.accessibility_item_dirty || i == prev_mouse_over || i == mouse_over) {
+					switch (item.checkable_type) {
+						case Item::CHECKABLE_TYPE_NONE: {
+							DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM);
+						} break;
+						case Item::CHECKABLE_TYPE_CHECK_BOX: {
+							DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM_CHECK_BOX);
+							DisplayServer::get_singleton()->accessibility_update_set_checked(item.accessibility_item_element, item.checked);
+						} break;
+						case Item::CHECKABLE_TYPE_RADIO_BUTTON: {
+							DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM_RADIO);
+							DisplayServer::get_singleton()->accessibility_update_set_checked(item.accessibility_item_element, item.checked);
+						} break;
+					}
+
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &PopupMenu::_accessibility_action_click).bind(i));
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_level(item.accessibility_item_element, 0);
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, i == mouse_over);
+					DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, item.xl_text);
+					DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
+					DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
+
+					DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(item_ofs, Size2(display_width, h + theme_cache.v_separation)));
+
+					item.accessibility_item_dirty = false;
+				}
+				ofs.y += h;
+			}
+			prev_mouse_over = -1;
+
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
 			if (pm) {
@@ -1118,12 +1227,6 @@ void PopupMenu::_notification(int p_what) {
 			}
 		} break;
 
-		case NOTIFICATION_EXIT_TREE: {
-			if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
-				unbind_global_menu();
-			}
-		} break;
-
 		case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_THEME_CHANGED: {
 			panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
@@ -1151,7 +1254,9 @@ void PopupMenu::_notification(int p_what) {
 				if (is_global) {
 					nmenu->set_item_text(global_menu, i, item.xl_text);
 				}
+				item.accessibility_item_dirty = true;
 				_shape_item(i);
+				queue_accessibility_update();
 			}
 
 			child_controls_changed();
@@ -1166,6 +1271,7 @@ void PopupMenu::_notification(int p_what) {
 		case NOTIFICATION_WM_MOUSE_EXIT: {
 			if (mouse_over >= 0 && (!items[mouse_over].submenu || submenu_over != -1)) {
 				mouse_over = -1;
+				queue_accessibility_update();
 				control->queue_redraw();
 			}
 		} break;
@@ -1281,7 +1387,9 @@ void PopupMenu::_notification(int p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			if (!is_visible()) {
 				if (mouse_over >= 0) {
+					prev_mouse_over = mouse_over;
 					mouse_over = -1;
+					queue_accessibility_update();
 					control->queue_redraw();
 				}
 
@@ -1341,6 +1449,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1364,6 +1473,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1387,6 +1497,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1412,6 +1523,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1435,6 +1547,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1460,6 +1573,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1485,6 +1599,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1522,6 +1637,7 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1551,6 +1667,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1580,6 +1697,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1611,6 +1729,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1640,6 +1759,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1671,6 +1791,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1697,6 +1818,7 @@ void PopupMenu::add_submenu_node_item(const String &p_label, PopupMenu *p_submen
 	item.text = p_label;
 	item.xl_text = atr(p_label);
 	item.id = p_id == -1 ? items.size() : p_id;
+	item.accessibility_item_dirty = true;
 	item.submenu = p_submenu;
 	item.submenu_name = p_submenu->get_name();
 	items.push_back(item);
@@ -1710,6 +1832,7 @@ void PopupMenu::add_submenu_node_item(const String &p_label, PopupMenu *p_submen
 	}
 
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 
 	child_controls_changed();
@@ -1733,13 +1856,16 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
 	items.write[p_idx].text = p_text;
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	items.write[p_idx].dirty = true;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_text(global_menu, p_idx, items[p_idx].xl_text);
 	}
-	_shape_item(p_idx);
 
+	_shape_item(p_idx);
+	queue_accessibility_update();
 	control->queue_redraw();
+
 	child_controls_changed();
 	_menu_changed();
 }
@@ -1750,9 +1876,14 @@ void PopupMenu::set_item_text_direction(int p_idx, Control::TextDirection p_text
 	}
 	ERR_FAIL_INDEX(p_idx, items.size());
 	ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+
 	if (items[p_idx].text_direction != p_text_direction) {
 		items.write[p_idx].text_direction = p_text_direction;
 		items.write[p_idx].dirty = true;
+		items.write[p_idx].accessibility_item_dirty = true;
+
+		_shape_item(p_idx);
+		queue_accessibility_update();
 		control->queue_redraw();
 	}
 }
@@ -1765,6 +1896,10 @@ void PopupMenu::set_item_language(int p_idx, const String &p_language) {
 	if (items[p_idx].language != p_language) {
 		items.write[p_idx].language = p_language;
 		items.write[p_idx].dirty = true;
+		items.write[p_idx].accessibility_item_dirty = true;
+
+		_shape_item(p_idx);
+		queue_accessibility_update();
 		control->queue_redraw();
 	}
 }
@@ -1846,11 +1981,13 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
 	}
 
 	items.write[p_idx].checked = p_checked;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, p_checked);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	child_controls_changed();
 	_menu_changed();
@@ -1889,11 +2026,13 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
 
 	items.write[p_idx].accel = p_accel;
 	items.write[p_idx].dirty = true;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_accelerator(global_menu, p_idx, p_accel);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	child_controls_changed();
 	_menu_changed();
@@ -1925,11 +2064,13 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
 	}
 
 	items.write[p_idx].disabled = p_disabled;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_disabled(global_menu, p_idx, p_disabled);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	child_controls_changed();
 	_menu_changed();
@@ -1993,11 +2134,13 @@ void PopupMenu::set_item_submenu_node(int p_idx, PopupMenu *p_submenu) {
 void PopupMenu::toggle_item_checked(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 	items.write[p_idx].checked = !items[p_idx].checked;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, items[p_idx].checked);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	child_controls_changed();
 	_menu_changed();
@@ -2134,6 +2277,9 @@ void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
 	}
 
 	items.write[p_idx].separator = p_separator;
+	items.write[p_idx].accessibility_item_dirty = true;
+
+	queue_accessibility_update();
 	control->queue_redraw();
 }
 
@@ -2154,11 +2300,13 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
 	}
 
 	items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checkable(global_menu, p_idx, p_checkable);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	_menu_changed();
 }
@@ -2175,11 +2323,13 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
 	}
 
 	items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_radio_checkable(global_menu, p_idx, p_radio_checkable);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	_menu_changed();
 }
@@ -2195,11 +2345,13 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
 	}
 
 	items.write[p_idx].tooltip = p_tooltip;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_tooltip(global_menu, p_idx, p_tooltip);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	_menu_changed();
 }
@@ -2299,11 +2451,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
 	}
 
 	items.write[p_idx].state = p_state;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, p_state);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	_menu_changed();
 }
@@ -2348,11 +2502,13 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
 	if (items.write[p_idx].max_states <= items[p_idx].state) {
 		items.write[p_idx].state = 0;
 	}
+	items.write[p_idx].accessibility_item_dirty = true;
 
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, items[p_idx].state);
 	}
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	_menu_changed();
 }
@@ -2386,11 +2542,12 @@ void PopupMenu::set_focused_item(int p_idx) {
 		return;
 	}
 
+	prev_mouse_over = mouse_over;
 	mouse_over = p_idx;
 	if (mouse_over != -1) {
 		scroll_to_item(mouse_over);
 	}
-
+	queue_accessibility_update();
 	control->queue_redraw();
 }
 
@@ -2412,6 +2569,10 @@ void PopupMenu::set_item_count(int p_count) {
 	if (is_global && prev_size > p_count) {
 		for (int i = prev_size - 1; i >= p_count; i--) {
 			nmenu->remove_item(global_menu, i);
+			if (items[i].accessibility_item_element.is_valid()) {
+				DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
+				items.write[i].accessibility_item_element = RID();
+			}
 		}
 	}
 
@@ -2592,6 +2753,10 @@ void PopupMenu::activate_item(int p_idx) {
 void PopupMenu::remove_item(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 
+	if (items[p_idx].accessibility_item_element.is_valid()) {
+		DisplayServer::get_singleton()->accessibility_free_element(items.write[p_idx].accessibility_item_element);
+		items.write[p_idx].accessibility_item_element = RID();
+	}
 	if (items[p_idx].shortcut.is_valid()) {
 		_unref_shortcut(items[p_idx].shortcut);
 	}
@@ -2611,6 +2776,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
 	Item sep;
 	sep.separator = true;
 	sep.id = p_id;
+	sep.accessibility_item_dirty = true;
 	if (!p_text.is_empty()) {
 		sep.text = p_text;
 		sep.xl_text = atr(p_text);
@@ -2626,7 +2792,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
 }
 
 void PopupMenu::clear(bool p_free_submenus) {
-	for (const Item &I : items) {
+	for (Item &I : items) {
+		if (I.accessibility_item_element.is_valid()) {
+			DisplayServer::get_singleton()->accessibility_free_element(I.accessibility_item_element);
+			I.accessibility_item_element = RID();
+		}
 		if (I.shortcut.is_valid()) {
 			_unref_shortcut(I.shortcut);
 		}
@@ -2650,7 +2820,9 @@ void PopupMenu::clear(bool p_free_submenus) {
 	}
 	items.clear();
 
+	prev_mouse_over = -1;
 	mouse_over = -1;
+	queue_accessibility_update();
 	control->queue_redraw();
 	child_controls_changed();
 	notify_property_list_changed();
@@ -3032,7 +3204,15 @@ void PopupMenu::popup(const Rect2i &p_bounds) {
 	if (native) {
 		_native_popup(p_bounds != Rect2i() ? p_bounds : Rect2i(get_position(), Size2i()));
 	} else {
-		set_flag(FLAG_NO_FOCUS, !is_embedded());
+		if (is_inside_tree()) {
+			bool ac = get_tree()->is_accessibility_enabled();
+			// Note: Native popup menus need keyboard focus to work with screen reader.
+			set_flag(FLAG_POPUP, !ac);
+			set_flag(FLAG_NO_FOCUS, !is_embedded() && !ac);
+			if (ac) {
+				set_ac_popup();
+			}
+		}
 
 		moved = Vector2();
 		popup_time_msec = OS::get_singleton()->get_ticks_msec();
@@ -3065,7 +3245,15 @@ void PopupMenu::set_visible(bool p_visible) {
 			_native_popup(Rect2i(get_position(), get_size()));
 		}
 	} else {
-		set_flag(FLAG_NO_FOCUS, !is_embedded());
+		if (is_inside_tree()) {
+			bool ac = get_tree()->is_accessibility_enabled();
+			// Note: Native popup menus need keyboard focus to work with screen reader.
+			set_flag(FLAG_POPUP, !ac);
+			set_flag(FLAG_NO_FOCUS, !is_embedded() && !ac);
+			if (ac) {
+				set_ac_popup();
+			}
+		}
 
 		Popup::set_visible(p_visible);
 	}

+ 9 - 0
scene/gui/popup_menu.h

@@ -44,6 +44,9 @@ class PopupMenu : public Popup {
 	static HashMap<NativeMenu::SystemMenus, PopupMenu *> system_menus;
 
 	struct Item {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		Ref<Texture2D> icon;
 		int icon_max_width = 0;
 		Color icon_modulate = Color(1, 1, 1, 1);
@@ -95,6 +98,7 @@ class PopupMenu : public Popup {
 
 		Item(bool p_dummy) {}
 	};
+	RID accessibility_scroll_element;
 
 	mutable Rect2i pre_popup_rect;
 	void _update_shadow_offsets() const;
@@ -122,6 +126,7 @@ class PopupMenu : public Popup {
 	bool during_grabbed_click = false;
 	bool is_scrolling = false;
 	int mouse_over = -1;
+	int prev_mouse_over = -1;
 	int submenu_over = -1;
 	String _get_accel_text(const Item &p_item) const;
 	int _get_mouse_over(const Point2 &p_over) const;
@@ -134,6 +139,8 @@ class PopupMenu : public Popup {
 
 	void _shape_item(int p_idx) const;
 
+	void _accessibility_action_click(const Variant &p_data, int p_idx);
+
 	void _activate_submenu(int p_over, bool p_by_keyboard = false);
 	void _submenu_timeout();
 
@@ -249,6 +256,8 @@ public:
 	// this value should be updated to reflect the new size.
 	static const int ITEM_PROPERTY_SIZE = 10;
 
+	virtual RID get_focused_accessibility_element() const override;
+
 	virtual void _parent_focused() override;
 
 	RID bind_global_menu();

+ 8 - 0
scene/gui/progress_bar.cpp

@@ -54,6 +54,14 @@ void ProgressBar::_notification(int p_what) {
 				queue_redraw();
 			}
 		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PROGRESS_INDICATOR);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size()));
 

+ 50 - 0
scene/gui/range.cpp

@@ -43,12 +43,51 @@ PackedStringArray Range::get_configuration_warnings() const {
 void Range::_value_changed(double p_value) {
 	GDVIRTUAL_CALL(_value_changed, p_value);
 }
+
 void Range::_value_changed_notify() {
 	_value_changed(shared->val);
 	emit_signal(SceneStringName(value_changed), shared->val);
+	queue_accessibility_update();
 	queue_redraw();
 }
 
+void Range::_accessibility_action_inc(const Variant &p_data) {
+	double step = ((shared->step > 0) ? shared->step : 1);
+	set_value(shared->val + step);
+}
+
+void Range::_accessibility_action_dec(const Variant &p_data) {
+	double step = ((shared->step > 0) ? shared->step : 1);
+	set_value(shared->val - step);
+}
+
+void Range::_accessibility_action_set_value(const Variant &p_data) {
+	double new_val = p_data;
+	set_value(new_val);
+}
+
+void Range::_notification(int p_what) {
+	ERR_MAIN_THREAD_GUARD;
+	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPIN_BUTTON);
+			DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, shared->val);
+			DisplayServer::get_singleton()->accessibility_update_set_num_range(ae, shared->min, shared->max);
+			if (shared->step > 0) {
+				DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, shared->step);
+			} else {
+				DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, 1);
+			}
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &Range::_accessibility_action_dec));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &Range::_accessibility_action_inc));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &Range::_accessibility_action_set_value));
+		} break;
+	}
+}
+
 void Range::Shared::emit_value_changed() {
 	for (Range *E : owners) {
 		Range *r = E;
@@ -80,6 +119,7 @@ void Range::Shared::redraw_owners() {
 		if (!r->is_inside_tree()) {
 			continue;
 		}
+		r->queue_accessibility_update();
 		r->queue_redraw();
 	}
 }
@@ -91,6 +131,7 @@ void Range::set_value(double p_val) {
 	if (shared->val != prev_val) {
 		shared->emit_value_changed();
 	}
+	queue_accessibility_update();
 }
 
 void Range::_set_value_no_signal(double p_val) {
@@ -143,6 +184,8 @@ void Range::set_min(double p_min) {
 	shared->emit_changed("min");
 
 	update_configuration_warnings();
+
+	queue_accessibility_update();
 }
 
 void Range::set_max(double p_max) {
@@ -156,6 +199,8 @@ void Range::set_max(double p_max) {
 	set_value(shared->val);
 
 	shared->emit_changed("max");
+
+	queue_accessibility_update();
 }
 
 void Range::set_step(double p_step) {
@@ -165,6 +210,8 @@ void Range::set_step(double p_step) {
 
 	shared->step = p_step;
 	shared->emit_changed("step");
+
+	queue_accessibility_update();
 }
 
 void Range::set_page(double p_page) {
@@ -177,6 +224,8 @@ void Range::set_page(double p_page) {
 	set_value(shared->val);
 
 	shared->emit_changed("page");
+
+	queue_accessibility_update();
 }
 
 double Range::get_value() const {
@@ -264,6 +313,7 @@ void Range::unshare() {
 	nshared->allow_lesser = shared->allow_lesser;
 	_unref_shared();
 	_ref_shared(nshared);
+	queue_accessibility_update();
 }
 
 void Range::_ref_shared(Shared *p_shared) {

+ 5 - 0
scene/gui/range.h

@@ -64,9 +64,14 @@ class Range : public Control {
 protected:
 	virtual void _value_changed(double p_value);
 	void _notify_shared_value_changed() { shared->emit_value_changed(); }
+	void _notification(int p_what);
 
 	static void _bind_methods();
 
+	void _accessibility_action_inc(const Variant &p_data);
+	void _accessibility_action_dec(const Variant &p_data);
+	void _accessibility_action_set_value(const Variant &p_data);
+
 	bool _rounded_values = false;
 
 	GDVIRTUAL1(_value_changed, double)

+ 11 - 1
scene/gui/rich_text_label.compat.inc

@@ -51,7 +51,15 @@ void RichTextLabel::_push_meta_bind_compat_89024(const Variant &p_meta) {
 }
 
 void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) {
-	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false);
+	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false, String());
+}
+
+void RichTextLabel::_add_image_bind_compat_76829(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, const Variant &p_key, bool p_pad, const String &p_tooltip, bool p_size_in_percent) {
+	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, p_key, p_pad, p_tooltip, p_size_in_percent, String());
+}
+
+void RichTextLabel::_push_table_bind_compat_76829(int p_columns, InlineAlignment p_alignment, int p_align_to_row) {
+	push_table(p_columns, p_alignment, p_align_to_row, String());
 }
 
 bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) {
@@ -65,6 +73,8 @@ void RichTextLabel::_bind_compatibility_methods() {
 	ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data", "underline_mode"), &RichTextLabel::_push_meta_bind_compat_99481, DEFVAL(META_UNDERLINE_ALWAYS));
 	ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024);
 	ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()));
+	ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::_add_image_bind_compat_76829, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
+	ClassDB::bind_compatibility_method(D_METHOD("push_table", "columns", "inline_align", "align_to_row"), &RichTextLabel::_push_table_bind_compat_76829, DEFVAL(INLINE_ALIGNMENT_TOP), DEFVAL(-1));
 	ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
 }
 

Fișier diff suprimat deoarece este prea mare
+ 623 - 35
scene/gui/rich_text_label.cpp


+ 54 - 10
scene/gui/rich_text_label.h

@@ -106,12 +106,12 @@ public:
 	};
 
 	enum DefaultFont {
-		NORMAL_FONT,
-		BOLD_FONT,
-		ITALICS_FONT,
-		BOLD_ITALICS_FONT,
-		MONO_FONT,
-		CUSTOM_FONT,
+		RTL_NORMAL_FONT,
+		RTL_BOLD_FONT,
+		RTL_ITALICS_FONT,
+		RTL_BOLD_ITALICS_FONT,
+		RTL_MONO_FONT,
+		RTL_CUSTOM_FONT,
 	};
 
 	enum ImageUpdateMask {
@@ -137,8 +137,11 @@ protected:
 	void _push_meta_bind_compat_99481(const Variant &p_meta, MetaUnderline p_underline_mode);
 	void _push_meta_bind_compat_89024(const Variant &p_meta);
 	void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region);
+	void _add_image_bind_compat_76829(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, const Variant &p_key, bool p_pad, const String &p_tooltip, bool p_size_in_percent);
+	void _push_table_bind_compat_76829(int p_columns, InlineAlignment p_alignment, int p_align_to_row);
 	bool _remove_paragraph_bind_compat_91098(int p_paragraph);
 	void _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio);
+
 	static void _bind_compatibility_methods();
 #endif
 
@@ -151,6 +154,11 @@ private:
 		Ref<TextLine> text_prefix;
 		float prefix_width = 0;
 		Ref<TextParagraph> text_buf;
+
+		RID accessibility_line_element;
+		RID accessibility_text_element;
+
+		Item *dc_item = nullptr;
 		Color dc_color;
 		int dc_ol_size = 0;
 		Color dc_ol_color;
@@ -160,7 +168,16 @@ private:
 		int char_offset = 0;
 		int char_count = 0;
 
-		Line() { text_buf.instantiate(); }
+		Line() {
+			text_buf.instantiate();
+		}
+		~Line() {
+			if (accessibility_line_element.is_valid()) {
+				DisplayServer::get_singleton()->accessibility_free_element(accessibility_line_element);
+				accessibility_line_element = RID();
+				accessibility_text_element = RID();
+			}
+		}
 
 		_FORCE_INLINE_ float get_height(float line_separation) const {
 			return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
@@ -178,6 +195,8 @@ private:
 		int line = 0;
 		RID rid;
 
+		RID accessibility_item_element;
+
 		void _clear_children() {
 			RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 			while (subitems.size()) {
@@ -237,6 +256,7 @@ private:
 
 	struct ItemImage : public Item {
 		Ref<Texture2D> image;
+		String alt_text;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		bool pad = false;
 		bool size_in_percent = false;
@@ -258,7 +278,7 @@ private:
 	};
 
 	struct ItemFont : public Item {
-		DefaultFont def_font = CUSTOM_FONT;
+		DefaultFont def_font = RTL_CUSTOM_FONT;
 		Ref<Font> font;
 		bool variation = false;
 		bool def_size = false;
@@ -341,6 +361,7 @@ private:
 
 	struct ItemTable : public Item {
 		struct Column {
+			String name;
 			bool expand = false;
 			bool shrink = true;
 			int expand_ratio = 0;
@@ -354,6 +375,7 @@ private:
 		LocalVector<float> rows;
 		LocalVector<float> rows_no_padding;
 		LocalVector<float> rows_baseline;
+		String name;
 
 		int align_to_row = -1;
 		int total_width = 0;
@@ -503,6 +525,9 @@ private:
 
 	Array custom_effects;
 
+	HashMap<RID, Rect2> ac_element_bounds_cache;
+
+	void _invalidate_accessibility();
 	void _invalidate_current_line(ItemFrame *p_frame);
 
 	void _thread_function(void *p_userdata);
@@ -552,6 +577,11 @@ private:
 	bool deselect_on_focus_loss_enabled = true;
 	bool drag_and_drop_selection_enabled = true;
 
+	ItemFrame *keyboard_focus_frame = nullptr;
+	int keyboard_focus_line = 0;
+	Item *keyboard_focus_item = nullptr;
+	bool keyboard_focus_on_text = true;
+
 	bool context_menu_enabled = false;
 	bool shortcut_keys_enabled = true;
 
@@ -580,6 +610,7 @@ private:
 	void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
 	int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
 	float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false);
+	void _accessibility_update_line(RID p_id, ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep);
 
 	String _roman(int p_num, bool p_capitalize) const;
 	String _letters(int p_num, bool p_capitalize) const;
@@ -646,6 +677,15 @@ private:
 	bool internal_stack_editing = false;
 	bool stack_externally_modified = false;
 
+	void _accessibility_action_menu(const Variant &p_data);
+	void _accessibility_scroll_down(const Variant &p_data);
+	void _accessibility_scroll_up(const Variant &p_data);
+	void _accessibility_scroll_set(const Variant &p_data);
+	void _accessibility_focus_item(const Variant &p_data, uint64_t p_item, bool p_line, bool p_foucs);
+	void _accessibility_scroll_to_item(const Variant &p_data, uint64_t p_item);
+
+	RID accessibility_scroll_element;
+
 	bool fit_content = false;
 
 	struct ThemeCache {
@@ -692,9 +732,12 @@ private:
 	} theme_cache;
 
 public:
+	virtual RID get_focused_accessibility_element() const override;
+	PackedStringArray get_accessibility_configuration_warnings() const override;
+
 	String get_parsed_text() const;
 	void add_text(const String &p_text);
-	void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
+	void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false, const String &p_alt_text = String());
 	void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
 	void add_newline();
 	bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
@@ -720,7 +763,7 @@ public:
 	void push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet = String::utf8("•"));
 	void push_meta(const Variant &p_meta, MetaUnderline p_underline_mode = META_UNDERLINE_ALWAYS, const String &p_tooltip = String());
 	void push_hint(const String &p_string);
-	void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP, int p_align_to_row = -1);
+	void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP, int p_align_to_row = -1, const String &p_name = String());
 	void push_fade(int p_start_index, int p_length);
 	void push_shake(int p_strength, float p_rate, bool p_connected);
 	void push_wave(float p_frequency, float p_amplitude, bool p_connected);
@@ -732,6 +775,7 @@ public:
 	void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment);
 	void push_context();
 	void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1, bool p_shrink = true);
+	void set_table_column_name(int p_column, const String &p_name);
 	void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg);
 	void set_cell_border_color(const Color &p_color);
 	void set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size);

+ 9 - 0
scene/gui/scroll_bar.cpp

@@ -224,6 +224,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
 
 void ScrollBar::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SCROLL_BAR);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			RID ci = get_canvas_item();
 
@@ -654,6 +661,8 @@ ScrollBar::ScrollBar(Orientation p_orientation) {
 
 	if (focus_by_default) {
 		set_focus_mode(FOCUS_ALL);
+	} else {
+		set_focus_mode(FOCUS_ACCESSIBILITY);
 	}
 	set_step(0);
 }

+ 35 - 0
scene/gui/scroll_container.cpp

@@ -353,8 +353,43 @@ void ScrollContainer::_reposition_children() {
 	queue_redraw();
 }
 
+void ScrollContainer::_accessibility_action_scroll_set(const Variant &p_data) {
+	const Point2 &pos = p_data;
+	h_scroll->set_value(pos.x);
+	v_scroll->set_value(pos.y);
+}
+
+void ScrollContainer::_accessibility_action_scroll_up(const Variant &p_data) {
+	v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8);
+}
+
+void ScrollContainer::_accessibility_action_scroll_down(const Variant &p_data) {
+	v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8);
+}
+
+void ScrollContainer::_accessibility_action_scroll_left(const Variant &p_data) {
+	h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8);
+}
+
+void ScrollContainer::_accessibility_action_scroll_right(const Variant &p_data) {
+	h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8);
+}
+
 void ScrollContainer::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SCROLL_VIEW);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_down));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_left));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_right));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_UP, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_up));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_set));
+		} break;
+
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:

+ 6 - 0
scene/gui/scroll_container.h

@@ -99,6 +99,12 @@ protected:
 	void _update_scrollbar_position();
 	void _scroll_moved(float);
 
+	void _accessibility_action_scroll_set(const Variant &p_data);
+	void _accessibility_action_scroll_up(const Variant &p_data);
+	void _accessibility_action_scroll_down(const Variant &p_data);
+	void _accessibility_action_scroll_left(const Variant &p_data);
+	void _accessibility_action_scroll_right(const Variant &p_data);
+
 public:
 	virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
 

+ 6 - 0
scene/gui/slider.cpp

@@ -237,7 +237,13 @@ void Slider::_notification(int p_what) {
 					}
 				}
 			}
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
 
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SLIDER);
 		} break;
 
 		case NOTIFICATION_THEME_CHANGED: {

+ 45 - 1
scene/gui/spin_box.cpp

@@ -34,6 +34,50 @@
 #include "core/math/expression.h"
 #include "scene/theme/theme_db.h"
 
+void SpinBoxLineEdit::_accessibility_action_inc(const Variant &p_data) {
+	SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
+	if (parent_sb) {
+		double step = ((parent_sb->get_step() > 0) ? parent_sb->get_step() : 1);
+		parent_sb->set_value(parent_sb->get_value() + step);
+	}
+}
+
+void SpinBoxLineEdit::_accessibility_action_dec(const Variant &p_data) {
+	SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
+	if (parent_sb) {
+		double step = ((parent_sb->get_step() > 0) ? parent_sb->get_step() : 1);
+		parent_sb->set_value(parent_sb->get_value() - step);
+	}
+}
+
+void SpinBoxLineEdit::_notification(int p_what) {
+	ERR_MAIN_THREAD_GUARD;
+	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
+			if (parent_sb) {
+				DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPIN_BUTTON);
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, parent_sb->get_accessibility_name());
+				DisplayServer::get_singleton()->accessibility_update_set_description(ae, parent_sb->get_accessibility_description());
+				DisplayServer::get_singleton()->accessibility_update_set_live(ae, parent_sb->get_accessibility_live());
+				DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, parent_sb->get_value());
+				DisplayServer::get_singleton()->accessibility_update_set_num_range(ae, parent_sb->get_min(), parent_sb->get_max());
+				if (parent_sb->get_step() > 0) {
+					DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, parent_sb->get_step());
+				} else {
+					DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, 1);
+				}
+				//DisplayServer::get_singleton()->accessibility_update_set_num_jump(ae, ???);
+				DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SpinBoxLineEdit::_accessibility_action_dec));
+				DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SpinBoxLineEdit::_accessibility_action_inc));
+			}
+		} break;
+	}
+}
+
 Size2 SpinBox::get_minimum_size() const {
 	Size2 ms = line_edit->get_combined_minimum_size();
 	ms.width += sizing_cache.buttons_block_width;
@@ -650,7 +694,7 @@ void SpinBox::_bind_methods() {
 }
 
 SpinBox::SpinBox() {
-	line_edit = memnew(LineEdit);
+	line_edit = memnew(SpinBoxLineEdit);
 	line_edit->set_emoji_menu_enabled(false);
 	add_child(line_edit, false, INTERNAL_MODE_FRONT);
 

+ 16 - 1
scene/gui/spin_box.h

@@ -34,10 +34,25 @@
 #include "scene/gui/range.h"
 #include "scene/main/timer.h"
 
+class SpinBoxLineEdit : public LineEdit {
+	GDCLASS(SpinBoxLineEdit, LineEdit);
+
+protected:
+	void _notification(int p_what);
+
+	static void _bind_methods() {}
+
+	void _accessibility_action_inc(const Variant &p_data);
+	void _accessibility_action_dec(const Variant &p_data);
+
+public:
+	SpinBoxLineEdit() {}
+};
+
 class SpinBox : public Range {
 	GDCLASS(SpinBox, Range);
 
-	LineEdit *line_edit = nullptr;
+	SpinBoxLineEdit *line_edit = nullptr;
 	bool update_on_text_changed = false;
 	bool accepted = true;
 

+ 55 - 0
scene/gui/split_container.cpp

@@ -91,8 +91,59 @@ Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos
 	return Control::get_cursor_shape(p_pos);
 }
 
+void SplitContainerDragger::_accessibility_action_inc(const Variant &p_data) {
+	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+
+	if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
+		return;
+	}
+	sc->split_offset -= 10;
+	sc->_compute_split_offset(true);
+	sc->queue_sort();
+}
+
+void SplitContainerDragger::_accessibility_action_dec(const Variant &p_data) {
+	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+
+	if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
+		return;
+	}
+	sc->split_offset += 10;
+	sc->_compute_split_offset(true);
+	sc->queue_sort();
+}
+
+void SplitContainerDragger::_accessibility_action_set_value(const Variant &p_data) {
+	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+
+	if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
+		return;
+	}
+	sc->split_offset = p_data;
+	sc->_compute_split_offset(true);
+	sc->queue_sort();
+}
+
 void SplitContainerDragger::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPLITTER);
+
+			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+			if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
+				return;
+			}
+			sc->_compute_split_offset(true);
+			DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->split_offset);
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_dec));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_inc));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &SplitContainerDragger::_accessibility_action_set_value));
+		} break;
+
 		case NOTIFICATION_MOUSE_ENTER: {
 			mouse_inside = true;
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
@@ -124,6 +175,10 @@ void SplitContainerDragger::_notification(int p_what) {
 	}
 }
 
+SplitContainerDragger::SplitContainerDragger() {
+	set_focus_mode(FOCUS_ACCESSIBILITY);
+}
+
 Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisibilityMode p_visibility_mode) const {
 	int idx = 0;
 	for (int i = 0; i < get_child_count(false); i++) {

+ 6 - 0
scene/gui/split_container.h

@@ -41,6 +41,10 @@ protected:
 	void _notification(int p_what);
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
+	void _accessibility_action_inc(const Variant &p_data);
+	void _accessibility_action_dec(const Variant &p_data);
+	void _accessibility_action_set_value(const Variant &p_data);
+
 private:
 	bool dragging = false;
 	int drag_from = 0;
@@ -49,6 +53,8 @@ private:
 
 public:
 	virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
+
+	SplitContainerDragger();
 };
 
 class SplitContainer : public Container {

+ 115 - 2
scene/gui/tab_bar.cpp

@@ -351,6 +351,35 @@ void TabBar::_shape(int p_tab) {
 	tabs.write[p_tab].text_buf->add_string(atr(tabs[p_tab].text), theme_cache.font, theme_cache.font_size, tabs[p_tab].language);
 }
 
+RID TabBar::get_tab_accessibility_element(int p_tab) const {
+	RID ae = get_accessibility_element();
+	ERR_FAIL_COND_V(ae.is_null(), RID());
+
+	const Tab &item = tabs[p_tab];
+	if (item.accessibility_item_element.is_null()) {
+		item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB);
+		item.accessibility_item_dirty = true;
+	}
+	return item.accessibility_item_element;
+}
+
+RID TabBar::get_focused_accessibility_element() const {
+	if (current == -1) {
+		return get_accessibility_element();
+	} else {
+		const Tab &item = tabs[current];
+		return item.accessibility_item_element;
+	}
+}
+
+void TabBar::_accessibility_action_scroll_into_view(const Variant &p_data, int p_index) {
+	ensure_tab_visible(p_index);
+}
+
+void TabBar::_accessibility_action_focus(const Variant &p_data, int p_index) {
+	set_current_tab(p_index);
+}
+
 void TabBar::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
@@ -383,6 +412,46 @@ void TabBar::_notification(int p_what) {
 			}
 		} break;
 
+		case NOTIFICATION_EXIT_TREE:
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			for (int i = 0; i < tabs.size(); i++) {
+				tabs.write[i].accessibility_item_element = RID();
+			}
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TAB_BAR);
+			DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, tabs.size());
+
+			for (int i = 0; i < tabs.size(); i++) {
+				const Tab &item = tabs[i];
+
+				if (item.accessibility_item_element.is_null()) {
+					item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB);
+					item.accessibility_item_dirty = true;
+				}
+
+				if (item.accessibility_item_dirty) {
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &TabBar::_accessibility_action_scroll_into_view).bind(i));
+					DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &TabBar::_accessibility_action_focus).bind(i));
+
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
+					DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, atr(item.text));
+					DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, i == current);
+					DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
+					DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, item.hidden);
+					DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
+
+					DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(Point2(item.ofs_cache, 0), Size2(item.size_cache, get_size().height)));
+
+					item.accessibility_item_dirty = false;
+				}
+			}
+		} break;
+
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_redraw();
 		} break;
@@ -393,6 +462,7 @@ void TabBar::_notification(int p_what) {
 				_shape(i);
 			}
 
+			queue_accessibility_update();
 			queue_redraw();
 			update_minimum_size();
 
@@ -672,6 +742,15 @@ void TabBar::set_tab_count(int p_count) {
 	}
 
 	ERR_FAIL_COND(p_count < 0);
+
+	if (tabs.size() > p_count) {
+		for (int i = p_count; i < tabs.size(); i++) {
+			if (tabs[i].accessibility_item_element.is_valid()) {
+				DisplayServer::get_singleton()->accessibility_free_element(tabs.write[i].accessibility_item_element);
+				tabs.write[i].accessibility_item_element = RID();
+			}
+		}
+	}
 	tabs.resize(p_count);
 
 	if (p_count == 0) {
@@ -702,6 +781,7 @@ void TabBar::set_tab_count(int p_count) {
 		}
 	}
 
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 	notify_property_list_changed();
@@ -737,6 +817,7 @@ void TabBar::set_current_tab(int p_current) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 
 	emit_signal(SNAME("tab_changed"), p_current);
@@ -785,6 +866,7 @@ void TabBar::set_tab_offset(int p_offset) {
 	ERR_FAIL_INDEX(p_offset, tabs.size());
 	offset = p_offset;
 	_update_cache();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -811,6 +893,7 @@ void TabBar::set_tab_title(int p_tab, const String &p_title) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 }
@@ -823,6 +906,7 @@ String TabBar::get_tab_title(int p_tab) const {
 void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) {
 	ERR_FAIL_INDEX(p_tab, tabs.size());
 	tabs.write[p_tab].tooltip = p_tooltip;
+	queue_accessibility_update();
 }
 
 String TabBar::get_tab_tooltip(int p_tab) const {
@@ -836,7 +920,9 @@ void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_dir
 
 	if (tabs[p_tab].text_direction != p_text_direction) {
 		tabs.write[p_tab].text_direction = p_text_direction;
+
 		_shape(p_tab);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -851,12 +937,14 @@ void TabBar::set_tab_language(int p_tab, const String &p_language) {
 
 	if (tabs[p_tab].language != p_language) {
 		tabs.write[p_tab].language = p_language;
+
 		_shape(p_tab);
 		_update_cache();
 		_ensure_no_over_offset();
 		if (scroll_to_selected) {
 			ensure_tab_visible(current);
 		}
+		queue_accessibility_update();
 		queue_redraw();
 		update_minimum_size();
 	}
@@ -927,6 +1015,7 @@ void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 }
@@ -950,6 +1039,7 @@ void TabBar::set_tab_hidden(int p_tab, bool p_hidden) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 }
@@ -1117,6 +1207,8 @@ void TabBar::_update_cache(bool p_update_hover) {
 				max_drawn_tab--;
 			}
 		}
+
+		tabs.write[i].accessibility_item_dirty = true;
 	}
 
 	missing_right = max_drawn_tab < tabs.size() - 1;
@@ -1171,6 +1263,7 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 
@@ -1189,12 +1282,19 @@ void TabBar::clear_tabs() {
 		return;
 	}
 
+	for (int i = 0; i < tabs.size(); i++) {
+		if (tabs[i].accessibility_item_element.is_valid()) {
+			DisplayServer::get_singleton()->accessibility_free_element(tabs.write[i].accessibility_item_element);
+			tabs.write[i].accessibility_item_element = RID();
+		}
+	}
 	tabs.clear();
 	offset = 0;
 	max_drawn_tab = 0;
 	current = -1;
 	previous = -1;
 
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 	notify_property_list_changed();
@@ -1202,6 +1302,11 @@ void TabBar::clear_tabs() {
 
 void TabBar::remove_tab(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, tabs.size());
+
+	if (tabs[p_idx].accessibility_item_element.is_valid()) {
+		DisplayServer::get_singleton()->accessibility_free_element(tabs.write[p_idx].accessibility_item_element);
+		tabs.write[p_idx].accessibility_item_element = RID();
+	}
 	tabs.remove_at(p_idx);
 
 	bool is_tab_changing = current == p_idx;
@@ -1251,6 +1356,7 @@ void TabBar::remove_tab(int p_idx) {
 		}
 	}
 
+	queue_accessibility_update();
 	queue_redraw();
 	update_minimum_size();
 	notify_property_list_changed();
@@ -1284,7 +1390,7 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
 }
 
 Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_point) {
-	int tab_over = get_tab_idx_at_point(p_point);
+	int tab_over = (p_point == Vector2(INFINITY, INFINITY)) ? current : get_tab_idx_at_point(p_point);
 	if (tab_over < 0) {
 		return Variant();
 	}
@@ -1349,7 +1455,7 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
 
 	if (String(d["type"]) == p_type) {
 		int tab_from_id = d["tab_index"];
-		int hover_now = get_closest_tab_idx_to_point(p_point);
+		int hover_now = (p_point == Vector2(INFINITY, INFINITY)) ? current : get_closest_tab_idx_to_point(p_point);
 		NodePath from_path = d["from_path"];
 		NodePath to_path = get_path();
 
@@ -1407,6 +1513,8 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
 
 void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
 	Tab moving_tab = p_from_tabbar->tabs[p_from_index];
+	moving_tab.accessibility_item_element = RID();
+	moving_tab.accessibility_item_dirty = true;
 	p_from_tabbar->remove_tab(p_from_index);
 	tabs.insert(p_to_index, moving_tab);
 
@@ -1426,6 +1534,7 @@ void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_in
 		queue_redraw();
 	}
 
+	queue_accessibility_update();
 	update_minimum_size();
 }
 
@@ -1515,6 +1624,8 @@ void TabBar::move_tab(int p_from, int p_to) {
 	ERR_FAIL_INDEX(p_to, tabs.size());
 
 	Tab tab_from = tabs[p_from];
+	tab_from.accessibility_item_dirty = true;
+
 	tabs.remove_at(p_from);
 	tabs.insert(p_to, tab_from);
 
@@ -1539,6 +1650,7 @@ void TabBar::move_tab(int p_from, int p_to) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	notify_property_list_changed();
 }
@@ -1957,6 +2069,7 @@ void TabBar::_bind_methods() {
 }
 
 TabBar::TabBar() {
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_size(Size2(get_size().width, get_minimum_size().height));
 	set_focus_mode(FOCUS_ALL);
 	connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));

+ 9 - 0
scene/gui/tab_bar.h

@@ -54,6 +54,9 @@ public:
 
 private:
 	struct Tab {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		String text;
 		String tooltip;
 
@@ -170,6 +173,9 @@ private:
 	void _shape(int p_tab);
 	void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus);
 
+	void _accessibility_action_scroll_into_view(const Variant &p_data, int p_index);
+	void _accessibility_action_focus(const Variant &p_data, int p_index);
+
 protected:
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual String get_tooltip(const Point2 &p_pos) const override;
@@ -188,6 +194,9 @@ protected:
 	void _move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
 
 public:
+	RID get_tab_accessibility_element(int p_tab) const;
+	virtual RID get_focused_accessibility_element() const override;
+
 	Variant _handle_get_drag_data(const String &p_type, const Point2 &p_point);
 	bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const;
 	void _handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback);

+ 47 - 0
scene/gui/tab_container.cpp

@@ -142,6 +142,45 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) {
 
 void TabContainer::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			tab_panels.clear();
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			int tab_index = 0;
+			int tab_cur = tab_bar->get_current_tab();
+			for (int i = 0; i < get_child_count(); i++) {
+				Node *child_node = get_child(i);
+				Window *child_wnd = Object::cast_to<Window>(child_node);
+				if (child_wnd && !child_wnd->is_embedded()) {
+					continue;
+				}
+				if (child_node->is_part_of_edited_scene()) {
+					continue;
+				}
+				Control *control = as_sortable_control(child_node, SortableVisibilityMode::IGNORE);
+				if (!control || control == tab_bar || children_removing.has(control)) {
+					DisplayServer::get_singleton()->accessibility_update_add_child(ae, child_node->get_accessibility_element());
+				} else {
+					if (!tab_panels.has(child_node)) {
+						tab_panels[child_node] = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB_PANEL);
+					}
+					RID panel = tab_panels[child_node];
+					RID tab = tab_bar->get_tab_accessibility_element(tab_index);
+
+					DisplayServer::get_singleton()->accessibility_update_add_related_controls(tab, panel);
+					DisplayServer::get_singleton()->accessibility_update_add_related_labeled_by(panel, tab);
+					DisplayServer::get_singleton()->accessibility_update_set_flag(panel, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, tab_index != tab_cur);
+					DisplayServer::get_singleton()->accessibility_update_add_child(panel, child_node->get_accessibility_element());
+
+					tab_index++;
+				}
+			}
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			// If some nodes happen to be renamed outside the tree, the tab names need to be updated manually.
 			if (get_tab_count() > 0) {
@@ -556,6 +595,7 @@ void TabContainer::add_child_notify(Node *p_child) {
 	if (get_tab_count() == 1) {
 		queue_redraw();
 	}
+	queue_accessibility_update();
 
 	p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
 	p_child->connect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c));
@@ -579,11 +619,17 @@ void TabContainer::move_child_notify(Node *p_child) {
 	}
 
 	_refresh_tab_indices();
+	queue_accessibility_update();
 }
 
 void TabContainer::remove_child_notify(Node *p_child) {
 	Container::remove_child_notify(p_child);
 
+	if (tab_panels.has(p_child)) {
+		DisplayServer::get_singleton()->accessibility_free_element(tab_panels[p_child]);
+		tab_panels.erase(p_child);
+	}
+
 	if (p_child == tab_bar) {
 		return;
 	}
@@ -607,6 +653,7 @@ void TabContainer::remove_child_notify(Node *p_child) {
 	if (get_tab_count() == 0) {
 		queue_redraw();
 	}
+	queue_accessibility_update();
 
 	p_child->remove_meta("_tab_index");
 	p_child->remove_meta("_tab_name");

+ 4 - 0
scene/gui/tab_container.h

@@ -97,6 +97,8 @@ private:
 		int tab_font_size;
 	} theme_cache;
 
+	HashMap<Node *, RID> tab_panels;
+
 	int _get_tab_height() const;
 	Vector<Control *> _get_tab_controls() const;
 	void _on_theme_changed();
@@ -129,6 +131,8 @@ protected:
 	static void _bind_methods();
 
 public:
+	virtual bool accessibility_override_tree_hierarchy() const override { return true; }
+
 	TabBar *get_tab_bar() const;
 
 	int get_tab_idx_at_point(const Point2 &p_point) const;

+ 273 - 9
scene/gui/text_edit.cpp

@@ -224,9 +224,35 @@ _FORCE_INLINE_ const String &TextEdit::Text::get_text_with_ime(int p_line) const
 	}
 }
 
+const Vector<RID> TextEdit::Text::get_accessibility_elements(int p_line) {
+	ERR_FAIL_INDEX_V(p_line, text.size(), Vector<RID>());
+
+	return text[p_line].accessibility_text_root_element;
+}
+
+void TextEdit::Text::update_accessibility(int p_line, RID p_root) {
+	ERR_FAIL_INDEX(p_line, text.size());
+
+	Line &l = text.write[p_line];
+	if (l.accessibility_text_root_element.is_empty()) {
+		for (int i = 0; i < l.data_buf->get_line_count(); i++) {
+			RID rid = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(p_root, l.data_buf->get_line_rid(i), max_line_height, p_line);
+			l.accessibility_text_root_element.push_back(rid);
+		}
+	}
+}
+
 void TextEdit::Text::invalidate_cache(int p_line, bool p_text_changed) {
 	ERR_FAIL_INDEX(p_line, text.size());
 
+	Line &l = text.write[p_line];
+	for (const RID rid : l.accessibility_text_root_element) {
+		if (rid.is_valid()) {
+			DisplayServer::get_singleton()->accessibility_free_element(rid);
+		}
+	}
+	l.accessibility_text_root_element.clear();
+
 	if (font.is_null()) {
 		return; // Not in tree?
 	}
@@ -564,8 +590,173 @@ String TextEdit::Text::get_enabled_word_separators() const {
 ///                            TEXT EDIT                                    ///
 ///////////////////////////////////////////////////////////////////////////////
 
+void TextEdit::_accessibility_action_set_selection(const Variant &p_data) {
+	Dictionary new_selection = p_data;
+	RID sel_start = new_selection["start_element"];
+	Vector2i sel_start_line = DisplayServer::get_singleton()->accessibility_element_get_meta(sel_start);
+	int sel_start_pos = new_selection["start_char"];
+
+	RID sel_end = new_selection["end_element"];
+	Vector2i sel_end_line = DisplayServer::get_singleton()->accessibility_element_get_meta(sel_end);
+	int sel_end_pos = new_selection["end_char"];
+
+	remove_secondary_carets();
+	select(sel_start_line.x, sel_start_pos, sel_end_line.x, sel_end_pos, 0);
+}
+
+void TextEdit::_accessibility_action_replace_selected(const Variant &p_data) {
+	String new_text = p_data;
+	insert_text_at_caret(new_text);
+}
+
+void TextEdit::_accessibility_action_set_value(const Variant &p_data) {
+	String new_text = p_data;
+	set_text(new_text);
+}
+
+void TextEdit::_accessibility_action_menu(const Variant &p_data) {
+	if (context_menu_enabled) {
+		_update_context_menu();
+		adjust_viewport_to_caret();
+		menu->set_position(get_screen_position() + get_caret_draw_pos());
+		menu->reset_size();
+		menu->popup();
+		menu->grab_focus();
+	}
+}
+
+void TextEdit::_accessibility_scroll_down(const Variant &p_data) {
+	v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 4);
+	queue_accessibility_update();
+}
+
+void TextEdit::_accessibility_scroll_left(const Variant &p_data) {
+	h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 4);
+	queue_accessibility_update();
+}
+
+void TextEdit::_accessibility_scroll_right(const Variant &p_data) {
+	h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 4);
+	queue_accessibility_update();
+}
+
+void TextEdit::_accessibility_scroll_up(const Variant &p_data) {
+	v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 4);
+	queue_accessibility_update();
+}
+
+void TextEdit::_accessibility_scroll_set(const Variant &p_data) {
+	const Point2 &pos = p_data;
+	h_scroll->set_value(pos.x);
+	v_scroll->set_value(pos.y);
+	queue_accessibility_update();
+}
+
+void TextEdit::_accessibility_action_scroll_into_view(const Variant &p_data, int p_line, int p_wrap) {
+	double delta = get_scroll_pos_for_line(p_line, p_wrap) - get_v_scroll();
+	if (delta < 0) {
+		_scroll_up(-delta, false);
+	} else {
+		_scroll_down(delta, false);
+	}
+}
+
 void TextEdit::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_EXIT_TREE:
+		case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
+			text.clear_accessibility();
+			accessibility_text_root_element_nl = RID();
+		} break;
+
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_MULTILINE_TEXT_FIELD);
+			if (text.size() == 1 && text[0].is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_placeholder(ae, atr(placeholder_text));
+			}
+			if (!placeholder_text.is_empty() && get_accessibility_name().is_empty()) {
+				DisplayServer::get_singleton()->accessibility_update_set_name(ae, atr(placeholder_text));
+			}
+			DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_READONLY, !editable);
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_TEXT_SELECTION, callable_mp(this, &TextEdit::_accessibility_action_set_selection));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_REPLACE_SELECTED_TEXT, callable_mp(this, &TextEdit::_accessibility_action_replace_selected));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &TextEdit::_accessibility_action_set_value));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &TextEdit::_accessibility_action_menu));
+
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &TextEdit::_accessibility_scroll_down));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &TextEdit::_accessibility_scroll_left));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &TextEdit::_accessibility_scroll_right));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_UP, callable_mp(this, &TextEdit::_accessibility_scroll_up));
+			DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &TextEdit::_accessibility_scroll_set));
+
+			int first_vis_line = get_first_visible_line();
+			int row_height = get_line_height();
+			int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding;
+			Size2 size = get_size();
+			bool rtl = is_layout_rtl();
+			int lines_drawn = 0;
+
+			RID selection_start;
+			RID selection_end;
+
+			for (int i = 0; i < text.size(); i++) {
+				text.update_accessibility(i, ae);
+				const Ref<TextParagraph> &ac_buf = text.get_line_data(i);
+				const Vector<RID> &text_aes = text.get_accessibility_elements(i);
+				for (int j = 0; j < text_aes.size(); j++) {
+					float text_off_x = 0.0;
+					float text_off_y = 0.0;
+					if (!editable) {
+						text_off_x = theme_cache.style_readonly->get_offset().x / 2;
+						text_off_x -= theme_cache.style_normal->get_offset().x / 2;
+						text_off_y = theme_cache.style_readonly->get_offset().y / 2;
+					} else {
+						text_off_y = theme_cache.style_normal->get_offset().y / 2;
+					}
+
+					text_off_y += (lines_drawn + j) * row_height + theme_cache.line_spacing / 2;
+					text_off_y -= (first_vis_line + first_visible_line_wrap_ofs) * row_height;
+					text_off_y -= _get_v_scroll_offset() * row_height;
+
+					int char_margin = xmargin_beg - first_visible_col;
+					if (rtl) {
+						char_margin = size.width - char_margin - ac_buf->get_line_width(j);
+					}
+
+					DisplayServer::get_singleton()->accessibility_update_set_flag(text_aes[j], DisplayServer::AccessibilityFlags::FLAG_HIDDEN, _is_line_hidden(i));
+					Transform2D tr;
+					tr.set_origin(Point2(char_margin + text_off_x, text_off_y));
+					DisplayServer::get_singleton()->accessibility_update_set_transform(text_aes[j], tr);
+					DisplayServer::get_singleton()->accessibility_update_set_name(text_aes[j], vformat(RTR("Line %d"), i));
+					DisplayServer::get_singleton()->accessibility_element_set_meta(text_aes[j], Vector2i(i, j));
+					DisplayServer::get_singleton()->accessibility_update_add_action(text_aes[j], DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &TextEdit::_accessibility_action_scroll_into_view).bind(i, j));
+				}
+				lines_drawn += ac_buf->get_line_count();
+			}
+			if (accessibility_text_root_element_nl.is_null()) {
+				accessibility_text_root_element_nl = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(ae, RID(), get_line_height());
+			}
+
+			// Selection.
+			if (carets.size() > 0) {
+				if (carets[0].selection.active) {
+					int start_wrap = get_line_wrap_index_at_column(carets[0].selection.origin_line, carets[0].selection.origin_column);
+					RID start_rid = text.get_accessibility_elements(carets[0].selection.origin_line)[start_wrap];
+
+					int end_wrap = get_line_wrap_index_at_column(carets[0].line, carets[0].column);
+					RID end_rid = text.get_accessibility_elements(carets[0].line)[end_wrap];
+					DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, start_rid, carets[0].selection.origin_column, end_rid, carets[0].column);
+				} else {
+					int caret_wrap = get_line_wrap_index_at_column(carets[0].line, carets[0].column);
+					RID caret_rid = text.get_accessibility_elements(carets[0].line)[caret_wrap];
+					DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, caret_rid, carets[0].column, caret_rid, carets[0].column);
+				}
+			}
+		} break;
+
 		case NOTIFICATION_POSTINITIALIZE: {
 			_update_caches();
 		} break;
@@ -1694,6 +1885,8 @@ void TextEdit::_notification(int p_what) {
 
 				_update_ime_text();
 				adjust_viewport_to_caret(0);
+
+				queue_accessibility_update();
 				queue_redraw();
 			}
 		} break;
@@ -1864,6 +2057,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
 				if (mb->is_shift_pressed()) {
 					h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
+					queue_accessibility_update();
 				} else if (mb->is_alt_pressed()) {
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					_scroll_up(15 * mb->get_factor(), true);
@@ -1875,6 +2069,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
 				if (mb->is_shift_pressed()) {
 					h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
+					queue_accessibility_update();
 				} else if (mb->is_alt_pressed()) {
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					_scroll_down(15 * mb->get_factor(), true);
@@ -1885,9 +2080,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			}
 			if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
 				h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
+				queue_accessibility_update();
 			}
 			if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
 				h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
+				queue_accessibility_update();
 			}
 
 			if (mb->get_button_index() == MouseButton::LEFT) {
@@ -1961,6 +2158,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 							return;
 						}
 
+						queue_accessibility_update();
+
 						last_dblclk = 0;
 					} else if (!mb->is_shift_pressed()) {
 						if (drag_and_drop_selection_enabled && mouse_over_selection_caret >= 0) {
@@ -2005,6 +2204,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 					last_dblclk = OS::get_singleton()->get_ticks_msec();
 					last_dblclk_pos = mb->get_position();
 				}
+				queue_accessibility_update();
 				queue_redraw();
 			}
 
@@ -2086,6 +2286,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 		if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
 			accept_event(); // Accept event if scroll changed.
 		}
+		queue_accessibility_update();
 
 		return;
 	}
@@ -2417,8 +2618,15 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			return;
 		}
 
+		// Toggle Tab mode.
+		if (k->is_action("ui_focus_mode", true)) {
+			tab_input_mode = !tab_input_mode;
+			accept_event();
+			return;
+		}
+
 		// Handle tab as it has no set unicode value.
-		if (k->is_action("ui_text_indent", true)) {
+		if (tab_input_mode && k->is_action("ui_text_indent", true)) {
 			if (editable) {
 				insert_text_at_caret("\t");
 			}
@@ -2996,6 +3204,7 @@ void TextEdit::_update_caches() {
 		syntax_highlighter->set_text_edit(this);
 	}
 	_clear_syntax_highlighting_cache();
+	queue_accessibility_update();
 }
 
 void TextEdit::_close_ime_window() {
@@ -3039,6 +3248,7 @@ void TextEdit::_update_ime_text() {
 		}
 	}
 	_clear_syntax_highlighting_cache();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -3111,7 +3321,9 @@ bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const
 void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	Control::drop_data(p_point, p_data);
 
-	if (p_data.is_string() && is_editable()) {
+	if (p_point == Vector2(INFINITY, INFINITY)) {
+		insert_text_at_caret(p_data);
+	} else if (p_data.is_string() && is_editable()) {
 		Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
 		int drop_at_line = pos.y;
 		int drop_at_column = pos.x;
@@ -3215,6 +3427,7 @@ String TextEdit::get_tooltip(const Point2 &p_pos) const {
 
 void TextEdit::set_tooltip_request_func(const Callable &p_tooltip_callback) {
 	tooltip_callback = p_tooltip_callback;
+	queue_accessibility_update();
 }
 
 /* Text */
@@ -3261,7 +3474,7 @@ void TextEdit::set_editable(bool p_editable) {
 	}
 
 	editable = p_editable;
-
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -3292,6 +3505,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
 			menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
 			menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
 		}
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -3312,6 +3526,7 @@ void TextEdit::set_language(const String &p_language) {
 		text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
 		text.invalidate_all();
 		_update_placeholder();
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -3326,6 +3541,7 @@ void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParse
 		for (int i = 0; i < text.size(); i++) {
 			text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 		}
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -3343,6 +3559,7 @@ void TextEdit::set_structured_text_bidi_override_options(Array p_args) {
 	for (int i = 0; i < text.size(); i++) {
 		text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 	}
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -3358,6 +3575,7 @@ void TextEdit::set_tab_size(const int p_size) {
 	text.set_tab_size(p_size);
 	text.invalidate_all_lines();
 	_update_placeholder();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -3379,6 +3597,14 @@ bool TextEdit::is_indent_wrapped_lines() const {
 	return text.is_indent_wrapped_lines();
 }
 
+void TextEdit::set_tab_input_mode(bool p_enabled) {
+	tab_input_mode = p_enabled;
+}
+
+bool TextEdit::get_tab_input_mode() const {
+	return tab_input_mode;
+}
+
 // User controls
 void TextEdit::set_overtype_mode_enabled(bool p_enabled) {
 	if (overtype_mode == p_enabled) {
@@ -3508,7 +3734,7 @@ void TextEdit::set_text(const String &p_text) {
 
 	set_caret_line(0);
 	set_caret_column(0);
-
+	queue_accessibility_update();
 	queue_redraw();
 	setting_text = false;
 	emit_signal(SNAME("text_set"));
@@ -3537,6 +3763,7 @@ void TextEdit::set_placeholder(const String &p_text) {
 
 	placeholder_text = p_text;
 	_update_placeholder();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -3773,6 +4000,9 @@ void TextEdit::remove_line_at(int p_line, bool p_move_carets_down) {
 	_offset_carets_after(next_line, next_column, from_line, from_column);
 	end_multicaret_edit();
 	end_complex_operation();
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 
 void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
@@ -4153,6 +4383,7 @@ void TextEdit::start_action(EditAction p_action) {
 void TextEdit::end_action() {
 	if (current_action != EditAction::ACTION_NONE) {
 		pending_action_end = true;
+		queue_accessibility_update();
 	}
 }
 
@@ -4172,6 +4403,8 @@ void TextEdit::begin_complex_operation() {
 void TextEdit::end_complex_operation() {
 	_push_current_op();
 
+	queue_accessibility_update();
+
 	complex_operation_count = MAX(complex_operation_count - 1, 0);
 	if (complex_operation_count > 0) {
 		return;
@@ -4264,6 +4497,7 @@ void TextEdit::undo() {
 		_selection_changed();
 	}
 	adjust_viewport_to_caret();
+	queue_accessibility_update();
 }
 
 void TextEdit::redo() {
@@ -4320,6 +4554,7 @@ void TextEdit::redo() {
 		_selection_changed();
 	}
 	adjust_viewport_to_caret();
+	queue_accessibility_update();
 }
 
 void TextEdit::clear_undo_history() {
@@ -4591,7 +4826,7 @@ Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
 	ERR_FAIL_COND_V(p_column < 0, Rect2i(-1, -1, 0, 0));
 	ERR_FAIL_COND_V(p_column > text[p_line].length(), Rect2i(-1, -1, 0, 0));
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		// The TextEdit is empty.
 		return Rect2i();
 	}
@@ -4841,6 +5076,7 @@ void TextEdit::remove_secondary_carets() {
 	if (drag_caret_index >= 0) {
 		drag_caret_index = -1;
 	}
+	queue_accessibility_update();
 }
 
 int TextEdit::get_caret_count() const {
@@ -5262,6 +5498,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
 	if (caret_moved) {
 		_caret_changed(p_caret);
 	}
+	queue_accessibility_update();
 }
 
 int TextEdit::get_caret_line(int p_caret) const {
@@ -5296,6 +5533,7 @@ void TextEdit::set_caret_column(int p_column, bool p_adjust_viewport, int p_care
 	if (caret_moved) {
 		_caret_changed(p_caret);
 	}
+	queue_accessibility_update();
 }
 
 int TextEdit::get_caret_column(int p_caret) const {
@@ -5384,7 +5622,7 @@ void TextEdit::select_all() {
 		return;
 	}
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 	}
 
@@ -5401,7 +5639,7 @@ void TextEdit::select_word_under_caret(int p_caret) {
 		return;
 	}
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 	}
 
@@ -5446,7 +5684,7 @@ void TextEdit::add_selection_for_next_occurrence() {
 		return;
 	}
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 	}
 
@@ -5489,7 +5727,7 @@ void TextEdit::skip_selection_for_next_occurrence() {
 		return;
 	}
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 	}
 
@@ -5558,6 +5796,9 @@ void TextEdit::select(int p_origin_line, int p_origin_column, int p_caret_line,
 	if (had_selection != activate) {
 		_selection_changed(p_caret);
 	}
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 
 bool TextEdit::has_selection(int p_caret) const {
@@ -5802,6 +6043,9 @@ void TextEdit::deselect(int p_caret) {
 	if (selection_changed) {
 		_selection_changed(p_caret);
 	}
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 
 void TextEdit::delete_selection(int p_caret) {
@@ -5843,6 +6087,7 @@ void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) {
 	if (line_wrapping_mode != p_wrapping_mode) {
 		line_wrapping_mode = p_wrapping_mode;
 		_update_wrap_at_column(true);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -5859,6 +6104,7 @@ void TextEdit::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
 	autowrap_mode = p_mode;
 	if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) {
 		_update_wrap_at_column(true);
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -5964,6 +6210,7 @@ void TextEdit::set_v_scroll(double p_scroll) {
 	if (p_scroll >= max_v_scroll - 1.0) {
 		_scroll_moved(v_scroll->get_value());
 	}
+	queue_accessibility_update();
 }
 
 double TextEdit::get_v_scroll() const {
@@ -5975,6 +6222,7 @@ void TextEdit::set_h_scroll(int p_scroll) {
 		p_scroll = 0;
 	}
 	h_scroll->set_value(p_scroll);
+	queue_accessibility_update();
 }
 
 int TextEdit::get_h_scroll() const {
@@ -6550,6 +6798,7 @@ void TextEdit::set_draw_control_chars(bool p_enabled) {
 		text.set_draw_control_chars(draw_control_chars);
 		text.invalidate_font();
 		_update_placeholder();
+		queue_accessibility_update();
 		queue_redraw();
 	}
 }
@@ -6615,6 +6864,9 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_indent_wrapped_lines", "enabled"), &TextEdit::set_indent_wrapped_lines);
 	ClassDB::bind_method(D_METHOD("is_indent_wrapped_lines"), &TextEdit::is_indent_wrapped_lines);
 
+	ClassDB::bind_method(D_METHOD("set_tab_input_mode", "enabled"), &TextEdit::set_tab_input_mode);
+	ClassDB::bind_method(D_METHOD("get_tab_input_mode"), &TextEdit::get_tab_input_mode);
+
 	// User controls
 	ClassDB::bind_method(D_METHOD("set_overtype_mode_enabled", "enabled"), &TextEdit::set_overtype_mode_enabled);
 	ClassDB::bind_method(D_METHOD("is_overtype_mode_enabled"), &TextEdit::is_overtype_mode_enabled);
@@ -7037,6 +7289,7 @@ void TextEdit::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_wrapped_lines"), "set_indent_wrapped_lines", "is_indent_wrapped_lines");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tab_input_mode"), "set_tab_input_mode", "get_tab_input_mode");
 
 	ADD_GROUP("Scroll", "scroll_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
@@ -7147,6 +7400,7 @@ void TextEdit::_set_hiding_enabled(bool p_enabled) {
 		_unhide_all_lines();
 	}
 	hiding_enabled = p_enabled;
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -7164,6 +7418,7 @@ void TextEdit::_unhide_all_lines() {
 		text.set_hidden(i, false);
 	}
 	_update_scrollbars();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -7181,6 +7436,7 @@ void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) {
 	if (_is_hiding_enabled() || !p_hidden) {
 		text.set_hidden(p_line, p_hidden);
 	}
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -7833,6 +8089,7 @@ void TextEdit::_selection_changed(int p_caret) {
 	}
 
 	_cancel_drag_and_drop_text();
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -8035,6 +8292,7 @@ void TextEdit::_update_wrap_at_column(bool p_force) {
 		first_visible_line_wrap_ofs = 0;
 	}
 	set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs);
+	queue_accessibility_update();
 }
 
 /* Viewport. */
@@ -8151,6 +8409,7 @@ void TextEdit::_scroll_moved(double p_to_val) {
 		first_visible_line = n_line;
 		first_visible_line_wrap_ofs = wi;
 	}
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -8185,6 +8444,7 @@ void TextEdit::_scroll_up(real_t p_delta, bool p_animate) {
 		}
 		if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
 			v_scroll->set_value(target_v_scroll);
+			queue_accessibility_update();
 		} else {
 			scrolling = true;
 			set_physics_process_internal(true);
@@ -8213,6 +8473,7 @@ void TextEdit::_scroll_down(real_t p_delta, bool p_animate) {
 		}
 		if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
 			v_scroll->set_value(target_v_scroll);
+			queue_accessibility_update();
 		} else {
 			scrolling = true;
 			set_physics_process_internal(true);
@@ -8326,6 +8587,8 @@ void TextEdit::_adjust_viewport_to_caret_horizontally(int p_caret, bool p_maximi
 	}
 
 	h_scroll->set_value(first_visible_col);
+
+	queue_accessibility_update();
 	queue_redraw();
 }
 
@@ -8418,6 +8681,7 @@ void TextEdit::_update_gutter_width() {
 	if (get_viewport()) {
 		hovered_gutter = _get_hovered_gutter(get_local_mouse_position());
 	}
+	queue_accessibility_update();
 	queue_redraw();
 }
 

+ 29 - 1
scene/gui/text_edit.h

@@ -144,12 +144,15 @@ private:
 			Color color = Color(1, 1, 1);
 		};
 
+		mutable int64_t next_item_id = 0;
+
 		struct Line {
 			Vector<Gutter> gutters;
 
 			String data;
 			Array bidi_override;
 			Ref<TextParagraph> data_buf;
+			Vector<RID> accessibility_text_root_element;
 
 			String ime_data;
 			Array ime_bidi_override;
@@ -227,6 +230,14 @@ private:
 		BitField<TextServer::LineBreakFlag> get_brk_flags() const;
 		int get_line_wrap_amount(int p_line) const;
 
+		const Vector<RID> get_accessibility_elements(int p_line);
+		void update_accessibility(int p_line, RID p_root);
+		void clear_accessibility() {
+			for (int i = 0; i < text.size(); i++) {
+				text.write[i].accessibility_text_root_element.clear();
+			}
+		}
+
 		Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
 		const Ref<TextParagraph> get_line_data(int p_line) const;
 		float get_indent_offset(int p_line, bool p_rtl) const;
@@ -275,13 +286,14 @@ private:
 
 	/* Text */
 	Text text;
-
 	bool setting_text = false;
 
 	bool alt_start = false;
 	bool alt_start_no_hold = false;
 	uint32_t alt_code = 0;
 
+	bool tab_input_mode = true;
+
 	// Text properties.
 	String ime_text = "";
 	Point2 ime_selection;
@@ -628,6 +640,8 @@ private:
 	bool draw_tabs = false;
 	bool draw_spaces = false;
 
+	RID accessibility_text_root_element_nl;
+
 	/*** Super internal Core API. Everything builds on it. ***/
 	bool text_changed_dirty = false;
 	void _text_changed();
@@ -718,6 +732,17 @@ protected:
 	virtual void _paste_internal(int p_caret);
 	virtual void _paste_primary_clipboard_internal(int p_caret);
 
+	void _accessibility_action_set_selection(const Variant &p_data);
+	void _accessibility_action_replace_selected(const Variant &p_data);
+	void _accessibility_action_set_value(const Variant &p_data);
+	void _accessibility_action_menu(const Variant &p_data);
+	void _accessibility_scroll_down(const Variant &p_data);
+	void _accessibility_scroll_left(const Variant &p_data);
+	void _accessibility_scroll_right(const Variant &p_data);
+	void _accessibility_scroll_up(const Variant &p_data);
+	void _accessibility_scroll_set(const Variant &p_data);
+	void _accessibility_action_scroll_into_view(const Variant &p_data, int p_line, int p_wrap);
+
 	GDVIRTUAL2(_handle_unicode_input, int, int)
 	GDVIRTUAL1(_backspace, int)
 	GDVIRTUAL1(_cut, int)
@@ -765,6 +790,9 @@ public:
 	void set_indent_wrapped_lines(bool p_enabled);
 	bool is_indent_wrapped_lines() const;
 
+	void set_tab_input_mode(bool p_enabled);
+	bool get_tab_input_mode() const;
+
 	// User controls
 	void set_overtype_mode_enabled(bool p_enabled);
 	bool is_overtype_mode_enabled() const;

+ 7 - 0
scene/gui/texture_progress_bar.cpp

@@ -429,6 +429,13 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
 
 void TextureProgressBar::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ACCESSIBILITY_UPDATE: {
+			RID ae = get_accessibility_element();
+			ERR_FAIL_COND(ae.is_null());
+
+			DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PROGRESS_INDICATOR);
+		} break;
+
 		case NOTIFICATION_DRAW: {
 			if (under.is_valid()) {
 				if (nine_patch_stretch) {

+ 41 - 0
scene/gui/tree.compat.inc

@@ -0,0 +1,41 @@
+/**************************************************************************/
+/*  tree.compat.inc                                                       */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void TreeItem::_add_button_bind_compat_76829(int p_column, const Ref<Texture2D> &p_button, int p_id, bool p_disabled, const String &p_tooltip) {
+	add_button(p_column, p_button, p_id, p_disabled, p_tooltip, String());
+}
+
+void TreeItem::_bind_compatibility_methods() {
+	ClassDB::bind_compatibility_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip_text"), &TreeItem::_add_button_bind_compat_76829, DEFVAL(-1), DEFVAL(false), DEFVAL(""));
+}
+
+#endif // DISABLE_DEPRECATED

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff