Browse Source

Base accessibility API.

Pāvels Nadtočajevs 4 months ago
parent
commit
b106dfd4f9
100 changed files with 5162 additions and 137 deletions
  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)^(
   (?x)^(
     .*thirdparty/.*|
     .*thirdparty/.*|
     .*-so_wrap\.(h|c)|
     .*-so_wrap\.(h|c)|
+    .*-dll_wrap\.(h|c)|
+    .*-dylib_wrap\.(h|c)|
     platform/android/java/editor/src/main/java/com/android/.*|
     platform/android/java/editor/src/main/java/com/android/.*|
     platform/android/java/lib/src/com/google/.*
     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/auto_accept_quit", true);
 	GLOBAL_DEF("application/config/quit_on_go_back", 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:
 	// The default window size is tuned to:
 	// - Have a 16:9 aspect ratio,
 	// - Have a 16:9 aspect ratio,
 	// - Have both dimensions divisible by 8 to better play along with video recording,
 	// - 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("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("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_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_get_deadzone", "action"), &InputMap::action_get_deadzone);
 	ClassDB::bind_method(D_METHOD("action_add_event", "action", "event"), &InputMap::action_add_event);
 	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);
 	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) {
 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));
 	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_cut",                                        TTRC("Cut") },
     { "ui_copy",                                       TTRC("Copy") },
     { "ui_copy",                                       TTRC("Copy") },
     { "ui_paste",                                      TTRC("Paste") },
     { "ui_paste",                                      TTRC("Paste") },
+	{ "ui_focus_mode",                                 TTRC("Toggle Tab Focus Mode") },
     { "ui_undo",                                       TTRC("Undo") },
     { "ui_undo",                                       TTRC("Undo") },
     { "ui_redo",                                       TTRC("Redo") },
     { "ui_redo",                                       TTRC("Redo") },
     { "ui_text_completion_query",                      TTRC("Completion Query") },
     { "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_text_submit",                                TTRC("Submit Text") },
     { "ui_graph_duplicate",                            TTRC("Duplicate Nodes") },
     { "ui_graph_duplicate",                            TTRC("Duplicate Nodes") },
     { "ui_graph_delete",                               TTRC("Delete 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_up_one_level",                    TTRC("Go Up One Level") },
     { "ui_filedialog_refresh",                         TTRC("Refresh") },
     { "ui_filedialog_refresh",                         TTRC("Refresh") },
     { "ui_filedialog_show_hidden",                     TTRC("Show Hidden") },
     { "ui_filedialog_show_hidden",                     TTRC("Show Hidden") },
     { "ui_swap_input_direction ",                      TTRC("Swap Input Direction") },
     { "ui_swap_input_direction ",                      TTRC("Swap Input Direction") },
     { "ui_unicode_start",                              TTRC("Start Unicode Character Input") },
     { "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 */
 	/* clang-format on */
 };
 };
@@ -488,6 +513,9 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	inputs.push_back(InputEventKey::create_reference(Key::END));
 	inputs.push_back(InputEventKey::create_reference(Key::END));
 	default_builtin_cache.insert("ui_end", inputs);
 	default_builtin_cache.insert("ui_end", inputs);
 
 
+	inputs = List<Ref<InputEvent>>();
+	default_builtin_cache.insert("ui_accessibility_drag_and_drop", inputs);
+
 	// ///// UI basic Shortcuts /////
 	// ///// UI basic Shortcuts /////
 
 
 	inputs = List<Ref<InputEvent>>();
 	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));
 	inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::CMD_OR_CTRL));
 	default_builtin_cache.insert("ui_copy", inputs);
 	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 = List<Ref<InputEvent>>();
 	inputs.push_back(InputEventKey::create_reference(Key::V | KeyModifierMask::CMD_OR_CTRL));
 	inputs.push_back(InputEventKey::create_reference(Key::V | KeyModifierMask::CMD_OR_CTRL));
 	inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::SHIFT));
 	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));
 	inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE));
 	default_builtin_cache.insert("ui_graph_delete", inputs);
 	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 /////
 	// ///// UI File Dialog Shortcuts /////
 	inputs = List<Ref<InputEvent>>();
 	inputs = List<Ref<InputEvent>>();
 	inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE));
 	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 add_action(const StringName &p_action, float p_deadzone = DEFAULT_DEADZONE);
 	void erase_action(const StringName &p_action);
 	void erase_action(const StringName &p_action);
 
 
+	String get_action_description(const StringName &p_action) const;
+
 	float action_get_deadzone(const StringName &p_action);
 	float action_get_deadzone(const StringName &p_action);
 	void action_set_deadzone(const StringName &p_action, float p_deadzone);
 	void action_set_deadzone(const StringName &p_action, float p_deadzone);
 	void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event);
 	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;
 	return result;
 }
 }
 
 
-CharString String::utf8() const {
+CharString String::utf8(Vector<uint8_t> *r_ch_length_map) const {
 	int l = length();
 	int l = length();
 	if (!l) {
 	if (!l) {
 		return CharString();
 		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);
 	const char32_t *d = &operator[](0);
 	int fl = 0;
 	int fl = 0;
 	for (int i = 0; i < l; i++) {
 	for (int i = 0; i < l; i++) {
 		uint32_t c = d[i];
 		uint32_t c = d[i];
+		int ch_w = 1;
 		if (c <= 0x7f) { // 7 bits.
 		if (c <= 0x7f) { // 7 bits.
-			fl += 1;
+			ch_w = 1;
 		} else if (c <= 0x7ff) { // 11 bits
 		} else if (c <= 0x7ff) { // 11 bits
-			fl += 2;
+			ch_w = 2;
 		} else if (c <= 0xffff) { // 16 bits
 		} else if (c <= 0xffff) { // 16 bits
-			fl += 3;
+			ch_w = 3;
 		} else if (c <= 0x001fffff) { // 21 bits
 		} else if (c <= 0x001fffff) { // 21 bits
-			fl += 4;
+			ch_w = 4;
 		} else if (c <= 0x03ffffff) { // 26 bits
 		} else if (c <= 0x03ffffff) { // 26 bits
-			fl += 5;
+			ch_w = 5;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 		} else if (c <= 0x7fffffff) { // 31 bits
 		} else if (c <= 0x7fffffff) { // 31 bits
-			fl += 6;
+			ch_w = 6;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 			print_unicode_error(vformat("Invalid unicode codepoint (%x)", c));
 		} else {
 		} else {
-			fl += 1;
+			ch_w = 1;
 			print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as UTF-8", c), true);
 			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;
 	CharString utf8s;

+ 1 - 1
core/string/ustring.h

@@ -511,7 +511,7 @@ public:
 		return string;
 		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 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) {
 	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);
 		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>
 		<link title="All GUI Demos">https://github.com/godotengine/godot-demo-projects/tree/master/gui</link>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<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">
 		<method name="_can_drop_data" qualifiers="virtual const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="at_position" type="Vector2" />
 			<param index="0" name="at_position" type="Vector2" />
@@ -31,6 +37,7 @@
 			<description>
 			<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.
 				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].
 				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]
 				[codeblocks]
 				[gdscript]
 				[gdscript]
 				func _can_drop_data(position, data):
 				func _can_drop_data(position, data):
@@ -55,6 +62,7 @@
 			<param index="1" name="data" type="Variant" />
 			<param index="1" name="data" type="Variant" />
 			<description>
 			<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.
 				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]
 				[codeblocks]
 				[gdscript]
 				[gdscript]
 				func _can_drop_data(position, data):
 				func _can_drop_data(position, data):
@@ -83,6 +91,7 @@
 			<description>
 			<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].
 				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.
 				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]
 				[codeblocks]
 				[gdscript]
 				[gdscript]
 				func _get_drag_data(position):
 				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.
 				[b]Note:[/b] This does not affect the methods in [Input], only the way events are propagated.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="add_theme_color_override">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="name" type="StringName" />
 			<param index="0" name="name" type="StringName" />
@@ -1174,6 +1195,9 @@
 		<constant name="FOCUS_ALL" value="2" enum="FocusMode">
 		<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].
 			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>
+		<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">
 		<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.
 			Inherits the associated behavior from the control's parent. This is the default for any newly created control.
 		</constant>
 		</constant>

+ 894 - 0
doc/classes/DisplayServer.xml

@@ -10,6 +10,630 @@
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<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">
 		<method name="beep" qualifiers="const">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>
@@ -1969,6 +2593,276 @@
 		<constant name="FEATURE_SELF_FITTING_WINDOWS" value="33" enum="Feature">
 		<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.
 			Display server automatically fits popups according to the screen boundaries. Window nodes should not attempt to do that themselves.
 		</constant>
 		</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">
 		<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
 			Makes the mouse cursor visible if it is hidden.
 			Makes the mouse cursor visible if it is hidden.
 		</constant>
 		</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">
 		<member name="snapping_enabled" type="bool" setter="set_snapping_enabled" getter="is_snapping_enabled" default="true">
 			If [code]true[/code], enables snapping.
 			If [code]true[/code], enables snapping.
 		</member>
 		</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">
 		<member name="zoom" type="float" setter="set_zoom" getter="get_zoom" default="1.0">
 			The current zoom value.
 			The current zoom value.
 		</member>
 		</member>
@@ -603,5 +606,8 @@
 		<theme_item name="panel" data_type="style" type="StyleBox">
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			The background drawn under the grid.
 			The background drawn under the grid.
 		</theme_item>
 		</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>
 	</theme_items>
 </class>
 </class>

+ 7 - 0
doc/classes/GraphNode.xml

@@ -267,6 +267,7 @@
 		</method>
 		</method>
 	</methods>
 	</methods>
 	<members>
 	<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">
 		<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].
 			If [code]true[/code], you can connect ports with different types, even if the connection was not explicitly allowed in the parent [GraphEdit].
 		</member>
 		</member>
@@ -299,12 +300,18 @@
 		<theme_item name="panel" data_type="style" type="StyleBox">
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			The default background for the slot area of the [GraphNode].
 			The default background for the slot area of the [GraphNode].
 		</theme_item>
 		</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">
 		<theme_item name="panel_selected" data_type="style" type="StyleBox">
 			The [StyleBox] used for the slot area when selected.
 			The [StyleBox] used for the slot area when selected.
 		</theme_item>
 		</theme_item>
 		<theme_item name="slot" data_type="style" type="StyleBox">
 		<theme_item name="slot" data_type="style" type="StyleBox">
 			The [StyleBox] used for each slot of the [GraphNode].
 			The [StyleBox] used for each slot of the [GraphNode].
 		</theme_item>
 		</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">
 		<theme_item name="titlebar" data_type="style" type="StyleBox">
 			The [StyleBox] used for the title bar of the [GraphNode].
 			The [StyleBox] used for the title bar of the [GraphNode].
 		</theme_item>
 		</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.
 				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>
 			</description>
 		</method>
 		</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">
 		<method name="get_actions">
 			<return type="StringName[]" />
 			<return type="StringName[]" />
 			<description>
 			<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;">
 		<member name="ellipsis_char" type="String" setter="set_ellipsis_char" getter="get_ellipsis_char" default="&quot;…&quot;">
 			Ellipsis character used for text clipping.
 			Ellipsis character used for text clipping.
 		</member>
 		</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">
 		<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.
 			Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
 		</member>
 		</member>
@@ -153,6 +154,9 @@
 		<theme_item name="font_size" data_type="font_size" type="int">
 		<theme_item name="font_size" data_type="font_size" type="int">
 			Font size of the [Label]'s text.
 			Font size of the [Label]'s text.
 		</theme_item>
 		</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">
 		<theme_item name="normal" data_type="style" type="StyleBox">
 			Background [StyleBox] for the [Label].
 			Background [StyleBox] for the [Label].
 		</theme_item>
 		</theme_item>

+ 1 - 1
doc/classes/LinkButton.xml

@@ -10,7 +10,7 @@
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>
 	<members>
 	<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;">
 		<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.
 			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
 		</member>
 		</member>

+ 1 - 0
doc/classes/MenuBar.xml

@@ -100,6 +100,7 @@
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
 			Flat [MenuBar] don't display item decoration.
 			Flat [MenuBar] don't display item decoration.
 		</member>
 		</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;">
 		<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.
 			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
 		</member>
 		</member>

+ 1 - 1
doc/classes/MenuButton.xml

@@ -34,7 +34,7 @@
 	<members>
 	<members>
 		<member name="action_mode" type="int" setter="set_action_mode" getter="get_action_mode" overrides="BaseButton" enum="BaseButton.ActionMode" default="0" />
 		<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="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">
 		<member name="item_count" type="int" setter="set_item_count" getter="get_item_count" default="0">
 			The number of items currently in the list.
 			The number of items currently in the list.
 		</member>
 		</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].
 				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>
 			</description>
 		</method>
 		</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">
 		<method name="_get_configuration_warnings" qualifiers="virtual const">
 			<return type="PackedStringArray" />
 			<return type="PackedStringArray" />
 			<description>
 			<description>
@@ -56,6 +70,12 @@
 				[/codeblock]
 				[/codeblock]
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="_input" qualifiers="virtual">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="event" type="InputEvent" />
 			<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]).
 				[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>
 			</description>
 		</method>
 		</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">
 		<method name="get_child" qualifiers="const">
 			<return type="Node" />
 			<return type="Node" />
 			<param index="0" name="idx" type="int" />
 			<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.
 				Calls [method Object.notification] with [param what] on this node and all of its children, recursively.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="queue_free" keywords="delete, remove, kill, die">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>
@@ -994,6 +1027,27 @@
 		</method>
 		</method>
 	</methods>
 	</methods>
 	<members>
 	<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">
 		<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.
 			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].
 			[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">
 		<constant name="NOTIFICATION_TEXT_SERVER_CHANGED" value="2018">
 			Notification received when the [TextServer] is changed.
 			Notification received when the [TextServer] is changed.
 		</constant>
 		</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">
 		<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.
 			Inherits [member process_mode] from the node's parent. This is the default for any newly created node.
 		</constant>
 		</constant>

+ 32 - 0
doc/classes/ProjectSettings.xml

@@ -235,6 +235,16 @@
 		</method>
 		</method>
 	</methods>
 	</methods>
 	<members>
 	<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">
 		<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.
 			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>
 		</member>
@@ -1191,6 +1201,10 @@
 			Default [InputEventAction] to confirm a focused button, menu or list item, or validate input.
 			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.
 			[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>
+		<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="">
 		<member name="input/ui_cancel" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to discard a modal or pending input.
 			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.
 			[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].
 			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.
 			[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>
+		<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="">
 		<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].
 			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.
 			[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].
 			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.
 			[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>
+		<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="">
 		<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.
 			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.
 			[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="7" name="pad" type="bool" default="false" />
 			<param index="8" name="tooltip" type="String" default="&quot;&quot;" />
 			<param index="8" name="tooltip" type="String" default="&quot;&quot;" />
 			<param index="9" name="size_in_percent" type="bool" default="false" />
 			<param index="9" name="size_in_percent" type="bool" default="false" />
+			<param index="10" name="alt_text" type="String" default="&quot;&quot;" />
 			<description>
 			<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.
 				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.
 				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].
 				[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 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.
 				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>
 			</description>
 		</method>
 		</method>
 		<method name="add_text">
 		<method name="add_text">
@@ -517,8 +519,9 @@
 			<param index="0" name="columns" type="int" />
 			<param index="0" name="columns" type="int" />
 			<param index="1" name="inline_align" type="int" enum="InlineAlignment" default="0" />
 			<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="2" name="align_to_row" type="int" default="-1" />
+			<param index="3" name="name" type="String" default="&quot;&quot;" />
 			<description>
 			<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>
 			</description>
 		</method>
 		</method>
 		<method name="push_underline">
 		<method name="push_underline">
@@ -612,6 +615,14 @@
 				If [param expand] is [code]false[/code], the column will not contribute to the total ratio.
 				If [param expand] is [code]false[/code], the column will not contribute to the total ratio.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="update_image">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="key" type="Variant" />
 			<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">
 		<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].
 			If [code]true[/code], the label's minimum size will be automatically updated to fit its content, matching the behavior of [Label].
 		</member>
 		</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">
 		<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].
 			If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
 		</member>
 		</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.
 				Returns [code]true[/code] if a node added to the given group [param name] exists in the tree.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="notify_group">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="group" type="StringName" />
 			<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">
 		<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.
 			Overrides the step used when clicking increment and decrement buttons or when using arrow keys when the [ScrollBar] is focused.
 		</member>
 		</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" />
 		<member name="step" type="float" setter="set_step" getter="get_step" overrides="Range" default="0.0" />
 	</members>
 	</members>
 	<signals>
 	<signals>

+ 3 - 0
doc/classes/TextEdit.xml

@@ -1380,6 +1380,9 @@
 			The syntax highlighter to use.
 			The syntax highlighter to use.
 			[b]Note:[/b] A [SyntaxHighlighter] instance should not be used across multiple [TextEdit] nodes.
 			[b]Note:[/b] A [SyntaxHighlighter] instance should not be used across multiple [TextEdit] nodes.
 		</member>
 		</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;">
 		<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
 			String value of the [TextEdit].
 			String value of the [TextEdit].
 		</member>
 		</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.
 				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>
 			</description>
 		</method>
 		</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">
 		<method name="get_line_ascent" qualifiers="const">
 			<return type="float" />
 			<return type="float" />
 			<description>
 			<description>

+ 12 - 0
doc/classes/TextParagraph.xml

@@ -122,6 +122,12 @@
 				Returns drop cap bounding box size.
 				Returns drop cap bounding box size.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="get_line_ascent" qualifiers="const">
 			<return type="float" />
 			<return type="float" />
 			<param index="0" name="line" type="int" />
 			<param index="0" name="line" type="int" />
@@ -205,6 +211,12 @@
 				Returns the size of the bounding box of the paragraph, without line breaks.
 				Returns the size of the bounding box of the paragraph, without line breaks.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="get_rid" qualifiers="const">
 			<return type="RID" />
 			<return type="RID" />
 			<description>
 			<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.
 				[b]Note:[/b] This function is used by during project export, to include TextServer database.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="shaped_get_span_count" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />
@@ -1238,6 +1301,29 @@
 				Returns text span metadata.
 				Returns text span metadata.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="shaped_set_span_update_font">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="shaped" type="RID" />
 			<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.
 				Saves optional TextServer database (e.g. ICU break iterators and dictionaries) to the file.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="_shaped_get_span_count" qualifiers="virtual const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />
@@ -1349,6 +1420,32 @@
 				Returns text span metadata.
 				Returns text span metadata.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="_shaped_set_span_update_font" qualifiers="virtual">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="shaped" type="RID" />
 			<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="2" name="id" type="int" default="-1" />
 			<param index="3" name="disabled" type="bool" default="false" />
 			<param index="3" name="disabled" type="bool" default="false" />
 			<param index="4" name="tooltip_text" type="String" default="&quot;&quot;" />
 			<param index="4" name="tooltip_text" type="String" default="&quot;&quot;" />
+			<param index="5" name="alt_text" type="String" default="&quot;&quot;" />
 			<description>
 			<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>
 			</description>
 		</method>
 		</method>
 		<method name="add_child">
 		<method name="add_child">
@@ -79,6 +80,13 @@
 				Removes the button at index [param button_index] in column [param column].
 				Removes the button at index [param button_index] in column [param column].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="get_auto_translate_mode" qualifiers="const">
 			<return type="int" enum="Node.AutoTranslateMode" />
 			<return type="int" enum="Node.AutoTranslateMode" />
 			<param index="0" name="column" type="int" />
 			<param index="0" name="column" type="int" />
@@ -506,6 +514,14 @@
 				Selects the given [param column].
 				Selects the given [param column].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_auto_translate_mode">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="column" type="int" />
 			<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].
 				Sets the given column's button [Texture2D] at index [param button_index] to [param button].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_button_color">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="column" type="int" />
 			<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].
 				Returns the drag data from the GUI, that was previously returned by [method Control._get_drag_data].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="gui_get_focus_owner" qualifiers="const">
 			<return type="Control" />
 			<return type="Control" />
 			<description>
 			<description>
@@ -180,6 +186,13 @@
 				Removes the focus from the currently focused [Control] within this viewport. If no [Control] has the focus, does nothing.
 				Removes the focus from the currently focused [Control] within this viewport. If no [Control] has the focus, does nothing.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="is_input_handled" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>

+ 6 - 0
doc/classes/Window.xml

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

+ 49 - 6
main/main.cpp

@@ -202,6 +202,8 @@ static int audio_driver_idx = -1;
 
 
 // Engine config/tools
 // 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 single_window = false;
 static bool editor = false;
 static bool editor = false;
 static bool project_manager = 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");
 	print_help_option("--xr-mode <mode>", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n");
 #endif
 #endif
 	print_help_option("--wid <window_id>", "Request parented to window.\n");
 	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_title("Debug options");
 	print_help_option("-d, --debug", "Debug (local stdout debugger).\n");
 	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
 		} else if (arg == "--single-window") { // force single window
 
 
 			single_window = true;
 			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
 		} else if (arg == "-t" || arg == "--always-on-top") { // force always-on-top window
 
 
 			init_always_on_top = true;
 			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";
 		default_renderer_mobile = "gl_compatibility";
 	}
 	}
 #endif
 #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.is_empty()) {
 		if (rendering_method != "forward_plus" &&
 		if (rendering_method != "forward_plus" &&
 				rendering_method != "mobile" &&
 				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 ",
 			OS::get_singleton()->print("Unknown rendering method '%s', aborting.\nValid options are ",
 					rendering_method.utf8().get_data());
 					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.
 		// Set a default renderer if none selected. Try to choose one that matches the driver.
 		if (rendering_method.is_empty()) {
 		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";
 				rendering_method = "gl_compatibility";
 			} else {
 			} else {
 				rendering_method = "forward_plus";
 				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");
 			available_drivers.push_back("opengl3_es");
 		}
 		}
 #endif
 #endif
+		if (rendering_method == "dummy") {
+			available_drivers.push_back("dummy");
+		}
 		if (available_drivers.is_empty()) {
 		if (available_drivers.is_empty()) {
 			OS::get_singleton()->print("Unknown renderer name '%s', aborting.\n", rendering_method.utf8().get_data());
 			OS::get_singleton()->print("Unknown renderer name '%s', aborting.\n", rendering_method.utf8().get_data());
 			goto error;
 			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_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");
 			rendering_driver = GLOBAL_GET("rendering/gl_compatibility/driver");
 		} else {
 		} else {
 			rendering_driver = GLOBAL_GET("rendering/rendering_device/driver");
 			rendering_driver = GLOBAL_GET("rendering/rendering_device/driver");
@@ -3095,6 +3128,11 @@ Error Main::setup2(bool p_show_boot_logo) {
 		}
 		}
 #endif
 #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()
 		// rendering_driver now held in static global String in main and initialized in setup()
 		Error err;
 		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);
 		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;
 		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
 #ifdef TOOLS_ENABLED
 	if (auto_build_solutions) {
 	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
 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.
 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->uthk = 0.0;
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs_logical.clear();
 	p_shaped->glyphs_logical.clear();
+	p_shaped->runs.clear();
+	p_shaped->runs_dirty = true;
 	p_shaped->overrun_trim_data = TrimData();
 	p_shaped->overrun_trim_data = TrimData();
 	p_shaped->utf16 = Char16String();
 	p_shaped->utf16 = Char16String();
 	for (int i = 0; i < p_shaped->bidi_iter.size(); i++) {
 	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) {
 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);
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL(sd);
 	ERR_FAIL_NULL(sd);
@@ -4575,6 +4784,13 @@ bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Vari
 	return true;
 	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) {
 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);
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
 	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->sort_valid = false;
 	p_new_sd->upos = p_sd->upos;
 	p_new_sd->upos = p_sd->upos;
 	p_new_sd->uthk = p_sd->uthk;
 	p_new_sd->uthk = p_sd->uthk;
+	p_new_sd->runs.clear();
+	p_new_sd->runs_dirty = true;
 
 
 	if (p_length > 0) {
 	if (p_length > 0) {
 		p_new_sd->text = p_sd->text.substr(p_start - p_sd->start, p_length);
 		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 {
 	} else {
 		bidi_ranges = sd->bidi_override;
 		bidi_ranges = sd->bidi_override;
 	}
 	}
+	sd->runs.clear();
+	sd->runs_dirty = true;
 
 
 	for (int ov = 0; ov < bidi_ranges.size(); ov++) {
 	for (int ov = 0; ov < bidi_ranges.size(); ov++) {
 		// Create BiDi iterator.
 		// 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;
 		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 {
 	struct ShapedTextDataAdvanced {
 		Mutex mutex;
 		Mutex mutex;
 
 
@@ -489,6 +497,9 @@ class TextServerAdvanced : public TextServerExtension {
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int last_span = 0;
 		int last_span = 0;
 
 
+		Vector<TextRun> runs;
+		bool runs_dirty = true;
+
 		struct EmbeddedObject {
 		struct EmbeddedObject {
 			int start = -1;
 			int start = -1;
 			int end = -1;
 			int end = -1;
@@ -664,6 +675,7 @@ class TextServerAdvanced : public TextServerExtension {
 	mutable HashMap<String, PackedByteArray> system_font_data;
 	mutable HashMap<String, PackedByteArray> system_font_data;
 
 
 	void _update_chars(ShapedTextDataAdvanced *p_sd) const;
 	void _update_chars(ShapedTextDataAdvanced *p_sd) const;
+	void _generate_runs(ShapedTextDataAdvanced *p_sd) const;
 	void _realign(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 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;
 	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 &);
 	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);
 	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);
 	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 &);
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_embedded_object, 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 &);
 	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);
 	MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
 	MODBIND1RC(RID, shaped_text_get_parent, const RID &);
 	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->uthk = 0.0;
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs.clear();
 	p_shaped->glyphs_logical.clear();
 	p_shaped->glyphs_logical.clear();
+	p_shaped->runs.clear();
+	p_shaped->runs_dirty = true;
 }
 }
 
 
 void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
 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) {
 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);
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL(sd);
 	ERR_FAIL_NULL(sd);
@@ -3450,6 +3658,13 @@ bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Vari
 	return true;
 	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) {
 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);
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
 	ERR_FAIL_NULL_V(sd, false);
@@ -4385,6 +4600,8 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
 	sd->descent = 0.0;
 	sd->descent = 0.0;
 	sd->width = 0.0;
 	sd->width = 0.0;
 	sd->glyphs.clear();
 	sd->glyphs.clear();
+	sd->runs.clear();
+	sd->runs_dirty = true;
 
 
 	if (sd->text.length() == 0) {
 	if (sd->text.length() == 0) {
 		sd->valid.set();
 		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;
 		Vector<Glyph> ellipsis_glyph_buf;
 	};
 	};
 
 
+	struct TextRun {
+		Vector2i range;
+		RID font_rid;
+		int font_size = 0;
+		int64_t span_index = -1;
+	};
+
 	struct ShapedTextDataFallback {
 	struct ShapedTextDataFallback {
 		Mutex mutex;
 		Mutex mutex;
 
 
@@ -431,6 +438,9 @@ class TextServerFallback : public TextServerExtension {
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int first_span = 0; // First span in the parent ShapedTextData.
 		int last_span = 0;
 		int last_span = 0;
 
 
+		Vector<TextRun> runs;
+		bool runs_dirty = true;
+
 		struct EmbeddedObject {
 		struct EmbeddedObject {
 			int start = -1;
 			int start = -1;
 			int end = -1;
 			int end = -1;
@@ -575,6 +585,7 @@ class TextServerFallback : public TextServerExtension {
 	mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
 	mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
 	mutable HashMap<String, PackedByteArray> system_font_data;
 	mutable HashMap<String, PackedByteArray> system_font_data;
 
 
+	void _generate_runs(ShapedTextDataFallback *p_sd) const;
 	void _realign(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);
 	_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 &);
 	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);
 	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);
 	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 &);
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
 	MODBIND2RC(Variant, shaped_get_span_embedded_object, 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 &);
 	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);
 	MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
 	MODBIND1RC(RID, shaped_text_get_parent, const RID &);
 	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) {
 void AnimatedSprite2D::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_READY: {
 			if (!Engine::get_singleton()->is_editor_hint() && frames.is_valid() && frames->has_animation(autoplay)) {
 			if (!Engine::get_singleton()->is_editor_hint() && frames.is_valid() && frames->has_animation(autoplay)) {
 				play(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."));
 		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."));
 		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) {
 void Node2D::_notification(int p_notification) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			ERR_MAIN_THREAD_GUARD;
 			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;
 	return shape_centered;
 }
 }
 
 
+void TouchScreenButton::_accessibility_action_click(const Variant &p_data) {
+	_press(0);
+	_release();
+}
+
 void TouchScreenButton::_notification(int p_what) {
 void TouchScreenButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			if (!is_inside_tree()) {
 			if (!is_inside_tree()) {
 				return;
 				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);
 	bool _set(const StringName &p_name, const Variant &p_value);
 #endif // DISABLE_DEPRECATED
 #endif // DISABLE_DEPRECATED
 
 
+	void _accessibility_action_click(const Variant &p_data);
+
 public:
 public:
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 	virtual Rect2 _edit_get_rect() const override;
 	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) {
 void Sprite2D::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			if (texture.is_null()) {
 			if (texture.is_null()) {
 				return;
 				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 Decal::get_configuration_warnings() const {
 	PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
 	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."));
 		warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile renderers."));
 		return warnings;
 		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)) {
 		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."));
 			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."));
 			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."));
 		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."));
 		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."));
 		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."));
 		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."));
 		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) {
 void Node3D::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			ERR_MAIN_THREAD_GUARD;
 			ERR_MAIN_THREAD_GUARD;
 			ERR_FAIL_NULL(get_tree());
 			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") {
 	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."));
 		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()) {
 	} 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."));
 		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"
 #include "servers/audio/audio_stream.h"
 
 
 void AudioStreamPlayer::_notification(int p_what) {
 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) {
 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()) {
 	if (toggle_mode && !button_group->is_allow_unpress()) {
 		status.pressed = true;
 		status.pressed = true;
+		queue_accessibility_update();
 	}
 	}
 
 
 	for (BaseButton *E : button_group->buttons) {
 	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) {
 void BaseButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_MOUSE_ENTER: {
 			status.hovering = true;
 			status.hovering = true;
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_MOUSE_EXIT: {
 		case NOTIFICATION_MOUSE_EXIT: {
 			status.hovering = false;
 			status.hovering = false;
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
 
 
@@ -175,6 +227,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
 				}
 				}
 				_toggled(status.pressed);
 				_toggled(status.pressed);
 				_pressed();
 				_pressed();
+				queue_accessibility_update();
 			}
 			}
 		} else {
 		} else {
 			if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
 			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.press_attempt = false;
 		status.pressing_inside = false;
 		status.pressing_inside = false;
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 }
 }
@@ -247,7 +301,7 @@ void BaseButton::set_pressed_no_signal(bool p_pressed) {
 		return;
 		return;
 	}
 	}
 	status.pressed = p_pressed;
 	status.pressed = p_pressed;
-
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -303,6 +357,7 @@ void BaseButton::set_toggle_mode(bool p_on) {
 	if (!p_on) {
 	if (!p_on) {
 		set_pressed(false);
 		set_pressed(false);
 	}
 	}
+	queue_accessibility_update();
 
 
 	toggle_mode = p_on;
 	toggle_mode = p_on;
 	update_configuration_warnings();
 	update_configuration_warnings();
@@ -313,7 +368,10 @@ bool BaseButton::is_toggle_mode() const {
 }
 }
 
 
 void BaseButton::set_shortcut_in_tooltip(bool p_on) {
 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 {
 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) {
 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 {
 Ref<Shortcut> BaseButton::get_shortcut() const {
@@ -380,7 +441,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
 
 
 			_toggled(status.pressed);
 			_toggled(status.pressed);
 			_pressed();
 			_pressed();
-
+			queue_accessibility_update();
 		} else {
 		} else {
 			_pressed();
 			_pressed();
 		}
 		}
@@ -440,6 +501,7 @@ void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) {
 		button_group->buttons.insert(this);
 		button_group->buttons.insert(this);
 	}
 	}
 
 
+	queue_accessibility_update();
 	queue_redraw(); //checkbox changes to radio if set a buttongroup
 	queue_redraw(); //checkbox changes to radio if set a buttongroup
 	update_configuration_warnings();
 	update_configuration_warnings();
 }
 }

+ 1 - 0
scene/gui/base_button.h

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

+ 22 - 0
scene/gui/button.cpp

@@ -30,6 +30,8 @@
 
 
 #include "button.h"
 #include "button.h"
 
 
+#include "scene/gui/dialogs.h"
+
 #include "scene/theme/theme_db.h"
 #include "scene/theme/theme_db.h"
 
 
 Size2 Button::get_minimum_size() const {
 Size2 Button::get_minimum_size() const {
@@ -185,6 +187,21 @@ Ref<StyleBox> Button::_get_current_stylebox() const {
 
 
 void Button::_notification(int p_what) {
 void Button::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
@@ -194,6 +211,7 @@ void Button::_notification(int p_what) {
 			_shape();
 			_shape();
 
 
 			update_minimum_size();
 			update_minimum_size();
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
 
 
@@ -610,6 +628,7 @@ void Button::set_text(const String &p_text) {
 	xl_text = translated_text;
 	xl_text = translated_text;
 	_shape();
 	_shape();
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 }
 }
@@ -649,6 +668,7 @@ void Button::set_text_direction(Control::TextDirection p_text_direction) {
 	if (text_direction != p_text_direction) {
 	if (text_direction != p_text_direction) {
 		text_direction = p_text_direction;
 		text_direction = p_text_direction;
 		_shape();
 		_shape();
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -661,6 +681,7 @@ void Button::set_language(const String &p_language) {
 	if (language != p_language) {
 	if (language != p_language) {
 		language = p_language;
 		language = p_language;
 		_shape();
 		_shape();
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -738,6 +759,7 @@ bool Button::get_clip_text() const {
 void Button::set_text_alignment(HorizontalAlignment p_alignment) {
 void Button::set_text_alignment(HorizontalAlignment p_alignment) {
 	if (alignment != p_alignment) {
 	if (alignment != p_alignment) {
 		alignment = p_alignment;
 		alignment = p_alignment;
+		queue_accessibility_update();
 		queue_redraw();
 		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) {
 void CheckBox::_notification(int p_what) {
 	switch (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_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_TRANSLATION_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();
 	return get_button_group().is_valid();
 }
 }
 
 

+ 1 - 1
scene/gui/check_box.h

@@ -60,7 +60,7 @@ protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 	static void _bind_methods();
 	static void _bind_methods();
 
 
-	bool is_radio();
+	bool is_radio() const;
 
 
 public:
 public:
 	CheckBox(const String &p_text = String());
 	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) {
 void CheckButton::_notification(int p_what) {
 	switch (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_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 		case NOTIFICATION_TRANSLATION_CHANGED: {

+ 47 - 3
scene/gui/color_picker.cpp

@@ -56,20 +56,31 @@
 
 
 void ColorPicker::_notification(int p_what) {
 void ColorPicker::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			_update_color();
 			_update_color();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_READY: {
 		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)) {
 			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->set_tooltip_text(ETR("Pick a color from the screen."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_native));
 				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()) {
 			} 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->set_tooltip_text(ETR("Pick a color from the screen."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed));
 			} else {
 			} else {
 				// On unsupported platforms, use a legacy method for color picking.
 				// 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->set_tooltip_text(ETR("Pick a color from the application window."));
 				btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy));
 				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++) {
 	for (int i = 0; i < current_slider_count; i++) {
 		labels[i]->set_text(modes[current_mode]->get_slider_label(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_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();
 	slider_theme_modified = modes[current_mode]->apply_theme();
 
 
@@ -726,6 +741,7 @@ void ColorPicker::_update_color(bool p_update_sliders) {
 	}
 	}
 	alpha_slider->queue_redraw();
 	alpha_slider->queue_redraw();
 	updating = false;
 	updating = false;
+	queue_accessibility_update();
 }
 }
 
 
 void ColorPicker::_update_presets() {
 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) {
 void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
 	ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size));
 	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_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);
 	SET_DRAG_FORWARDING_GCDU(btn_preset_new, ColorPicker);
 	btn_preset_new->set_button_group(preset_group);
 	btn_preset_new->set_button_group(preset_group);
 	preset_container->add_child(btn_preset_new);
 	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) {
 void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
 	ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size));
 	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_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);
 	btn_preset_new->set_button_group(recent_preset_group);
 	recent_preset_hbc->add_child(btn_preset_new);
 	recent_preset_hbc->add_child(btn_preset_new);
 	recent_preset_hbc->move_child(btn_preset_new, 0);
 	recent_preset_hbc->move_child(btn_preset_new, 0);
@@ -2000,6 +2018,7 @@ ColorPicker::ColorPicker() {
 
 
 	btn_pick = memnew(Button);
 	btn_pick = memnew(Button);
 	btn_pick->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	btn_pick->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
+	btn_pick->set_accessibility_name(ETR("Pick"));
 	sample_hbc->add_child(btn_pick);
 	sample_hbc->add_child(btn_pick);
 
 
 	sample = memnew(TextureRect);
 	sample = memnew(TextureRect);
@@ -2012,6 +2031,7 @@ ColorPicker::ColorPicker() {
 	btn_shape->set_flat(false);
 	btn_shape->set_flat(false);
 	sample_hbc->add_child(btn_shape);
 	sample_hbc->add_child(btn_shape);
 	btn_shape->set_toggle_mode(true);
 	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_tooltip_text(ETR("Select a picker shape."));
 	btn_shape->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	btn_shape->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	btn_shape->set_focus_mode(FOCUS_ALL);
 	btn_shape->set_focus_mode(FOCUS_ALL);
@@ -2061,6 +2081,7 @@ ColorPicker::ColorPicker() {
 	btn_mode->set_flat(false);
 	btn_mode->set_flat(false);
 	mode_hbc->add_child(btn_mode);
 	mode_hbc->add_child(btn_mode);
 	btn_mode->set_toggle_mode(true);
 	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_tooltip_text(ETR("Select a picker mode."));
 	btn_mode->set_focus_mode(FOCUS_ALL);
 	btn_mode->set_focus_mode(FOCUS_ALL);
 
 
@@ -2101,7 +2122,8 @@ ColorPicker::ColorPicker() {
 	text_type = memnew(Button);
 	text_type = memnew(Button);
 	hex_hbc->add_child(text_type);
 	hex_hbc->add_child(text_type);
 	text_type->set_text("#");
 	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()) {
 	if (Engine::get_singleton()->is_editor_hint()) {
 		text_type->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_text_type_toggled));
 		text_type->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_text_type_toggled));
 	} else {
 	} else {
@@ -2114,6 +2136,7 @@ ColorPicker::ColorPicker() {
 	hex_hbc->add_child(c_text);
 	hex_hbc->add_child(c_text);
 	c_text->set_h_size_flags(SIZE_EXPAND_FILL);
 	c_text->set_h_size_flags(SIZE_EXPAND_FILL);
 	c_text->set_select_all_on_focus(true);
 	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_tooltip_text(ETR("Enter a hex code (\"#ff0000\") or named color (\"red\")."));
 	c_text->set_placeholder(ETR("Hex code or named color"));
 	c_text->set_placeholder(ETR("Hex code or named color"));
 	c_text->connect(SceneStringName(text_submitted), callable_mp(this, &ColorPicker::_html_submitted));
 	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_flat(true);
 	menu_btn->set_focus_mode(FOCUS_ALL);
 	menu_btn->set_focus_mode(FOCUS_ALL);
 	menu_btn->set_tooltip_text(ETR("Show all options available."));
 	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));
 	menu_btn->connect("about_to_popup", callable_mp(this, &ColorPicker::_update_menu_items));
 	palette_box->add_child(menu_btn);
 	palette_box->add_child(menu_btn);
 
 
@@ -2187,6 +2211,7 @@ ColorPicker::ColorPicker() {
 	btn_add_preset = memnew(Button);
 	btn_add_preset = memnew(Button);
 	btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	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_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));
 	btn_add_preset->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_add_preset_pressed));
 	preset_container->add_child(btn_add_preset);
 	preset_container->add_child(btn_add_preset);
 }
 }
@@ -2223,6 +2248,7 @@ void ColorPickerButton::_about_to_popup() {
 
 
 void ColorPickerButton::_color_changed(const Color &p_color) {
 void ColorPickerButton::_color_changed(const Color &p_color) {
 	color = p_color;
 	color = p_color;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	emit_signal(SNAME("color_changed"), color);
 	emit_signal(SNAME("color_changed"), color);
 }
 }
@@ -2269,6 +2295,15 @@ void ColorPickerButton::pressed() {
 
 
 void ColorPickerButton::_notification(int p_what) {
 void ColorPickerButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			const Rect2 r = Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size());
 			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);
 			draw_texture_rect(theme_cache.background_icon, r, true);
@@ -2302,7 +2337,7 @@ void ColorPickerButton::set_pick_color(const Color &p_color) {
 	if (picker) {
 	if (picker) {
 		picker->set_pick_color(p_color);
 		picker->set_pick_color(p_color);
 	}
 	}
-
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2382,6 +2417,14 @@ ColorPickerButton::ColorPickerButton(const String &p_text) :
 
 
 void ColorPresetButton::_notification(int p_what) {
 void ColorPresetButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			const Rect2 r = Rect2(Point2(0, 0), get_size());
 			const Rect2 r = Rect2(Point2(0, 0), get_size());
 			Ref<StyleBox> sb_raw = theme_cache.foreground_style->duplicate();
 			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) {
 void ColorPresetButton::set_preset_color(const Color &p_color) {
 	preset_color = p_color;
 	preset_color = p_color;
+	queue_accessibility_update();
 }
 }
 
 
 Color ColorPresetButton::get_preset_color() const {
 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;
 		return;
 	}
 	}
 	color = p_color;
 	color = p_color;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -44,6 +45,13 @@ Color ColorRect::get_color() const {
 
 
 void ColorRect::_notification(int p_what) {
 void ColorRect::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			draw_rect(Rect2(Point2(), get_size()), color);
 			draw_rect(Rect2(Point2(), get_size()), color);
 		} break;
 		} 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) {
 void Container::_notification(int p_what) {
 	switch (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_RESIZED:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_ENTER_TREE: {
 		case NOTIFICATION_ENTER_TREE: {

+ 187 - 7
scene/gui/control.cpp

@@ -32,8 +32,12 @@
 
 
 #include "container.h"
 #include "container.h"
 #include "core/config/project_settings.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/os/os.h"
 #include "core/string/translation_server.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/gui/scroll_container.h"
 #include "scene/main/canvas_layer.h"
 #include "scene/main/canvas_layer.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
@@ -246,6 +250,27 @@ PackedStringArray Control::get_configuration_warnings() const {
 	return warnings;
 	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 {
 bool Control::is_text_field() const {
 	ERR_READ_THREAD_GUARD_V(false);
 	ERR_READ_THREAD_GUARD_V(false);
 	return false;
 	return false;
@@ -1534,6 +1559,7 @@ void Control::set_scale(const Vector2 &p_scale) {
 	}
 	}
 	queue_redraw();
 	queue_redraw();
 	_notify_transform();
 	_notify_transform();
+	queue_accessibility_update();
 }
 }
 
 
 Vector2 Control::get_scale() const {
 Vector2 Control::get_scale() const {
@@ -1550,6 +1576,7 @@ void Control::set_rotation(real_t p_radians) {
 	data.rotation = p_radians;
 	data.rotation = p_radians;
 	queue_redraw();
 	queue_redraw();
 	_notify_transform();
 	_notify_transform();
+	queue_accessibility_update();
 }
 }
 
 
 void Control::set_rotation_degrees(real_t p_degrees) {
 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;
 	data.pivot_offset = p_pivot;
 	queue_redraw();
 	queue_redraw();
 	_notify_transform();
 	_notify_transform();
+	queue_accessibility_update();
 }
 }
 
 
 Vector2 Control::get_pivot_offset() const {
 Vector2 Control::get_pivot_offset() const {
@@ -1749,6 +1777,8 @@ void Control::_size_changed() {
 		if (pos_changed && !size_changed) {
 		if (pos_changed && !size_changed) {
 			_update_canvas_item_transform();
 			_update_canvas_item_transform();
 		}
 		}
+
+		queue_accessibility_update();
 	} else if (pos_changed) {
 	} else if (pos_changed) {
 		_notify_transform();
 		_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(!is_inside_tree());
 	ERR_FAIL_COND(p_data.get_type() == Variant::NIL);
 	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) {
 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) {
 void Control::set_focus_mode(FocusMode p_focus_mode) {
 	ERR_MAIN_THREAD_GUARD;
 	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()) {
 	if (is_inside_tree() && p_focus_mode == FOCUS_NONE && data.focus_mode != FOCUS_NONE && has_focus()) {
 		release_focus();
 		release_focus();
@@ -2163,6 +2226,10 @@ Control *Control::find_next_valid_focus() const {
 	}
 	}
 
 
 	Control *from = const_cast<Control *>(this);
 	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) {
 	while (true) {
 		// Find next child.
 		// Find next child.
@@ -2194,6 +2261,25 @@ Control *Control::find_next_valid_focus() const {
 					}
 					}
 					next_child = next_child->data.parent_control;
 					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;
 			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;
 			return next_child;
 		}
 		}
 
 
@@ -2244,6 +2330,10 @@ Control *Control::find_prev_valid_focus() const {
 	}
 	}
 
 
 	Control *from = const_cast<Control *>(this);
 	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) {
 	while (true) {
 		// Find prev child.
 		// 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) {
 		if (from->is_set_as_top_level() || !from->data.parent_control) {
 			// Find last of the children.
 			// 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 {
 		} else {
 			for (int i = (from->get_index() - 1); i >= 0; i--) {
 			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;
 			return prev_child;
 		}
 		}
 
 
@@ -2509,11 +2620,13 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
 		return; // Bye.
 		return; // Bye.
 	}
 	}
 
 
+	bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
+
 	Control *c = Object::cast_to<Control>(p_at);
 	Control *c = Object::cast_to<Control>(p_at);
 	Container *container = Object::cast_to<Container>(p_at);
 	Container *container = Object::cast_to<Container>(p_at);
 	bool in_container = container ? container->is_ancestor_of(this) : false;
 	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();
 		Rect2 r_c = c->get_global_rect();
 		r_c = r_c.intersection(p_clamp);
 		r_c = r_c.intersection(p_clamp);
 		real_t begin_d = p_dir.dot(r_c.get_position());
 		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;
 	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 {
 Control *Control::make_custom_tooltip(const String &p_text) const {
 	ERR_READ_THREAD_GUARD_V(nullptr);
 	ERR_READ_THREAD_GUARD_V(nullptr);
 	Object *ret = nullptr;
 	Object *ret = nullptr;
@@ -3428,6 +3548,35 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
 
 
 // Base object overrides.
 // 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) {
 void Control::_notification(int p_notification) {
 	ERR_MAIN_THREAD_GUARD;
 	ERR_MAIN_THREAD_GUARD;
 	switch (p_notification) {
 	switch (p_notification) {
@@ -3439,6 +3588,31 @@ void Control::_notification(int p_notification) {
 			saving = false;
 			saving = false;
 		} break;
 		} break;
 #endif // TOOLS_ENABLED
 #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: {
 		case NOTIFICATION_POSTINITIALIZE: {
 			data.initialized = true;
 			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("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("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"), &Control::get_mouse_filter);
 	ClassDB::bind_method(D_METHOD("get_mouse_filter_with_recursive"), &Control::get_mouse_filter_with_recursive);
 	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_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_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::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_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_");
 	ADD_GROUP("Mouse", "mouse_");
@@ -3893,6 +4070,7 @@ void Control::_bind_methods() {
 	BIND_ENUM_CONSTANT(FOCUS_NONE);
 	BIND_ENUM_CONSTANT(FOCUS_NONE);
 	BIND_ENUM_CONSTANT(FOCUS_CLICK);
 	BIND_ENUM_CONSTANT(FOCUS_CLICK);
 	BIND_ENUM_CONSTANT(FOCUS_ALL);
 	BIND_ENUM_CONSTANT(FOCUS_ALL);
+	BIND_ENUM_CONSTANT(FOCUS_ACCESSIBILITY);
 
 
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED);
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED);
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED);
 	BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED);
@@ -4003,6 +4181,8 @@ void Control::_bind_methods() {
 	GDVIRTUAL_BIND(_drop_data, "at_position", "data");
 	GDVIRTUAL_BIND(_drop_data, "at_position", "data");
 	GDVIRTUAL_BIND(_make_custom_tooltip, "for_text");
 	GDVIRTUAL_BIND(_make_custom_tooltip, "for_text");
 
 
+	GDVIRTUAL_BIND(_accessibility_get_contextual_info);
+
 	GDVIRTUAL_BIND(_gui_input, "event");
 	GDVIRTUAL_BIND(_gui_input, "event");
 }
 }
 
 

+ 16 - 3
scene/gui/control.h

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

+ 6 - 0
scene/gui/dialogs.cpp

@@ -51,6 +51,12 @@ void AcceptDialog::_parent_focused() {
 
 
 void AcceptDialog::_notification(int p_what) {
 void AcceptDialog::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_POST_ENTER_TREE: {
 			if (is_visible()) {
 			if (is_visible()) {
 				get_ok_button()->grab_focus();
 				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) {
 	for (const FileDialog::Option &opt : options) {
 		Label *lbl = memnew(Label);
 		Label *lbl = memnew(Label);
 		lbl->set_text(opt.name);
 		lbl->set_text(opt.name);
+		lbl->set_focus_mode(Control::FOCUS_NONE);
 		grid_options->add_child(lbl);
 		grid_options->add_child(lbl);
 		if (opt.values.is_empty()) {
 		if (opt.values.is_empty()) {
 			CheckBox *cb = memnew(CheckBox);
 			CheckBox *cb = memnew(CheckBox);
+			cb->set_accessibility_name(opt.name);
 			cb->set_pressed(opt.default_idx);
 			cb->set_pressed(opt.default_idx);
 			grid_options->add_child(cb);
 			grid_options->add_child(cb);
 			cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
 			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) {
 			for (const String &val : opt.values) {
 				ob->add_item(val);
 				ob->add_item(val);
 			}
 			}
+			ob->set_accessibility_name(opt.name);
 			ob->select(opt.default_idx);
 			ob->select(opt.default_idx);
 			grid_options->add_child(ob);
 			grid_options->add_child(ob);
 			ob->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_option_changed_item_selected).bind(opt.name));
 			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 = memnew(Button);
 	dir_prev->set_theme_type_variation(SceneStringName(FlatButton));
 	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_prev->set_tooltip_text(ETR("Go to previous folder."));
 	dir_next = memnew(Button);
 	dir_next = memnew(Button);
+	dir_next->set_accessibility_name(ETR("Next"));
 	dir_next->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_next->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_next->set_tooltip_text(ETR("Go to next folder."));
 	dir_next->set_tooltip_text(ETR("Go to next folder."));
 	dir_up = memnew(Button);
 	dir_up = memnew(Button);
+	dir_up->set_accessibility_name(ETR("Parent Folder"));
 	dir_up->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_up->set_theme_type_variation(SceneStringName(FlatButton));
 	dir_up->set_tooltip_text(ETR("Go to parent folder."));
 	dir_up->set_tooltip_text(ETR("Go to parent folder."));
 	hbc->add_child(dir_prev);
 	hbc->add_child(dir_prev);
@@ -1746,22 +1752,27 @@ FileDialog::FileDialog() {
 	dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
 	dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
 	dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up));
 	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);
 	drives_container = memnew(HBoxContainer);
 	hbc->add_child(drives_container);
 	hbc->add_child(drives_container);
 
 
 	drives = memnew(OptionButton);
 	drives = memnew(OptionButton);
 	drives->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_select_drive));
 	drives->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_select_drive));
+	drives->set_accessibility_name(ETR("Drive"));
 	hbc->add_child(drives);
 	hbc->add_child(drives);
 
 
 	dir = memnew(LineEdit);
 	dir = memnew(LineEdit);
+	dir->set_accessibility_name(ETR("Directory Path"));
 	dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	hbc->add_child(dir);
 	hbc->add_child(dir);
 	dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 
 
 	refresh = memnew(Button);
 	refresh = memnew(Button);
 	refresh->set_theme_type_variation(SceneStringName(FlatButton));
 	refresh->set_theme_type_variation(SceneStringName(FlatButton));
+	refresh->set_accessibility_name(ETR("Refresh"));
 	refresh->set_tooltip_text(ETR("Refresh files."));
 	refresh->set_tooltip_text(ETR("Refresh files."));
 	refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
 	refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
 	hbc->add_child(refresh);
 	hbc->add_child(refresh);
@@ -1770,6 +1781,7 @@ FileDialog::FileDialog() {
 	show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
 	show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
 	show_hidden->set_toggle_mode(true);
 	show_hidden->set_toggle_mode(true);
 	show_hidden->set_pressed(is_showing_hidden_files());
 	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->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
 	show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
 	show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
 	hbc->add_child(show_hidden);
 	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_theme_type_variation(SceneStringName(FlatButton));
 	show_filename_filter_button->set_toggle_mode(true);
 	show_filename_filter_button->set_toggle_mode(true);
 	show_filename_filter_button->set_pressed(false);
 	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->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));
 	show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
 	hbc->add_child(show_filename_filter_button);
 	hbc->add_child(show_filename_filter_button);
@@ -1787,6 +1800,7 @@ FileDialog::FileDialog() {
 
 
 	makedir = memnew(Button);
 	makedir = memnew(Button);
 	makedir->set_theme_type_variation(SceneStringName(FlatButton));
 	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->set_tooltip_text(ETR("Create a new folder."));
 	makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
 	makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
 	hbc->add_child(makedir);
 	hbc->add_child(makedir);
@@ -1794,6 +1808,7 @@ FileDialog::FileDialog() {
 
 
 	tree = memnew(Tree);
 	tree = memnew(Tree);
 	tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
 	tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+	tree->set_accessibility_name(ETR("Directories and Files"));
 	tree->set_hide_root(true);
 	tree->set_hide_root(true);
 	vbox->add_margin_child(ETR("Directories & Files:"), tree, 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_stretch_ratio(4);
 	filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	filename_filter->set_clear_button_enabled(true);
 	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->add_child(filename_filter);
 	filename_filter_box->set_visible(false);
 	filename_filter_box->set_visible(false);
 	vbox->add_child(filename_filter_box);
 	vbox->add_child(filename_filter_box);
 
 
 	file_box = memnew(HBoxContainer);
 	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 = memnew(LineEdit);
+	file->set_accessibility_name(ETR("File Name"));
 	file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
 	file->set_stretch_ratio(4);
 	file->set_stretch_ratio(4);
 	file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	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;
 	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) {
 void GraphEdit::set_scroll_offset(const Vector2 &p_offset) {
 	setting_scroll_offset = true;
 	setting_scroll_offset = true;
 	h_scrollbar->set_value(p_offset.x);
 	h_scrollbar->set_value(p_offset.x);
@@ -780,6 +836,10 @@ void GraphEdit::_notification(int p_what) {
 			// Draw background fill.
 			// Draw background fill.
 			draw_style_box(theme_cache.panel, Rect2(Point2(), get_size()));
 			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.
 			// Draw background grid.
 			if (show_grid) {
 			if (show_grid) {
 				_draw_grid();
 				_draw_grid();
@@ -958,9 +1018,172 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
 	return false;
 	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) {
 void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
 	Ref<InputEventMouseButton> mb = p_ev;
 	Ref<InputEventMouseButton> mb = p_ev;
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
 	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;
 		connecting_valid = false;
 		click_pos = mb->get_position() / zoom;
 		click_pos = mb->get_position() / zoom;
 		for (int i = get_child_count() - 1; i >= 0; i--) {
 		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;
 	Ref<InputEventMouseMotion> mm = p_ev;
-	if (mm.is_valid() && connecting) {
+	if (mm.is_valid() && connecting && !keyboard_connecting) {
 		connecting_to_point = mm->get_position();
 		connecting_to_point = mm->get_position();
 		minimap->queue_redraw();
 		minimap->queue_redraw();
 		callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred();
 		callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred();
@@ -2142,6 +2365,7 @@ void GraphEdit::force_connection_drag_end() {
 
 
 	connecting = false;
 	connecting = false;
 	connecting_valid = false;
 	connecting_valid = false;
+	keyboard_connecting = false;
 	minimap->queue_redraw();
 	minimap->queue_redraw();
 	queue_redraw();
 	queue_redraw();
 	connections_layer->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("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("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_input_hotzone, "in_node", "in_port", "mouse_position");
 	GDVIRTUAL_BIND(_is_in_output_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::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::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_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_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");
 	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_ENUM_CONSTANT(GRID_PATTERN_DOTS);
 
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, panel);
 	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_major);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, grid_minor);
 	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_theme_type_variation(SceneStringName(FlatButton));
 	zoom_minus_button->set_visible(show_zoom_buttons);
 	zoom_minus_button->set_visible(show_zoom_buttons);
 	zoom_minus_button->set_tooltip_text(ETR("Zoom Out"));
 	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);
 	menu_hbox->add_child(zoom_minus_button);
 	zoom_minus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_minus));
 	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_theme_type_variation(SceneStringName(FlatButton));
 	zoom_reset_button->set_visible(show_zoom_buttons);
 	zoom_reset_button->set_visible(show_zoom_buttons);
 	zoom_reset_button->set_tooltip_text(ETR("Zoom Reset"));
 	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);
 	menu_hbox->add_child(zoom_reset_button);
 	zoom_reset_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_reset));
 	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_theme_type_variation(SceneStringName(FlatButton));
 	zoom_plus_button->set_visible(show_zoom_buttons);
 	zoom_plus_button->set_visible(show_zoom_buttons);
 	zoom_plus_button->set_tooltip_text(ETR("Zoom In"));
 	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);
 	menu_hbox->add_child(zoom_plus_button);
 	zoom_plus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_plus));
 	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_toggle_mode(true);
 	toggle_grid_button->set_pressed(true);
 	toggle_grid_button->set_pressed(true);
 	toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid."));
 	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);
 	toggle_grid_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(toggle_grid_button);
 	menu_hbox->add_child(toggle_grid_button);
 	toggle_grid_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_show_grid_toggled));
 	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_visible(show_grid_buttons);
 	toggle_snapping_button->set_toggle_mode(true);
 	toggle_snapping_button->set_toggle_mode(true);
 	toggle_snapping_button->set_tooltip_text(ETR("Toggle snapping to the grid."));
 	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_pressed(snapping_enabled);
 	toggle_snapping_button->set_focus_mode(FOCUS_NONE);
 	toggle_snapping_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(toggle_snapping_button);
 	menu_hbox->add_child(toggle_snapping_button);
@@ -3026,6 +3261,7 @@ GraphEdit::GraphEdit() {
 	snapping_distance_spinbox->set_step(1);
 	snapping_distance_spinbox->set_step(1);
 	snapping_distance_spinbox->set_value(snapping_distance);
 	snapping_distance_spinbox->set_value(snapping_distance);
 	snapping_distance_spinbox->set_tooltip_text(ETR("Change the 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);
 	menu_hbox->add_child(snapping_distance_spinbox);
 	snapping_distance_spinbox->connect(SceneStringName(value_changed), callable_mp(this, &GraphEdit::_snapping_distance_changed));
 	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_visible(show_minimap_button);
 	minimap_button->set_toggle_mode(true);
 	minimap_button->set_toggle_mode(true);
 	minimap_button->set_tooltip_text(ETR("Toggle the graph minimap."));
 	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_pressed(show_grid);
 	minimap_button->set_focus_mode(FOCUS_NONE);
 	minimap_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(minimap_button);
 	menu_hbox->add_child(minimap_button);
@@ -3044,6 +3281,7 @@ GraphEdit::GraphEdit() {
 	arrange_button = memnew(Button);
 	arrange_button = memnew(Button);
 	arrange_button->set_theme_type_variation(SceneStringName(FlatButton));
 	arrange_button->set_theme_type_variation(SceneStringName(FlatButton));
 	arrange_button->set_visible(show_arrange_button);
 	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->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::arrange_nodes));
 	arrange_button->set_focus_mode(FOCUS_NONE);
 	arrange_button->set_focus_mode(FOCUS_NONE);
 	menu_hbox->add_child(arrange_button);
 	menu_hbox->add_child(arrange_button);

+ 15 - 0
scene/gui/graph_edit.h

@@ -30,6 +30,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "core/variant/typed_dictionary.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/graph_frame.h"
 #include "scene/gui/graph_frame.h"
 #include "scene/gui/graph_node.h"
 #include "scene/gui/graph_node.h"
@@ -198,6 +199,7 @@ private:
 	bool show_grid = true;
 	bool show_grid = true;
 	GridPattern grid_pattern = GRID_PATTERN_LINES;
 	GridPattern grid_pattern = GRID_PATTERN_LINES;
 
 
+	bool keyboard_connecting = false;
 	bool connecting = false;
 	bool connecting = false;
 	StringName connecting_from_node;
 	StringName connecting_from_node;
 	bool connecting_from_output = false;
 	bool connecting_from_output = false;
@@ -269,6 +271,7 @@ private:
 		float base_scale = 1.0;
 		float base_scale = 1.0;
 
 
 		Ref<StyleBox> panel;
 		Ref<StyleBox> panel;
+		Ref<StyleBox> panel_focus;
 		Color grid_major;
 		Color grid_major;
 		Color grid_minor;
 		Color grid_minor;
 
 
@@ -303,6 +306,8 @@ private:
 	HashMap<StringName, HashSet<StringName>> frame_attached_nodes;
 	HashMap<StringName, HashSet<StringName>> frame_attached_nodes;
 	HashMap<StringName, StringName> linked_parent_map;
 	HashMap<StringName, StringName> linked_parent_map;
 
 
+	Dictionary type_names;
+
 	void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
 	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);
 	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);
 	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);
 	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);
 	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 disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
 
 
 	void force_connection_drag_end();
 	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;
 	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;
 	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);
 	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);
 	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 "graph_node.h"
 
 
 #include "scene/gui/box_container.h"
 #include "scene/gui/box_container.h"
+#include "scene/gui/graph_edit.h"
 #include "scene/gui/label.h"
 #include "scene/gui/label.h"
 #include "scene/theme/theme_db.h"
 #include "scene/theme/theme_db.h"
 
 
@@ -196,6 +197,10 @@ void GraphNode::_resort() {
 
 
 		children_count++;
 		children_count++;
 	}
 	}
+	slot_count = children_count;
+	if (selected_slot >= slot_count) {
+		selected_slot = -1;
+	}
 
 
 	if (children_count == 0) {
 	if (children_count == 0) {
 		return;
 		return;
@@ -285,6 +290,7 @@ void GraphNode::_resort() {
 		valid_children_idx++;
 		valid_children_idx++;
 	}
 	}
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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);
 	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) {
 void GraphNode::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			// Used for layout calculations.
 			// Used for layout calculations.
 			Ref<StyleBox> sb_panel = theme_cache.panel;
 			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_to_draw_titlebar = selected ? theme_cache.titlebar_selected : theme_cache.titlebar;
 
 
 			Ref<StyleBox> sb_slot = theme_cache.slot;
 			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;
 			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 body (slots area) stylebox.
 			draw_style_box(sb_to_draw_panel, body_rect);
 			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 title bar stylebox above.
 			draw_style_box(sb_to_draw_titlebar, titlebar_rect);
 			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);
 						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.
 					// Draw slot stylebox.
 					if (slot.draw_stylebox) {
 					if (slot.draw_stylebox) {
 						Control *child = Object::cast_to<Control>(get_child(E.key, false));
 						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.custom_port_icon_right = p_custom_right;
 	slot.draw_stylebox = p_draw_stylebox;
 	slot.draw_stylebox = p_draw_stylebox;
 	slot_table[p_slot_index] = slot;
 	slot_table[p_slot_index] = slot;
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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) {
 void GraphNode::clear_slot(int p_slot_index) {
 	slot_table.erase(p_slot_index);
 	slot_table.erase(p_slot_index);
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	port_pos_dirty = true;
 }
 }
 
 
 void GraphNode::clear_all_slots() {
 void GraphNode::clear_all_slots() {
 	slot_table.clear();
 	slot_table.clear();
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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;
 	slot_table[p_slot_index].enable_left = p_enable;
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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;
 	slot_table[p_slot_index].type_left = p_type;
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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;
 	slot_table[p_slot_index].enable_right = p_enable;
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	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;
 	slot_table[p_slot_index].type_right = p_type;
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	port_pos_dirty = true;
 	port_pos_dirty = true;
 
 
@@ -681,6 +1012,10 @@ void GraphNode::_port_pos_update() {
 
 
 		slot_index++;
 		slot_index++;
 	}
 	}
+	slot_count = slot_index;
+	if (selected_slot >= slot_count) {
+		selected_slot = -1;
+	}
 
 
 	port_pos_dirty = false;
 	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;
 	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) {
 void GraphNode::set_title(const String &p_title) {
 	if (title == p_title) {
 	if (title == p_title) {
 		return;
 		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);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_selected);
 	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);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar_selected);
 	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);
+	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, separation);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset);
@@ -904,7 +1260,9 @@ GraphNode::GraphNode() {
 	title_label = memnew(Label);
 	title_label = memnew(Label);
 	title_label->set_theme_type_variation("GraphNodeTitleLabel");
 	title_label->set_theme_type_variation("GraphNodeTitleLabel");
 	title_label->set_h_size_flags(SIZE_EXPAND_FILL);
 	title_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	title_label->set_focus_mode(Control::FOCUS_NONE);
 	titlebar_hbox->add_child(title_label);
 	titlebar_hbox->add_child(title_label);
 
 
 	set_mouse_filter(MOUSE_FILTER_STOP);
 	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;
 		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;
 	HBoxContainer *titlebar_hbox = nullptr;
 	Label *title_label = nullptr;
 	Label *title_label = nullptr;
 
 
@@ -77,12 +85,17 @@ class GraphNode : public GraphElement {
 	HashMap<int, Slot> slot_table;
 	HashMap<int, Slot> slot_table;
 	Vector<int> slot_y_cache;
 	Vector<int> slot_y_cache;
 
 
+	int slot_count = 0;
+	int selected_slot = -1;
+
 	struct ThemeCache {
 	struct ThemeCache {
 		Ref<StyleBox> panel;
 		Ref<StyleBox> panel;
 		Ref<StyleBox> panel_selected;
 		Ref<StyleBox> panel_selected;
+		Ref<StyleBox> panel_focus;
 		Ref<StyleBox> titlebar;
 		Ref<StyleBox> titlebar;
 		Ref<StyleBox> titlebar_selected;
 		Ref<StyleBox> titlebar_selected;
 		Ref<StyleBox> slot;
 		Ref<StyleBox> slot;
+		Ref<StyleBox> slot_selected;
 
 
 		int separation = 0;
 		int separation = 0;
 		int port_h_offset = 0;
 		int port_h_offset = 0;
@@ -112,6 +125,9 @@ protected:
 	void _get_property_list(List<PropertyInfo> *p_list) const;
 	void _get_property_list(List<PropertyInfo> *p_list) const;
 
 
 public:
 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);
 	void set_title(const String &p_title);
 	String get_title() const;
 	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);
 	items.write[item_id].xl_text = _atr(item_id, p_item);
 	_shape_text(item_id);
 	_shape_text(item_id);
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	notify_property_list_changed();
 	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);
 	items.push_back(item);
 	int item_id = items.size() - 1;
 	int item_id = items.size() - 1;
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	notify_property_list_changed();
 	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].text = p_text;
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	_shape_text(p_idx);
 	_shape_text(p_idx);
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	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) {
 	if (items[p_idx].text_direction != p_text_direction) {
 		items.write[p_idx].text_direction = p_text_direction;
 		items.write[p_idx].text_direction = p_text_direction;
 		_shape_text(p_idx);
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 		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) {
 	if (items[p_idx].language != p_language) {
 		items.write[p_idx].language = p_language;
 		items.write[p_idx].language = p_language;
 		_shape_text(p_idx);
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 		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].auto_translate_mode = p_mode;
 		items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
 		items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
 		_shape_text(p_idx);
 		_shape_text(p_idx);
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -163,7 +169,11 @@ void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
 		p_idx += get_item_count();
 		p_idx += get_item_count();
 	}
 	}
 	ERR_FAIL_INDEX(p_idx, items.size());
 	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 {
 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;
 	items.write[p_idx].tooltip = p_tooltip;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	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());
 	ERR_FAIL_INDEX(p_idx, items.size());
 
 
 	items.write[p_idx].selectable = p_selectable;
 	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 {
 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].disabled = p_disabled;
+	items.write[p_idx].accessibility_item_dirty = true;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -403,7 +418,10 @@ void ItemList::select(int p_idx, bool p_single) {
 		}
 		}
 
 
 		for (int i = 0; i < items.size(); i++) {
 		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;
 		current = p_idx;
@@ -411,8 +429,10 @@ void ItemList::select(int p_idx, bool p_single) {
 	} else {
 	} else {
 		if (items[p_idx].selectable && !items[p_idx].disabled) {
 		if (items[p_idx].selectable && !items[p_idx].disabled) {
 			items.write[p_idx].selected = true;
 			items.write[p_idx].selected = true;
+			items.write[p_idx].accessibility_item_dirty = true;
 		}
 		}
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -425,6 +445,8 @@ void ItemList::deselect(int p_idx) {
 	} else {
 	} else {
 		items.write[p_idx].selected = false;
 		items.write[p_idx].selected = false;
 	}
 	}
+	items.write[p_idx].accessibility_item_dirty = true;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -434,9 +456,13 @@ void ItemList::deselect_all() {
 	}
 	}
 
 
 	for (int i = 0; i < items.size(); i++) {
 	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;
 	current = -1;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -457,6 +483,7 @@ void ItemList::set_current(int p_current) {
 		select(p_current, true);
 		select(p_current, true);
 	} else {
 	} else {
 		current = p_current;
 		current = p_current;
+		queue_accessibility_update();
 		queue_redraw();
 		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.remove_at(p_from_idx);
 	items.insert(p_to_idx, item);
 	items.insert(p_to_idx, item);
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	notify_property_list_changed();
 	notify_property_list_changed();
@@ -489,7 +517,17 @@ void ItemList::set_item_count(int p_count) {
 		return;
 		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);
 	items.resize(p_count);
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	notify_property_list_changed();
 	notify_property_list_changed();
@@ -502,10 +540,15 @@ int ItemList::get_item_count() const {
 void ItemList::remove_item(int p_idx) {
 void ItemList::remove_item(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 	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);
 	items.remove_at(p_idx);
 	if (current == p_idx) {
 	if (current == p_idx) {
 		current = -1;
 		current = -1;
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	defer_select_single = -1;
 	defer_select_single = -1;
@@ -513,9 +556,16 @@ void ItemList::remove_item(int p_idx) {
 }
 }
 
 
 void ItemList::clear() {
 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();
 	items.clear();
 	current = -1;
 	current = -1;
 	ensure_selected_visible = false;
 	ensure_selected_visible = false;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 	defer_select_single = -1;
 	defer_select_single = -1;
@@ -565,6 +615,7 @@ void ItemList::set_max_text_lines(int p_lines) {
 			}
 			}
 		}
 		}
 		shape_changed = true;
 		shape_changed = true;
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -581,6 +632,7 @@ void ItemList::set_max_columns(int p_amount) {
 	}
 	}
 
 
 	max_columns = p_amount;
 	max_columns = p_amount;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 }
 }
@@ -595,6 +647,7 @@ void ItemList::set_select_mode(SelectMode p_mode) {
 	}
 	}
 
 
 	select_mode = p_mode;
 	select_mode = p_mode;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -694,7 +747,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
 	if (mm.is_valid()) {
 	if (mm.is_valid()) {
 		int closest = get_item_at_position(mm->get_position(), true);
 		int closest = get_item_at_position(mm->get_position(), true);
 		if (closest != hovered) {
 		if (closest != hovered) {
+			prev_hovered = hovered;
 			hovered = closest;
 			hovered = closest;
+			queue_accessibility_update();
 			queue_redraw();
 			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_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 (p_event->is_action("ui_up", true)) {
 			if (!search_string.is_empty()) {
 			if (!search_string.is_empty()) {
 				uint64_t now = OS::get_singleton()->get_ticks_msec();
 				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);
 	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) {
 void ItemList::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_RESIZED: {
 			shape_changed = true;
 			shape_changed = true;
 			queue_redraw();
 			queue_redraw();
@@ -1089,6 +1282,7 @@ void ItemList::_notification(int p_what) {
 				_shape_text(i);
 				_shape_text(i);
 			}
 			}
 			shape_changed = true;
 			shape_changed = true;
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 		case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -1097,6 +1291,7 @@ void ItemList::_notification(int p_what) {
 				_shape_text(i);
 				_shape_text(i);
 			}
 			}
 			shape_changed = true;
 			shape_changed = true;
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
 
 
@@ -1537,6 +1732,8 @@ void ItemList::force_update_list_size() {
 
 
 		items.write[i].rect_cache.size = minsize;
 		items.write[i].rect_cache.size = minsize;
 		items.write[i].min_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;
 	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() {
 void ItemList::_mouse_exited() {
 	if (hovered > -1) {
 	if (hovered > -1) {
+		prev_hovered = hovered;
 		hovered = -1;
 		hovered = -1;
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -1763,6 +1962,7 @@ String ItemList::get_tooltip(const Point2 &p_pos) const {
 
 
 void ItemList::sort_items_by_text() {
 void ItemList::sort_items_by_text() {
 	items.sort();
 	items.sort();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	shape_changed = true;
 	shape_changed = true;
 
 
@@ -1872,6 +2072,7 @@ void ItemList::set_auto_width(bool p_enable) {
 
 
 	auto_width = p_enable;
 	auto_width = p_enable;
 	shape_changed = true;
 	shape_changed = true;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -1886,6 +2087,7 @@ void ItemList::set_auto_height(bool p_enable) {
 
 
 	auto_height = p_enable;
 	auto_height = p_enable;
 	shape_changed = true;
 	shape_changed = true;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 

+ 16 - 0
scene/gui/item_list.h

@@ -52,6 +52,9 @@ public:
 
 
 private:
 private:
 	struct Item {
 	struct Item {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		Ref<Texture2D> icon;
 		Ref<Texture2D> icon;
 		bool icon_transposed = false;
 		bool icon_transposed = false;
 		Rect2i icon_region;
 		Rect2i icon_region;
@@ -87,12 +90,14 @@ private:
 
 
 		Item(bool p_dummy) {}
 		Item(bool p_dummy) {}
 	};
 	};
+	RID accessibility_scroll_element;
 
 
 	static inline PropertyListHelper base_property_helper;
 	static inline PropertyListHelper base_property_helper;
 	PropertyListHelper property_helper;
 	PropertyListHelper property_helper;
 
 
 	int current = -1;
 	int current = -1;
 	int hovered = -1;
 	int hovered = -1;
+	int prev_hovered = -1;
 
 
 	bool shape_changed = true;
 	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); }
 	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();
 	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:
 public:
+	virtual RID get_focused_accessibility_element() const override;
+
 	virtual void gui_input(const Ref<InputEvent> &p_event) 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);
 	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;
 	uppercase = p_uppercase;
 	text_dirty = true;
 	text_dirty = true;
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -692,6 +693,15 @@ PackedStringArray Label::get_configuration_warnings() const {
 
 
 void Label::_notification(int p_what) {
 void Label::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 			String new_text = atr(text);
 			String new_text = atr(text);
 			if (new_text == xl_text) {
 			if (new_text == xl_text) {
@@ -703,6 +713,7 @@ void Label::_notification(int p_what) {
 			}
 			}
 			text_dirty = true;
 			text_dirty = true;
 
 
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 			update_configuration_warnings();
 			update_configuration_warnings();
 		} break;
 		} 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;
 			int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
 			bool rtl_layout = is_layout_rtl();
 			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_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));
 			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;
 	horizontal_alignment = p_alignment;
-
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -1103,6 +1118,7 @@ void Label::set_text(const String &p_string) {
 	if (visible_ratio < 1) {
 	if (visible_ratio < 1) {
 		visible_chars = get_total_character_count() * visible_ratio;
 		visible_chars = get_total_character_count() * visible_ratio;
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 	update_configuration_warnings();
 	update_configuration_warnings();
@@ -1193,6 +1209,7 @@ void Label::set_paragraph_separator(const String &p_paragraph_separator) {
 	if (paragraph_separator != p_paragraph_separator) {
 	if (paragraph_separator != p_paragraph_separator) {
 		paragraph_separator = p_paragraph_separator;
 		paragraph_separator = p_paragraph_separator;
 		text_dirty = true;
 		text_dirty = true;
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -1285,6 +1302,7 @@ void Label::set_visible_characters(int p_amount) {
 		}
 		}
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		}
 		queue_redraw();
 		queue_redraw();
 	}
 	}
@@ -1309,6 +1327,7 @@ void Label::set_visible_ratio(float p_ratio) {
 
 
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		}
 		queue_redraw();
 		queue_redraw();
 	}
 	}
@@ -1326,6 +1345,7 @@ void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavio
 	if (visible_chars_behavior != p_behavior) {
 	if (visible_chars_behavior != p_behavior) {
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING || p_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 		if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING || p_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
 			text_dirty = true;
 			text_dirty = true;
+			queue_accessibility_update();
 		}
 		}
 		visible_chars_behavior = p_behavior;
 		visible_chars_behavior = p_behavior;
 		queue_redraw();
 		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");
 	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, 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, line_spacing);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, paragraph_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) {
 Label::Label(const String &p_text) {
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_mouse_filter(MOUSE_FILTER_IGNORE);
 	set_mouse_filter(MOUSE_FILTER_IGNORE);
 	set_text(p_text);
 	set_text(p_text);
 	set_v_size_flags(SIZE_SHRINK_CENTER);
 	set_v_size_flags(SIZE_SHRINK_CENTER);

+ 1 - 0
scene/gui/label.h

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

+ 142 - 1
scene/gui/line_edit.cpp

@@ -33,6 +33,7 @@
 #include "core/input/input_map.h"
 #include "core/input/input_map.h"
 #include "core/os/keyboard.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
 #include "core/os/os.h"
+#include "core/string/translation_server.h"
 #include "scene/gui/label.h"
 #include "scene/gui/label.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
 #include "scene/theme/theme_db.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)) {
 						if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
 							DisplayServer::get_singleton()->clipboard_set_primary(text);
 							DisplayServer::get_singleton()->clipboard_set_primary(text);
 						}
 						}
+						queue_accessibility_update();
 					} else if (b->is_double_click()) {
 					} else if (b->is_double_click()) {
 						// Double-click select word.
 						// Double-click select word.
 						last_dblclk = OS::get_singleton()->get_ticks_msec();
 						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.creating = true;
 								selection.start_column = caret_column;
 								selection.start_column = caret_column;
 								set_caret_column(selection.end);
 								set_caret_column(selection.end);
+								queue_accessibility_update();
 								break;
 								break;
 							}
 							}
 						}
 						}
@@ -973,7 +976,9 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	if (p_data.is_string() && is_editable()) {
 	if (p_data.is_string() && is_editable()) {
 		apply_ime();
 		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;
 		int caret_column_tmp = caret_column;
 		bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
 		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)) {
 		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;
 			text_changed_dirty = true;
 		}
 		}
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -1035,6 +1041,33 @@ void LineEdit::_update_theme_item_cache() {
 	theme_cache.base_scale = get_theme_default_base_scale();
 	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) {
 void LineEdit::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -1049,6 +1082,98 @@ void LineEdit::_notification(int p_what) {
 			}
 			}
 		} break;
 		} break;
 #endif
 #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: {
 		case NOTIFICATION_RESIZED: {
 			_fit_to_width();
 			_fit_to_width();
@@ -1209,6 +1334,7 @@ void LineEdit::_notification(int p_what) {
 					RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_color);
 					RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_color);
 				}
 				}
 			}
 			}
+
 			const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid);
 			const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid);
 			int gl_size = TS->shaped_text_get_glyph_count(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;
 	caret_column = p_column;
 
 
+	queue_accessibility_update();
+
 	// Fit to window.
 	// Fit to window.
 
 
 	if (!is_inside_tree()) {
 	if (!is_inside_tree()) {
@@ -1989,6 +2117,7 @@ void LineEdit::set_caret_column(int p_column) {
 
 
 	scroll_offset = MIN(0, scroll_offset);
 	scroll_offset = MIN(0, scroll_offset);
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2083,6 +2212,7 @@ void LineEdit::deselect() {
 	selection.enabled = false;
 	selection.enabled = false;
 	selection.creating = false;
 	selection.creating = false;
 	selection.double_click = false;
 	selection.double_click = false;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2141,6 +2271,7 @@ void LineEdit::selection_fill_at_caret() {
 	}
 	}
 
 
 	selection.enabled = (selection.begin != selection.end);
 	selection.enabled = (selection.begin != selection.end);
+	queue_accessibility_update();
 }
 }
 
 
 void LineEdit::select_all() {
 void LineEdit::select_all() {
@@ -2156,6 +2287,7 @@ void LineEdit::select_all() {
 	selection.begin = 0;
 	selection.begin = 0;
 	selection.end = text.length();
 	selection.end = text.length();
 	selection.enabled = true;
 	selection.enabled = true;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2173,6 +2305,7 @@ void LineEdit::set_editable(bool p_editable) {
 	_validate_caret_can_draw();
 	_validate_caret_can_draw();
 
 
 	update_minimum_size();
 	update_minimum_size();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2242,6 +2375,7 @@ void LineEdit::select(int p_from, int p_to) {
 	selection.end = p_to;
 	selection.end = p_to;
 	selection.creating = false;
 	selection.creating = false;
 	selection.double_click = false;
 	selection.double_click = false;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -2635,6 +2769,13 @@ void LineEdit::_shape() {
 	if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) {
 	if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) {
 		update_minimum_size();
 		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() {
 void LineEdit::_fit_to_width() {

+ 6 - 0
scene/gui/line_edit.h

@@ -105,6 +105,7 @@ private:
 	Point2 ime_selection;
 	Point2 ime_selection;
 
 
 	RID text_rid;
 	RID text_rid;
+	RID accessibility_text_root_element;
 	float full_width = 0.0;
 	float full_width = 0.0;
 
 
 	bool selecting_enabled = true;
 	bool selecting_enabled = true;
@@ -264,6 +265,11 @@ protected:
 	virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
 	virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
 	virtual void gui_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:
 public:
 	void edit();
 	void edit();
 	void unedit();
 	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));
 	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);
 	text_buf->add_string(xl_text, font, font_size, language);
+
+	queue_accessibility_update();
 }
 }
 
 
 void LinkButton::set_text(const String &p_text) {
 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) {
 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 {
 String LinkButton::get_uri() const {
@@ -147,6 +152,17 @@ Size2 LinkButton::get_minimum_size() const {
 
 
 void LinkButton::_notification(int p_what) {
 void LinkButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_TRANSLATION_CHANGED: {
 			xl_text = atr(text);
 			xl_text = atr(text);
 			_shape();
 			_shape();
@@ -288,7 +304,7 @@ void LinkButton::_bind_methods() {
 
 
 LinkButton::LinkButton(const String &p_text) {
 LinkButton::LinkButton(const String &p_text) {
 	text_buf.instantiate();
 	text_buf.instantiate();
-	set_focus_mode(FOCUS_NONE);
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_default_cursor_shape(CURSOR_POINTING_HAND);
 	set_default_cursor_shape(CURSOR_POINTING_HAND);
 
 
 	set_text(p_text);
 	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);
 			_open_popup(selected_menu, true);
 		}
 		}
 		return;
 		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;
 	Ref<InputEventMouseMotion> mm = p_event;
@@ -276,6 +285,12 @@ void MenuBar::unbind_global_menu() {
 
 
 void MenuBar::_notification(int p_what) {
 void MenuBar::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			if (get_menu_count() > 0) {
 			if (get_menu_count() > 0) {
 				_refresh_menu_names();
 				_refresh_menu_names();
@@ -408,6 +423,10 @@ void MenuBar::_draw_menu_item(int p_index) {
 	bool pressed = (active_menu == p_index);
 	bool pressed = (active_menu == p_index);
 	bool rtl = is_layout_rtl();
 	bool rtl = is_layout_rtl();
 
 
+	if (has_focus() && focused_menu == -1 && p_index == 0) {
+		hovered = true;
+	}
+
 	if (menu_cache[p_index].hidden) {
 	if (menu_cache[p_index].hidden) {
 		return;
 		return;
 	}
 	}
@@ -950,6 +969,7 @@ String MenuBar::get_tooltip(const Point2 &p_pos) const {
 }
 }
 
 
 MenuBar::MenuBar() {
 MenuBar::MenuBar() {
+	set_focus_mode(FOCUS_ALL);
 	set_process_shortcut_input(true);
 	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) {
 void MenuButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
 			popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
 		} break;
 		} break;
@@ -218,7 +226,7 @@ MenuButton::MenuButton(const String &p_text) :
 	set_toggle_mode(true);
 	set_toggle_mode(true);
 	set_disable_shortcuts(false);
 	set_disable_shortcuts(false);
 	set_process_shortcut_input(true);
 	set_process_shortcut_input(true);
-	set_focus_mode(FOCUS_NONE);
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_action_mode(ACTION_MODE_BUTTON_PRESS);
 	set_action_mode(ACTION_MODE_BUTTON_PRESS);
 
 
 	popup = memnew(PopupMenu);
 	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) {
 void OptionButton::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_POSTINITIALIZE: {
 			_refresh_size_cache();
 			_refresh_size_cache();
 			if (has_theme_icon(SNAME("arrow"))) {
 			if (has_theme_icon(SNAME("arrow"))) {

+ 7 - 0
scene/gui/panel.cpp

@@ -33,6 +33,13 @@
 
 
 void Panel::_notification(int p_what) {
 void Panel::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			RID ci = get_canvas_item();
 			RID ci = get_canvas_item();
 			theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size()));
 			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"
 #include "scene/theme/theme_db.h"
 
 
 void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
 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.
 		hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally.
 		_close_pressed();
 		_close_pressed();
 	}
 	}
@@ -115,7 +115,7 @@ void Popup::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_APPLICATION_FOCUS_OUT: {
 		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) {
 				if (hide_reason == HIDE_REASON_NONE) {
 					hide_reason = HIDE_REASON_UNFOCUSED;
 					hide_reason = HIDE_REASON_UNFOCUSED;
 				}
 				}
@@ -126,7 +126,7 @@ void Popup::_notification(int p_what) {
 }
 }
 
 
 void Popup::_parent_focused() {
 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) {
 		if (hide_reason == HIDE_REASON_NONE) {
 			hide_reason = HIDE_REASON_UNFOCUSED;
 			hide_reason = HIDE_REASON_UNFOCUSED;
 		}
 		}

+ 2 - 0
scene/gui/popup.h

@@ -40,6 +40,7 @@ class Popup : public Window {
 	GDCLASS(Popup, Window);
 	GDCLASS(Popup, Window);
 
 
 	LocalVector<Window *> visible_parents;
 	LocalVector<Window *> visible_parents;
+	bool ac_popup = false;
 	bool popped_up = false;
 	bool popped_up = false;
 
 
 public:
 public:
@@ -59,6 +60,7 @@ protected:
 	void _close_pressed();
 	void _close_pressed();
 	virtual Rect2i _popup_adjust_rect() const override;
 	virtual Rect2i _popup_adjust_rect() const override;
 	virtual void _input_from_window(const Ref<InputEvent> &p_event) 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 _notification(int p_what);
 	void _validate_property(PropertyInfo &p_property) const;
 	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;
 			bool match_found = false;
 			for (int i = search_from; i < items.size(); i++) {
 			for (int i = search_from; i < items.size(); i++) {
 				if (!items[i].separator && !items[i].disabled) {
 				if (!items[i].separator && !items[i].disabled) {
+					prev_mouse_over = mouse_over;
 					mouse_over = i;
 					mouse_over = i;
 					emit_signal(SNAME("id_focused"), items[i].id);
 					emit_signal(SNAME("id_focused"), items[i].id);
 					scroll_to_item(i);
 					scroll_to_item(i);
+					queue_accessibility_update();
 					control->queue_redraw();
 					control->queue_redraw();
 					set_input_as_handled();
 					set_input_as_handled();
 					match_found = true;
 					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.
 				// If the last item is not selectable, try re-searching from the start.
 				for (int i = 0; i < search_from; i++) {
 				for (int i = 0; i < search_from; i++) {
 					if (!items[i].separator && !items[i].disabled) {
 					if (!items[i].separator && !items[i].disabled) {
+						prev_mouse_over = mouse_over;
 						mouse_over = i;
 						mouse_over = i;
 						emit_signal(SNAME("id_focused"), items[i].id);
 						emit_signal(SNAME("id_focused"), items[i].id);
 						scroll_to_item(i);
 						scroll_to_item(i);
+						queue_accessibility_update();
 						control->queue_redraw();
 						control->queue_redraw();
 						set_input_as_handled();
 						set_input_as_handled();
 						break;
 						break;
@@ -537,9 +541,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 			bool match_found = false;
 			bool match_found = false;
 			for (int i = search_from; i >= 0; i--) {
 			for (int i = search_from; i >= 0; i--) {
 				if (!items[i].separator && !items[i].disabled) {
 				if (!items[i].separator && !items[i].disabled) {
+					prev_mouse_over = mouse_over;
 					mouse_over = i;
 					mouse_over = i;
 					emit_signal(SNAME("id_focused"), items[i].id);
 					emit_signal(SNAME("id_focused"), items[i].id);
 					scroll_to_item(i);
 					scroll_to_item(i);
+					queue_accessibility_update();
 					control->queue_redraw();
 					control->queue_redraw();
 					set_input_as_handled();
 					set_input_as_handled();
 					match_found = true;
 					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.
 				// If the first item is not selectable, try re-searching from the end.
 				for (int i = items.size() - 1; i >= search_from; i--) {
 				for (int i = items.size() - 1; i >= search_from; i--) {
 					if (!items[i].separator && !items[i].disabled) {
 					if (!items[i].separator && !items[i].disabled) {
+						prev_mouse_over = mouse_over;
 						mouse_over = i;
 						mouse_over = i;
 						emit_signal(SNAME("id_focused"), items[i].id);
 						emit_signal(SNAME("id_focused"), items[i].id);
 						scroll_to_item(i);
 						scroll_to_item(i);
+						queue_accessibility_update();
 						control->queue_redraw();
 						control->queue_redraw();
 						set_input_as_handled();
 						set_input_as_handled();
 						break;
 						break;
@@ -738,9 +746,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
 			}
 			}
 
 
 			if (items[i].text.findn(search_string) == 0) {
 			if (items[i].text.findn(search_string) == 0) {
+				prev_mouse_over = mouse_over;
 				mouse_over = i;
 				mouse_over = i;
 				emit_signal(SNAME("id_focused"), items[i].id);
 				emit_signal(SNAME("id_focused"), items[i].id);
 				scroll_to_item(i);
 				scroll_to_item(i);
+				queue_accessibility_update();
 				control->queue_redraw();
 				control->queue_redraw();
 				set_input_as_handled();
 				set_input_as_handled();
 				break;
 				break;
@@ -755,6 +765,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
 
 
 	if (id < 0) {
 	if (id < 0) {
 		mouse_over = -1;
 		mouse_over = -1;
+		queue_accessibility_update();
 		control->queue_redraw();
 		control->queue_redraw();
 		return;
 		return;
 	}
 	}
@@ -766,6 +777,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
 
 
 	if (over != mouse_over) {
 	if (over != mouse_over) {
 		mouse_over = over;
 		mouse_over = over;
+		queue_accessibility_update();
 		control->queue_redraw();
 		control->queue_redraw();
 	}
 	}
 }
 }
@@ -1104,8 +1116,105 @@ void PopupMenu::remove_child_notify(Node *p_child) {
 	_menu_changed();
 	_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) {
 void PopupMenu::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
 			PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
 			if (pm) {
 			if (pm) {
@@ -1118,12 +1227,6 @@ void PopupMenu::_notification(int p_what) {
 			}
 			}
 		} break;
 		} break;
 
 
-		case NOTIFICATION_EXIT_TREE: {
-			if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
-				unbind_global_menu();
-			}
-		} break;
-
 		case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
 			panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
@@ -1151,7 +1254,9 @@ void PopupMenu::_notification(int p_what) {
 				if (is_global) {
 				if (is_global) {
 					nmenu->set_item_text(global_menu, i, item.xl_text);
 					nmenu->set_item_text(global_menu, i, item.xl_text);
 				}
 				}
+				item.accessibility_item_dirty = true;
 				_shape_item(i);
 				_shape_item(i);
+				queue_accessibility_update();
 			}
 			}
 
 
 			child_controls_changed();
 			child_controls_changed();
@@ -1166,6 +1271,7 @@ void PopupMenu::_notification(int p_what) {
 		case NOTIFICATION_WM_MOUSE_EXIT: {
 		case NOTIFICATION_WM_MOUSE_EXIT: {
 			if (mouse_over >= 0 && (!items[mouse_over].submenu || submenu_over != -1)) {
 			if (mouse_over >= 0 && (!items[mouse_over].submenu || submenu_over != -1)) {
 				mouse_over = -1;
 				mouse_over = -1;
+				queue_accessibility_update();
 				control->queue_redraw();
 				control->queue_redraw();
 			}
 			}
 		} break;
 		} break;
@@ -1281,7 +1387,9 @@ void PopupMenu::_notification(int p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			if (!is_visible()) {
 			if (!is_visible()) {
 				if (mouse_over >= 0) {
 				if (mouse_over >= 0) {
+					prev_mouse_over = mouse_over;
 					mouse_over = -1;
 					mouse_over = -1;
+					queue_accessibility_update();
 					control->queue_redraw();
 					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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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.text = p_label;
 	item.xl_text = atr(p_label);
 	item.xl_text = atr(p_label);
 	item.id = p_id == -1 ? items.size() : p_id;
 	item.id = p_id == -1 ? items.size() : p_id;
+	item.accessibility_item_dirty = true;
 	item.submenu = p_submenu;
 	item.submenu = p_submenu;
 	item.submenu_name = p_submenu->get_name();
 	item.submenu_name = p_submenu->get_name();
 	items.push_back(item);
 	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);
 	_shape_item(items.size() - 1);
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 
 
 	child_controls_changed();
 	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].text = p_text;
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	items.write[p_idx].xl_text = _atr(p_idx, p_text);
 	items.write[p_idx].dirty = true;
 	items.write[p_idx].dirty = true;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_text(global_menu, p_idx, items[p_idx].xl_text);
 		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();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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_INDEX(p_idx, items.size());
 	ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
 	ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+
 	if (items[p_idx].text_direction != p_text_direction) {
 	if (items[p_idx].text_direction != p_text_direction) {
 		items.write[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].dirty = true;
+		items.write[p_idx].accessibility_item_dirty = true;
+
+		_shape_item(p_idx);
+		queue_accessibility_update();
 		control->queue_redraw();
 		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) {
 	if (items[p_idx].language != p_language) {
 		items.write[p_idx].language = p_language;
 		items.write[p_idx].language = p_language;
 		items.write[p_idx].dirty = true;
 		items.write[p_idx].dirty = true;
+		items.write[p_idx].accessibility_item_dirty = true;
+
+		_shape_item(p_idx);
+		queue_accessibility_update();
 		control->queue_redraw();
 		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].checked = p_checked;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, p_checked);
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, p_checked);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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].accel = p_accel;
 	items.write[p_idx].dirty = true;
 	items.write[p_idx].dirty = true;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_accelerator(global_menu, p_idx, p_accel);
 		NativeMenu::get_singleton()->set_item_accelerator(global_menu, p_idx, p_accel);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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].disabled = p_disabled;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_disabled(global_menu, p_idx, p_disabled);
 		NativeMenu::get_singleton()->set_item_disabled(global_menu, p_idx, p_disabled);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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) {
 void PopupMenu::toggle_item_checked(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 	ERR_FAIL_INDEX(p_idx, items.size());
 	items.write[p_idx].checked = !items[p_idx].checked;
 	items.write[p_idx].checked = !items[p_idx].checked;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, items[p_idx].checked);
 		NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, items[p_idx].checked);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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].separator = p_separator;
+	items.write[p_idx].accessibility_item_dirty = true;
+
+	queue_accessibility_update();
 	control->queue_redraw();
 	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].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()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_checkable(global_menu, p_idx, p_checkable);
 		NativeMenu::get_singleton()->set_item_checkable(global_menu, p_idx, p_checkable);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	_menu_changed();
 	_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].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()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_radio_checkable(global_menu, p_idx, p_radio_checkable);
 		NativeMenu::get_singleton()->set_item_radio_checkable(global_menu, p_idx, p_radio_checkable);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	_menu_changed();
 	_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].tooltip = p_tooltip;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_tooltip(global_menu, p_idx, p_tooltip);
 		NativeMenu::get_singleton()->set_item_tooltip(global_menu, p_idx, p_tooltip);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	_menu_changed();
 	_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].state = p_state;
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, p_state);
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, p_state);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	_menu_changed();
 	_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) {
 	if (items.write[p_idx].max_states <= items[p_idx].state) {
 		items.write[p_idx].state = 0;
 		items.write[p_idx].state = 0;
 	}
 	}
+	items.write[p_idx].accessibility_item_dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	if (global_menu.is_valid()) {
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, items[p_idx].state);
 		NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, items[p_idx].state);
 	}
 	}
 
 
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	_menu_changed();
 	_menu_changed();
 }
 }
@@ -2386,11 +2542,12 @@ void PopupMenu::set_focused_item(int p_idx) {
 		return;
 		return;
 	}
 	}
 
 
+	prev_mouse_over = mouse_over;
 	mouse_over = p_idx;
 	mouse_over = p_idx;
 	if (mouse_over != -1) {
 	if (mouse_over != -1) {
 		scroll_to_item(mouse_over);
 		scroll_to_item(mouse_over);
 	}
 	}
-
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 }
 }
 
 
@@ -2412,6 +2569,10 @@ void PopupMenu::set_item_count(int p_count) {
 	if (is_global && prev_size > p_count) {
 	if (is_global && prev_size > p_count) {
 		for (int i = prev_size - 1; i >= p_count; i--) {
 		for (int i = prev_size - 1; i >= p_count; i--) {
 			nmenu->remove_item(global_menu, 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) {
 void PopupMenu::remove_item(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, items.size());
 	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()) {
 	if (items[p_idx].shortcut.is_valid()) {
 		_unref_shortcut(items[p_idx].shortcut);
 		_unref_shortcut(items[p_idx].shortcut);
 	}
 	}
@@ -2611,6 +2776,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
 	Item sep;
 	Item sep;
 	sep.separator = true;
 	sep.separator = true;
 	sep.id = p_id;
 	sep.id = p_id;
+	sep.accessibility_item_dirty = true;
 	if (!p_text.is_empty()) {
 	if (!p_text.is_empty()) {
 		sep.text = p_text;
 		sep.text = p_text;
 		sep.xl_text = atr(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) {
 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()) {
 		if (I.shortcut.is_valid()) {
 			_unref_shortcut(I.shortcut);
 			_unref_shortcut(I.shortcut);
 		}
 		}
@@ -2650,7 +2820,9 @@ void PopupMenu::clear(bool p_free_submenus) {
 	}
 	}
 	items.clear();
 	items.clear();
 
 
+	prev_mouse_over = -1;
 	mouse_over = -1;
 	mouse_over = -1;
+	queue_accessibility_update();
 	control->queue_redraw();
 	control->queue_redraw();
 	child_controls_changed();
 	child_controls_changed();
 	notify_property_list_changed();
 	notify_property_list_changed();
@@ -3032,7 +3204,15 @@ void PopupMenu::popup(const Rect2i &p_bounds) {
 	if (native) {
 	if (native) {
 		_native_popup(p_bounds != Rect2i() ? p_bounds : Rect2i(get_position(), Size2i()));
 		_native_popup(p_bounds != Rect2i() ? p_bounds : Rect2i(get_position(), Size2i()));
 	} else {
 	} 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();
 		moved = Vector2();
 		popup_time_msec = OS::get_singleton()->get_ticks_msec();
 		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()));
 			_native_popup(Rect2i(get_position(), get_size()));
 		}
 		}
 	} else {
 	} 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);
 		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;
 	static HashMap<NativeMenu::SystemMenus, PopupMenu *> system_menus;
 
 
 	struct Item {
 	struct Item {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		Ref<Texture2D> icon;
 		Ref<Texture2D> icon;
 		int icon_max_width = 0;
 		int icon_max_width = 0;
 		Color icon_modulate = Color(1, 1, 1, 1);
 		Color icon_modulate = Color(1, 1, 1, 1);
@@ -95,6 +98,7 @@ class PopupMenu : public Popup {
 
 
 		Item(bool p_dummy) {}
 		Item(bool p_dummy) {}
 	};
 	};
+	RID accessibility_scroll_element;
 
 
 	mutable Rect2i pre_popup_rect;
 	mutable Rect2i pre_popup_rect;
 	void _update_shadow_offsets() const;
 	void _update_shadow_offsets() const;
@@ -122,6 +126,7 @@ class PopupMenu : public Popup {
 	bool during_grabbed_click = false;
 	bool during_grabbed_click = false;
 	bool is_scrolling = false;
 	bool is_scrolling = false;
 	int mouse_over = -1;
 	int mouse_over = -1;
+	int prev_mouse_over = -1;
 	int submenu_over = -1;
 	int submenu_over = -1;
 	String _get_accel_text(const Item &p_item) const;
 	String _get_accel_text(const Item &p_item) const;
 	int _get_mouse_over(const Point2 &p_over) 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 _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 _activate_submenu(int p_over, bool p_by_keyboard = false);
 	void _submenu_timeout();
 	void _submenu_timeout();
 
 
@@ -249,6 +256,8 @@ public:
 	// this value should be updated to reflect the new size.
 	// this value should be updated to reflect the new size.
 	static const int ITEM_PROPERTY_SIZE = 10;
 	static const int ITEM_PROPERTY_SIZE = 10;
 
 
+	virtual RID get_focused_accessibility_element() const override;
+
 	virtual void _parent_focused() override;
 	virtual void _parent_focused() override;
 
 
 	RID bind_global_menu();
 	RID bind_global_menu();

+ 8 - 0
scene/gui/progress_bar.cpp

@@ -54,6 +54,14 @@ void ProgressBar::_notification(int p_what) {
 				queue_redraw();
 				queue_redraw();
 			}
 			}
 		} break;
 		} 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: {
 		case NOTIFICATION_DRAW: {
 			draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size()));
 			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) {
 void Range::_value_changed(double p_value) {
 	GDVIRTUAL_CALL(_value_changed, p_value);
 	GDVIRTUAL_CALL(_value_changed, p_value);
 }
 }
+
 void Range::_value_changed_notify() {
 void Range::_value_changed_notify() {
 	_value_changed(shared->val);
 	_value_changed(shared->val);
 	emit_signal(SceneStringName(value_changed), shared->val);
 	emit_signal(SceneStringName(value_changed), shared->val);
+	queue_accessibility_update();
 	queue_redraw();
 	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() {
 void Range::Shared::emit_value_changed() {
 	for (Range *E : owners) {
 	for (Range *E : owners) {
 		Range *r = E;
 		Range *r = E;
@@ -80,6 +119,7 @@ void Range::Shared::redraw_owners() {
 		if (!r->is_inside_tree()) {
 		if (!r->is_inside_tree()) {
 			continue;
 			continue;
 		}
 		}
+		r->queue_accessibility_update();
 		r->queue_redraw();
 		r->queue_redraw();
 	}
 	}
 }
 }
@@ -91,6 +131,7 @@ void Range::set_value(double p_val) {
 	if (shared->val != prev_val) {
 	if (shared->val != prev_val) {
 		shared->emit_value_changed();
 		shared->emit_value_changed();
 	}
 	}
+	queue_accessibility_update();
 }
 }
 
 
 void Range::_set_value_no_signal(double p_val) {
 void Range::_set_value_no_signal(double p_val) {
@@ -143,6 +184,8 @@ void Range::set_min(double p_min) {
 	shared->emit_changed("min");
 	shared->emit_changed("min");
 
 
 	update_configuration_warnings();
 	update_configuration_warnings();
+
+	queue_accessibility_update();
 }
 }
 
 
 void Range::set_max(double p_max) {
 void Range::set_max(double p_max) {
@@ -156,6 +199,8 @@ void Range::set_max(double p_max) {
 	set_value(shared->val);
 	set_value(shared->val);
 
 
 	shared->emit_changed("max");
 	shared->emit_changed("max");
+
+	queue_accessibility_update();
 }
 }
 
 
 void Range::set_step(double p_step) {
 void Range::set_step(double p_step) {
@@ -165,6 +210,8 @@ void Range::set_step(double p_step) {
 
 
 	shared->step = p_step;
 	shared->step = p_step;
 	shared->emit_changed("step");
 	shared->emit_changed("step");
+
+	queue_accessibility_update();
 }
 }
 
 
 void Range::set_page(double p_page) {
 void Range::set_page(double p_page) {
@@ -177,6 +224,8 @@ void Range::set_page(double p_page) {
 	set_value(shared->val);
 	set_value(shared->val);
 
 
 	shared->emit_changed("page");
 	shared->emit_changed("page");
+
+	queue_accessibility_update();
 }
 }
 
 
 double Range::get_value() const {
 double Range::get_value() const {
@@ -264,6 +313,7 @@ void Range::unshare() {
 	nshared->allow_lesser = shared->allow_lesser;
 	nshared->allow_lesser = shared->allow_lesser;
 	_unref_shared();
 	_unref_shared();
 	_ref_shared(nshared);
 	_ref_shared(nshared);
+	queue_accessibility_update();
 }
 }
 
 
 void Range::_ref_shared(Shared *p_shared) {
 void Range::_ref_shared(Shared *p_shared) {

+ 5 - 0
scene/gui/range.h

@@ -64,9 +64,14 @@ class Range : public Control {
 protected:
 protected:
 	virtual void _value_changed(double p_value);
 	virtual void _value_changed(double p_value);
 	void _notify_shared_value_changed() { shared->emit_value_changed(); }
 	void _notify_shared_value_changed() { shared->emit_value_changed(); }
+	void _notification(int p_what);
 
 
 	static void _bind_methods();
 	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;
 	bool _rounded_values = false;
 
 
 	GDVIRTUAL1(_value_changed, double)
 	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) {
 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) {
 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", "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("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"), &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);
 	ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
 }
 }
 
 

File diff suppressed because it is too large
+ 623 - 35
scene/gui/rich_text_label.cpp


+ 54 - 10
scene/gui/rich_text_label.h

@@ -106,12 +106,12 @@ public:
 	};
 	};
 
 
 	enum DefaultFont {
 	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 {
 	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_99481(const Variant &p_meta, MetaUnderline p_underline_mode);
 	void _push_meta_bind_compat_89024(const Variant &p_meta);
 	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_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);
 	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);
 	void _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio);
+
 	static void _bind_compatibility_methods();
 	static void _bind_compatibility_methods();
 #endif
 #endif
 
 
@@ -151,6 +154,11 @@ private:
 		Ref<TextLine> text_prefix;
 		Ref<TextLine> text_prefix;
 		float prefix_width = 0;
 		float prefix_width = 0;
 		Ref<TextParagraph> text_buf;
 		Ref<TextParagraph> text_buf;
+
+		RID accessibility_line_element;
+		RID accessibility_text_element;
+
+		Item *dc_item = nullptr;
 		Color dc_color;
 		Color dc_color;
 		int dc_ol_size = 0;
 		int dc_ol_size = 0;
 		Color dc_ol_color;
 		Color dc_ol_color;
@@ -160,7 +168,16 @@ private:
 		int char_offset = 0;
 		int char_offset = 0;
 		int char_count = 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 {
 		_FORCE_INLINE_ float get_height(float line_separation) const {
 			return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
 			return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
@@ -178,6 +195,8 @@ private:
 		int line = 0;
 		int line = 0;
 		RID rid;
 		RID rid;
 
 
+		RID accessibility_item_element;
+
 		void _clear_children() {
 		void _clear_children() {
 			RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 			RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 			while (subitems.size()) {
 			while (subitems.size()) {
@@ -237,6 +256,7 @@ private:
 
 
 	struct ItemImage : public Item {
 	struct ItemImage : public Item {
 		Ref<Texture2D> image;
 		Ref<Texture2D> image;
+		String alt_text;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		bool pad = false;
 		bool pad = false;
 		bool size_in_percent = false;
 		bool size_in_percent = false;
@@ -258,7 +278,7 @@ private:
 	};
 	};
 
 
 	struct ItemFont : public Item {
 	struct ItemFont : public Item {
-		DefaultFont def_font = CUSTOM_FONT;
+		DefaultFont def_font = RTL_CUSTOM_FONT;
 		Ref<Font> font;
 		Ref<Font> font;
 		bool variation = false;
 		bool variation = false;
 		bool def_size = false;
 		bool def_size = false;
@@ -341,6 +361,7 @@ private:
 
 
 	struct ItemTable : public Item {
 	struct ItemTable : public Item {
 		struct Column {
 		struct Column {
+			String name;
 			bool expand = false;
 			bool expand = false;
 			bool shrink = true;
 			bool shrink = true;
 			int expand_ratio = 0;
 			int expand_ratio = 0;
@@ -354,6 +375,7 @@ private:
 		LocalVector<float> rows;
 		LocalVector<float> rows;
 		LocalVector<float> rows_no_padding;
 		LocalVector<float> rows_no_padding;
 		LocalVector<float> rows_baseline;
 		LocalVector<float> rows_baseline;
+		String name;
 
 
 		int align_to_row = -1;
 		int align_to_row = -1;
 		int total_width = 0;
 		int total_width = 0;
@@ -503,6 +525,9 @@ private:
 
 
 	Array custom_effects;
 	Array custom_effects;
 
 
+	HashMap<RID, Rect2> ac_element_bounds_cache;
+
+	void _invalidate_accessibility();
 	void _invalidate_current_line(ItemFrame *p_frame);
 	void _invalidate_current_line(ItemFrame *p_frame);
 
 
 	void _thread_function(void *p_userdata);
 	void _thread_function(void *p_userdata);
@@ -552,6 +577,11 @@ private:
 	bool deselect_on_focus_loss_enabled = true;
 	bool deselect_on_focus_loss_enabled = true;
 	bool drag_and_drop_selection_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 context_menu_enabled = false;
 	bool shortcut_keys_enabled = true;
 	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);
 	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);
 	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);
 	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 _roman(int p_num, bool p_capitalize) const;
 	String _letters(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 internal_stack_editing = false;
 	bool stack_externally_modified = 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;
 	bool fit_content = false;
 
 
 	struct ThemeCache {
 	struct ThemeCache {
@@ -692,9 +732,12 @@ private:
 	} theme_cache;
 	} theme_cache;
 
 
 public:
 public:
+	virtual RID get_focused_accessibility_element() const override;
+	PackedStringArray get_accessibility_configuration_warnings() const override;
+
 	String get_parsed_text() const;
 	String get_parsed_text() const;
 	void add_text(const String &p_text);
 	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 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();
 	void add_newline();
 	bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
 	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_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_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_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_fade(int p_start_index, int p_length);
 	void push_shake(int p_strength, float p_rate, bool p_connected);
 	void push_shake(int p_strength, float p_rate, bool p_connected);
 	void push_wave(float p_frequency, float p_amplitude, 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_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment);
 	void push_context();
 	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_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_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_border_color(const Color &p_color);
 	void set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size);
 	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) {
 void ScrollBar::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			RID ci = get_canvas_item();
 			RID ci = get_canvas_item();
 
 
@@ -654,6 +661,8 @@ ScrollBar::ScrollBar(Orientation p_orientation) {
 
 
 	if (focus_by_default) {
 	if (focus_by_default) {
 		set_focus_mode(FOCUS_ALL);
 		set_focus_mode(FOCUS_ALL);
+	} else {
+		set_focus_mode(FOCUS_ACCESSIBILITY);
 	}
 	}
 	set_step(0);
 	set_step(0);
 }
 }

+ 35 - 0
scene/gui/scroll_container.cpp

@@ -353,8 +353,43 @@ void ScrollContainer::_reposition_children() {
 	queue_redraw();
 	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) {
 void ScrollContainer::_notification(int p_what) {
 	switch (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_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:

+ 6 - 0
scene/gui/scroll_container.h

@@ -99,6 +99,12 @@ protected:
 	void _update_scrollbar_position();
 	void _update_scrollbar_position();
 	void _scroll_moved(float);
 	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:
 public:
 	virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
 	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;
 		} break;
 
 
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {

+ 45 - 1
scene/gui/spin_box.cpp

@@ -34,6 +34,50 @@
 #include "core/math/expression.h"
 #include "core/math/expression.h"
 #include "scene/theme/theme_db.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 SpinBox::get_minimum_size() const {
 	Size2 ms = line_edit->get_combined_minimum_size();
 	Size2 ms = line_edit->get_combined_minimum_size();
 	ms.width += sizing_cache.buttons_block_width;
 	ms.width += sizing_cache.buttons_block_width;
@@ -650,7 +694,7 @@ void SpinBox::_bind_methods() {
 }
 }
 
 
 SpinBox::SpinBox() {
 SpinBox::SpinBox() {
-	line_edit = memnew(LineEdit);
+	line_edit = memnew(SpinBoxLineEdit);
 	line_edit->set_emoji_menu_enabled(false);
 	line_edit->set_emoji_menu_enabled(false);
 	add_child(line_edit, false, INTERNAL_MODE_FRONT);
 	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/gui/range.h"
 #include "scene/main/timer.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 {
 class SpinBox : public Range {
 	GDCLASS(SpinBox, Range);
 	GDCLASS(SpinBox, Range);
 
 
-	LineEdit *line_edit = nullptr;
+	SpinBoxLineEdit *line_edit = nullptr;
 	bool update_on_text_changed = false;
 	bool update_on_text_changed = false;
 	bool accepted = true;
 	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);
 	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) {
 void SplitContainerDragger::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_MOUSE_ENTER: {
 			mouse_inside = true;
 			mouse_inside = true;
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 			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 {
 Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisibilityMode p_visibility_mode) const {
 	int idx = 0;
 	int idx = 0;
 	for (int i = 0; i < get_child_count(false); i++) {
 	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);
 	void _notification(int p_what);
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	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:
 private:
 	bool dragging = false;
 	bool dragging = false;
 	int drag_from = 0;
 	int drag_from = 0;
@@ -49,6 +53,8 @@ private:
 
 
 public:
 public:
 	virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
 	virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
+
+	SplitContainerDragger();
 };
 };
 
 
 class SplitContainer : public Container {
 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);
 	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) {
 void TabBar::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 		case NOTIFICATION_ENTER_TREE: {
@@ -383,6 +412,46 @@ void TabBar::_notification(int p_what) {
 			}
 			}
 		} break;
 		} 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: {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
@@ -393,6 +462,7 @@ void TabBar::_notification(int p_what) {
 				_shape(i);
 				_shape(i);
 			}
 			}
 
 
+			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
 			update_minimum_size();
 			update_minimum_size();
 
 
@@ -672,6 +742,15 @@ void TabBar::set_tab_count(int p_count) {
 	}
 	}
 
 
 	ERR_FAIL_COND(p_count < 0);
 	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);
 	tabs.resize(p_count);
 
 
 	if (p_count == 0) {
 	if (p_count == 0) {
@@ -702,6 +781,7 @@ void TabBar::set_tab_count(int p_count) {
 		}
 		}
 	}
 	}
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 	notify_property_list_changed();
 	notify_property_list_changed();
@@ -737,6 +817,7 @@ void TabBar::set_current_tab(int p_current) {
 	if (scroll_to_selected) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 
 
 	emit_signal(SNAME("tab_changed"), p_current);
 	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());
 	ERR_FAIL_INDEX(p_offset, tabs.size());
 	offset = p_offset;
 	offset = p_offset;
 	_update_cache();
 	_update_cache();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -811,6 +893,7 @@ void TabBar::set_tab_title(int p_tab, const String &p_title) {
 	if (scroll_to_selected) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	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) {
 void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) {
 	ERR_FAIL_INDEX(p_tab, tabs.size());
 	ERR_FAIL_INDEX(p_tab, tabs.size());
 	tabs.write[p_tab].tooltip = p_tooltip;
 	tabs.write[p_tab].tooltip = p_tooltip;
+	queue_accessibility_update();
 }
 }
 
 
 String TabBar::get_tab_tooltip(int p_tab) const {
 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) {
 	if (tabs[p_tab].text_direction != p_text_direction) {
 		tabs.write[p_tab].text_direction = p_text_direction;
 		tabs.write[p_tab].text_direction = p_text_direction;
+
 		_shape(p_tab);
 		_shape(p_tab);
+		queue_accessibility_update();
 		queue_redraw();
 		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) {
 	if (tabs[p_tab].language != p_language) {
 		tabs.write[p_tab].language = p_language;
 		tabs.write[p_tab].language = p_language;
+
 		_shape(p_tab);
 		_shape(p_tab);
 		_update_cache();
 		_update_cache();
 		_ensure_no_over_offset();
 		_ensure_no_over_offset();
 		if (scroll_to_selected) {
 		if (scroll_to_selected) {
 			ensure_tab_visible(current);
 			ensure_tab_visible(current);
 		}
 		}
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 		update_minimum_size();
 		update_minimum_size();
 	}
 	}
@@ -927,6 +1015,7 @@ void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
 	if (scroll_to_selected) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 }
 }
@@ -950,6 +1039,7 @@ void TabBar::set_tab_hidden(int p_tab, bool p_hidden) {
 	if (scroll_to_selected) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 }
 }
@@ -1117,6 +1207,8 @@ void TabBar::_update_cache(bool p_update_hover) {
 				max_drawn_tab--;
 				max_drawn_tab--;
 			}
 			}
 		}
 		}
+
+		tabs.write[i].accessibility_item_dirty = true;
 	}
 	}
 
 
 	missing_right = max_drawn_tab < tabs.size() - 1;
 	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) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 
 
@@ -1189,12 +1282,19 @@ void TabBar::clear_tabs() {
 		return;
 		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();
 	tabs.clear();
 	offset = 0;
 	offset = 0;
 	max_drawn_tab = 0;
 	max_drawn_tab = 0;
 	current = -1;
 	current = -1;
 	previous = -1;
 	previous = -1;
 
 
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 	notify_property_list_changed();
 	notify_property_list_changed();
@@ -1202,6 +1302,11 @@ void TabBar::clear_tabs() {
 
 
 void TabBar::remove_tab(int p_idx) {
 void TabBar::remove_tab(int p_idx) {
 	ERR_FAIL_INDEX(p_idx, tabs.size());
 	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);
 	tabs.remove_at(p_idx);
 
 
 	bool is_tab_changing = current == 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();
 	queue_redraw();
 	update_minimum_size();
 	update_minimum_size();
 	notify_property_list_changed();
 	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) {
 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) {
 	if (tab_over < 0) {
 		return Variant();
 		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) {
 	if (String(d["type"]) == p_type) {
 		int tab_from_id = d["tab_index"];
 		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 from_path = d["from_path"];
 		NodePath to_path = get_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) {
 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];
 	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);
 	p_from_tabbar->remove_tab(p_from_index);
 	tabs.insert(p_to_index, moving_tab);
 	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_redraw();
 	}
 	}
 
 
+	queue_accessibility_update();
 	update_minimum_size();
 	update_minimum_size();
 }
 }
 
 
@@ -1515,6 +1624,8 @@ void TabBar::move_tab(int p_from, int p_to) {
 	ERR_FAIL_INDEX(p_to, tabs.size());
 	ERR_FAIL_INDEX(p_to, tabs.size());
 
 
 	Tab tab_from = tabs[p_from];
 	Tab tab_from = tabs[p_from];
+	tab_from.accessibility_item_dirty = true;
+
 	tabs.remove_at(p_from);
 	tabs.remove_at(p_from);
 	tabs.insert(p_to, tab_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) {
 	if (scroll_to_selected) {
 		ensure_tab_visible(current);
 		ensure_tab_visible(current);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	notify_property_list_changed();
 	notify_property_list_changed();
 }
 }
@@ -1957,6 +2069,7 @@ void TabBar::_bind_methods() {
 }
 }
 
 
 TabBar::TabBar() {
 TabBar::TabBar() {
+	set_focus_mode(FOCUS_ACCESSIBILITY);
 	set_size(Size2(get_size().width, get_minimum_size().height));
 	set_size(Size2(get_size().width, get_minimum_size().height));
 	set_focus_mode(FOCUS_ALL);
 	set_focus_mode(FOCUS_ALL);
 	connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));
 	connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));

+ 9 - 0
scene/gui/tab_bar.h

@@ -54,6 +54,9 @@ public:
 
 
 private:
 private:
 	struct Tab {
 	struct Tab {
+		mutable RID accessibility_item_element;
+		mutable bool accessibility_item_dirty = true;
+
 		String text;
 		String text;
 		String tooltip;
 		String tooltip;
 
 
@@ -170,6 +173,9 @@ private:
 	void _shape(int p_tab);
 	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 _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:
 protected:
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual String get_tooltip(const Point2 &p_pos) const 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);
 	void _move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
 
 
 public:
 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);
 	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;
 	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);
 	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) {
 void TabContainer::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_ENTER_TREE: {
 			// If some nodes happen to be renamed outside the tree, the tab names need to be updated manually.
 			// If some nodes happen to be renamed outside the tree, the tab names need to be updated manually.
 			if (get_tab_count() > 0) {
 			if (get_tab_count() > 0) {
@@ -556,6 +595,7 @@ void TabContainer::add_child_notify(Node *p_child) {
 	if (get_tab_count() == 1) {
 	if (get_tab_count() == 1) {
 		queue_redraw();
 		queue_redraw();
 	}
 	}
+	queue_accessibility_update();
 
 
 	p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
 	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));
 	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();
 	_refresh_tab_indices();
+	queue_accessibility_update();
 }
 }
 
 
 void TabContainer::remove_child_notify(Node *p_child) {
 void TabContainer::remove_child_notify(Node *p_child) {
 	Container::remove_child_notify(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) {
 	if (p_child == tab_bar) {
 		return;
 		return;
 	}
 	}
@@ -607,6 +653,7 @@ void TabContainer::remove_child_notify(Node *p_child) {
 	if (get_tab_count() == 0) {
 	if (get_tab_count() == 0) {
 		queue_redraw();
 		queue_redraw();
 	}
 	}
+	queue_accessibility_update();
 
 
 	p_child->remove_meta("_tab_index");
 	p_child->remove_meta("_tab_index");
 	p_child->remove_meta("_tab_name");
 	p_child->remove_meta("_tab_name");

+ 4 - 0
scene/gui/tab_container.h

@@ -97,6 +97,8 @@ private:
 		int tab_font_size;
 		int tab_font_size;
 	} theme_cache;
 	} theme_cache;
 
 
+	HashMap<Node *, RID> tab_panels;
+
 	int _get_tab_height() const;
 	int _get_tab_height() const;
 	Vector<Control *> _get_tab_controls() const;
 	Vector<Control *> _get_tab_controls() const;
 	void _on_theme_changed();
 	void _on_theme_changed();
@@ -129,6 +131,8 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
 public:
 public:
+	virtual bool accessibility_override_tree_hierarchy() const override { return true; }
+
 	TabBar *get_tab_bar() const;
 	TabBar *get_tab_bar() const;
 
 
 	int get_tab_idx_at_point(const Point2 &p_point) 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) {
 void TextEdit::Text::invalidate_cache(int p_line, bool p_text_changed) {
 	ERR_FAIL_INDEX(p_line, text.size());
 	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()) {
 	if (font.is_null()) {
 		return; // Not in tree?
 		return; // Not in tree?
 	}
 	}
@@ -564,8 +590,173 @@ String TextEdit::Text::get_enabled_word_separators() const {
 ///                            TEXT EDIT                                    ///
 ///                            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) {
 void TextEdit::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_POSTINITIALIZE: {
 			_update_caches();
 			_update_caches();
 		} break;
 		} break;
@@ -1694,6 +1885,8 @@ void TextEdit::_notification(int p_what) {
 
 
 				_update_ime_text();
 				_update_ime_text();
 				adjust_viewport_to_caret(0);
 				adjust_viewport_to_caret(0);
+
+				queue_accessibility_update();
 				queue_redraw();
 				queue_redraw();
 			}
 			}
 		} break;
 		} 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->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
 				if (mb->is_shift_pressed()) {
 				if (mb->is_shift_pressed()) {
 					h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
 					h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
+					queue_accessibility_update();
 				} else if (mb->is_alt_pressed()) {
 				} else if (mb->is_alt_pressed()) {
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					_scroll_up(15 * mb->get_factor(), true);
 					_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->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
 				if (mb->is_shift_pressed()) {
 				if (mb->is_shift_pressed()) {
 					h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
 					h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
+					queue_accessibility_update();
 				} else if (mb->is_alt_pressed()) {
 				} else if (mb->is_alt_pressed()) {
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					// Scroll 5 times as fast as normal (like in Visual Studio Code).
 					_scroll_down(15 * mb->get_factor(), true);
 					_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) {
 			if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
 				h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
 				h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
+				queue_accessibility_update();
 			}
 			}
 			if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
 			if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
 				h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
 				h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
+				queue_accessibility_update();
 			}
 			}
 
 
 			if (mb->get_button_index() == MouseButton::LEFT) {
 			if (mb->get_button_index() == MouseButton::LEFT) {
@@ -1961,6 +2158,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 							return;
 							return;
 						}
 						}
 
 
+						queue_accessibility_update();
+
 						last_dblclk = 0;
 						last_dblclk = 0;
 					} else if (!mb->is_shift_pressed()) {
 					} else if (!mb->is_shift_pressed()) {
 						if (drag_and_drop_selection_enabled && mouse_over_selection_caret >= 0) {
 						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 = OS::get_singleton()->get_ticks_msec();
 					last_dblclk_pos = mb->get_position();
 					last_dblclk_pos = mb->get_position();
 				}
 				}
+				queue_accessibility_update();
 				queue_redraw();
 				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) {
 		if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
 			accept_event(); // Accept event if scroll changed.
 			accept_event(); // Accept event if scroll changed.
 		}
 		}
+		queue_accessibility_update();
 
 
 		return;
 		return;
 	}
 	}
@@ -2417,8 +2618,15 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			return;
 			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.
 		// 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) {
 			if (editable) {
 				insert_text_at_caret("\t");
 				insert_text_at_caret("\t");
 			}
 			}
@@ -2996,6 +3204,7 @@ void TextEdit::_update_caches() {
 		syntax_highlighter->set_text_edit(this);
 		syntax_highlighter->set_text_edit(this);
 	}
 	}
 	_clear_syntax_highlighting_cache();
 	_clear_syntax_highlighting_cache();
+	queue_accessibility_update();
 }
 }
 
 
 void TextEdit::_close_ime_window() {
 void TextEdit::_close_ime_window() {
@@ -3039,6 +3248,7 @@ void TextEdit::_update_ime_text() {
 		}
 		}
 	}
 	}
 	_clear_syntax_highlighting_cache();
 	_clear_syntax_highlighting_cache();
+	queue_accessibility_update();
 	queue_redraw();
 	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) {
 void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	Control::drop_data(p_point, 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());
 		Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
 		int drop_at_line = pos.y;
 		int drop_at_line = pos.y;
 		int drop_at_column = pos.x;
 		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) {
 void TextEdit::set_tooltip_request_func(const Callable &p_tooltip_callback) {
 	tooltip_callback = p_tooltip_callback;
 	tooltip_callback = p_tooltip_callback;
+	queue_accessibility_update();
 }
 }
 
 
 /* Text */
 /* Text */
@@ -3261,7 +3474,7 @@ void TextEdit::set_editable(bool p_editable) {
 	}
 	}
 
 
 	editable = p_editable;
 	editable = p_editable;
-
+	queue_accessibility_update();
 	queue_redraw();
 	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_LTR), text_direction == TEXT_DIRECTION_LTR);
 			menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
 			menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
 		}
 		}
+		queue_accessibility_update();
 		queue_redraw();
 		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.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
 		text.invalidate_all();
 		text.invalidate_all();
 		_update_placeholder();
 		_update_placeholder();
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -3326,6 +3541,7 @@ void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParse
 		for (int i = 0; i < text.size(); i++) {
 		for (int i = 0; i < text.size(); i++) {
 			text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 			text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 		}
 		}
+		queue_accessibility_update();
 		queue_redraw();
 		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++) {
 	for (int i = 0; i < text.size(); i++) {
 		text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 		text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -3358,6 +3575,7 @@ void TextEdit::set_tab_size(const int p_size) {
 	text.set_tab_size(p_size);
 	text.set_tab_size(p_size);
 	text.invalidate_all_lines();
 	text.invalidate_all_lines();
 	_update_placeholder();
 	_update_placeholder();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -3379,6 +3597,14 @@ bool TextEdit::is_indent_wrapped_lines() const {
 	return text.is_indent_wrapped_lines();
 	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
 // User controls
 void TextEdit::set_overtype_mode_enabled(bool p_enabled) {
 void TextEdit::set_overtype_mode_enabled(bool p_enabled) {
 	if (overtype_mode == 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_line(0);
 	set_caret_column(0);
 	set_caret_column(0);
-
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 	setting_text = false;
 	setting_text = false;
 	emit_signal(SNAME("text_set"));
 	emit_signal(SNAME("text_set"));
@@ -3537,6 +3763,7 @@ void TextEdit::set_placeholder(const String &p_text) {
 
 
 	placeholder_text = p_text;
 	placeholder_text = p_text;
 	_update_placeholder();
 	_update_placeholder();
+	queue_accessibility_update();
 	queue_redraw();
 	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);
 	_offset_carets_after(next_line, next_column, from_line, from_column);
 	end_multicaret_edit();
 	end_multicaret_edit();
 	end_complex_operation();
 	end_complex_operation();
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 }
 
 
 void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
 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() {
 void TextEdit::end_action() {
 	if (current_action != EditAction::ACTION_NONE) {
 	if (current_action != EditAction::ACTION_NONE) {
 		pending_action_end = true;
 		pending_action_end = true;
+		queue_accessibility_update();
 	}
 	}
 }
 }
 
 
@@ -4172,6 +4403,8 @@ void TextEdit::begin_complex_operation() {
 void TextEdit::end_complex_operation() {
 void TextEdit::end_complex_operation() {
 	_push_current_op();
 	_push_current_op();
 
 
+	queue_accessibility_update();
+
 	complex_operation_count = MAX(complex_operation_count - 1, 0);
 	complex_operation_count = MAX(complex_operation_count - 1, 0);
 	if (complex_operation_count > 0) {
 	if (complex_operation_count > 0) {
 		return;
 		return;
@@ -4264,6 +4497,7 @@ void TextEdit::undo() {
 		_selection_changed();
 		_selection_changed();
 	}
 	}
 	adjust_viewport_to_caret();
 	adjust_viewport_to_caret();
+	queue_accessibility_update();
 }
 }
 
 
 void TextEdit::redo() {
 void TextEdit::redo() {
@@ -4320,6 +4554,7 @@ void TextEdit::redo() {
 		_selection_changed();
 		_selection_changed();
 	}
 	}
 	adjust_viewport_to_caret();
 	adjust_viewport_to_caret();
+	queue_accessibility_update();
 }
 }
 
 
 void TextEdit::clear_undo_history() {
 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 < 0, Rect2i(-1, -1, 0, 0));
 	ERR_FAIL_COND_V(p_column > text[p_line].length(), 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.
 		// The TextEdit is empty.
 		return Rect2i();
 		return Rect2i();
 	}
 	}
@@ -4841,6 +5076,7 @@ void TextEdit::remove_secondary_carets() {
 	if (drag_caret_index >= 0) {
 	if (drag_caret_index >= 0) {
 		drag_caret_index = -1;
 		drag_caret_index = -1;
 	}
 	}
+	queue_accessibility_update();
 }
 }
 
 
 int TextEdit::get_caret_count() const {
 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) {
 	if (caret_moved) {
 		_caret_changed(p_caret);
 		_caret_changed(p_caret);
 	}
 	}
+	queue_accessibility_update();
 }
 }
 
 
 int TextEdit::get_caret_line(int p_caret) const {
 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) {
 	if (caret_moved) {
 		_caret_changed(p_caret);
 		_caret_changed(p_caret);
 	}
 	}
+	queue_accessibility_update();
 }
 }
 
 
 int TextEdit::get_caret_column(int p_caret) const {
 int TextEdit::get_caret_column(int p_caret) const {
@@ -5384,7 +5622,7 @@ void TextEdit::select_all() {
 		return;
 		return;
 	}
 	}
 
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -5401,7 +5639,7 @@ void TextEdit::select_word_under_caret(int p_caret) {
 		return;
 		return;
 	}
 	}
 
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -5446,7 +5684,7 @@ void TextEdit::add_selection_for_next_occurrence() {
 		return;
 		return;
 	}
 	}
 
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -5489,7 +5727,7 @@ void TextEdit::skip_selection_for_next_occurrence() {
 		return;
 		return;
 	}
 	}
 
 
-	if (text.size() == 1 && text[0].length() == 0) {
+	if (text.size() == 1 && text[0].is_empty()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -5558,6 +5796,9 @@ void TextEdit::select(int p_origin_line, int p_origin_column, int p_caret_line,
 	if (had_selection != activate) {
 	if (had_selection != activate) {
 		_selection_changed(p_caret);
 		_selection_changed(p_caret);
 	}
 	}
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 }
 
 
 bool TextEdit::has_selection(int p_caret) const {
 bool TextEdit::has_selection(int p_caret) const {
@@ -5802,6 +6043,9 @@ void TextEdit::deselect(int p_caret) {
 	if (selection_changed) {
 	if (selection_changed) {
 		_selection_changed(p_caret);
 		_selection_changed(p_caret);
 	}
 	}
+
+	queue_accessibility_update();
+	queue_redraw();
 }
 }
 
 
 void TextEdit::delete_selection(int p_caret) {
 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) {
 	if (line_wrapping_mode != p_wrapping_mode) {
 		line_wrapping_mode = p_wrapping_mode;
 		line_wrapping_mode = p_wrapping_mode;
 		_update_wrap_at_column(true);
 		_update_wrap_at_column(true);
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -5859,6 +6104,7 @@ void TextEdit::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
 	autowrap_mode = p_mode;
 	autowrap_mode = p_mode;
 	if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) {
 	if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) {
 		_update_wrap_at_column(true);
 		_update_wrap_at_column(true);
+		queue_accessibility_update();
 		queue_redraw();
 		queue_redraw();
 	}
 	}
 }
 }
@@ -5964,6 +6210,7 @@ void TextEdit::set_v_scroll(double p_scroll) {
 	if (p_scroll >= max_v_scroll - 1.0) {
 	if (p_scroll >= max_v_scroll - 1.0) {
 		_scroll_moved(v_scroll->get_value());
 		_scroll_moved(v_scroll->get_value());
 	}
 	}
+	queue_accessibility_update();
 }
 }
 
 
 double TextEdit::get_v_scroll() const {
 double TextEdit::get_v_scroll() const {
@@ -5975,6 +6222,7 @@ void TextEdit::set_h_scroll(int p_scroll) {
 		p_scroll = 0;
 		p_scroll = 0;
 	}
 	}
 	h_scroll->set_value(p_scroll);
 	h_scroll->set_value(p_scroll);
+	queue_accessibility_update();
 }
 }
 
 
 int TextEdit::get_h_scroll() const {
 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.set_draw_control_chars(draw_control_chars);
 		text.invalidate_font();
 		text.invalidate_font();
 		_update_placeholder();
 		_update_placeholder();
+		queue_accessibility_update();
 		queue_redraw();
 		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("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("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
 	// User controls
 	ClassDB::bind_method(D_METHOD("set_overtype_mode_enabled", "enabled"), &TextEdit::set_overtype_mode_enabled);
 	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);
 	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, "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::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, "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_GROUP("Scroll", "scroll_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
 	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();
 		_unhide_all_lines();
 	}
 	}
 	hiding_enabled = p_enabled;
 	hiding_enabled = p_enabled;
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -7164,6 +7418,7 @@ void TextEdit::_unhide_all_lines() {
 		text.set_hidden(i, false);
 		text.set_hidden(i, false);
 	}
 	}
 	_update_scrollbars();
 	_update_scrollbars();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -7181,6 +7436,7 @@ void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) {
 	if (_is_hiding_enabled() || !p_hidden) {
 	if (_is_hiding_enabled() || !p_hidden) {
 		text.set_hidden(p_line, p_hidden);
 		text.set_hidden(p_line, p_hidden);
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -7833,6 +8089,7 @@ void TextEdit::_selection_changed(int p_caret) {
 	}
 	}
 
 
 	_cancel_drag_and_drop_text();
 	_cancel_drag_and_drop_text();
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -8035,6 +8292,7 @@ void TextEdit::_update_wrap_at_column(bool p_force) {
 		first_visible_line_wrap_ofs = 0;
 		first_visible_line_wrap_ofs = 0;
 	}
 	}
 	set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs);
 	set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs);
+	queue_accessibility_update();
 }
 }
 
 
 /* Viewport. */
 /* Viewport. */
@@ -8151,6 +8409,7 @@ void TextEdit::_scroll_moved(double p_to_val) {
 		first_visible_line = n_line;
 		first_visible_line = n_line;
 		first_visible_line_wrap_ofs = wi;
 		first_visible_line_wrap_ofs = wi;
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	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) {
 		if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
 			v_scroll->set_value(target_v_scroll);
 			v_scroll->set_value(target_v_scroll);
+			queue_accessibility_update();
 		} else {
 		} else {
 			scrolling = true;
 			scrolling = true;
 			set_physics_process_internal(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) {
 		if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
 			v_scroll->set_value(target_v_scroll);
 			v_scroll->set_value(target_v_scroll);
+			queue_accessibility_update();
 		} else {
 		} else {
 			scrolling = true;
 			scrolling = true;
 			set_physics_process_internal(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);
 	h_scroll->set_value(first_visible_col);
+
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
@@ -8418,6 +8681,7 @@ void TextEdit::_update_gutter_width() {
 	if (get_viewport()) {
 	if (get_viewport()) {
 		hovered_gutter = _get_hovered_gutter(get_local_mouse_position());
 		hovered_gutter = _get_hovered_gutter(get_local_mouse_position());
 	}
 	}
+	queue_accessibility_update();
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 

+ 29 - 1
scene/gui/text_edit.h

@@ -144,12 +144,15 @@ private:
 			Color color = Color(1, 1, 1);
 			Color color = Color(1, 1, 1);
 		};
 		};
 
 
+		mutable int64_t next_item_id = 0;
+
 		struct Line {
 		struct Line {
 			Vector<Gutter> gutters;
 			Vector<Gutter> gutters;
 
 
 			String data;
 			String data;
 			Array bidi_override;
 			Array bidi_override;
 			Ref<TextParagraph> data_buf;
 			Ref<TextParagraph> data_buf;
+			Vector<RID> accessibility_text_root_element;
 
 
 			String ime_data;
 			String ime_data;
 			Array ime_bidi_override;
 			Array ime_bidi_override;
@@ -227,6 +230,14 @@ private:
 		BitField<TextServer::LineBreakFlag> get_brk_flags() const;
 		BitField<TextServer::LineBreakFlag> get_brk_flags() const;
 		int get_line_wrap_amount(int p_line) 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;
 		Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
 		const Ref<TextParagraph> get_line_data(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;
 		float get_indent_offset(int p_line, bool p_rtl) const;
@@ -275,13 +286,14 @@ private:
 
 
 	/* Text */
 	/* Text */
 	Text text;
 	Text text;
-
 	bool setting_text = false;
 	bool setting_text = false;
 
 
 	bool alt_start = false;
 	bool alt_start = false;
 	bool alt_start_no_hold = false;
 	bool alt_start_no_hold = false;
 	uint32_t alt_code = 0;
 	uint32_t alt_code = 0;
 
 
+	bool tab_input_mode = true;
+
 	// Text properties.
 	// Text properties.
 	String ime_text = "";
 	String ime_text = "";
 	Point2 ime_selection;
 	Point2 ime_selection;
@@ -628,6 +640,8 @@ private:
 	bool draw_tabs = false;
 	bool draw_tabs = false;
 	bool draw_spaces = false;
 	bool draw_spaces = false;
 
 
+	RID accessibility_text_root_element_nl;
+
 	/*** Super internal Core API. Everything builds on it. ***/
 	/*** Super internal Core API. Everything builds on it. ***/
 	bool text_changed_dirty = false;
 	bool text_changed_dirty = false;
 	void _text_changed();
 	void _text_changed();
@@ -718,6 +732,17 @@ protected:
 	virtual void _paste_internal(int p_caret);
 	virtual void _paste_internal(int p_caret);
 	virtual void _paste_primary_clipboard_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)
 	GDVIRTUAL2(_handle_unicode_input, int, int)
 	GDVIRTUAL1(_backspace, int)
 	GDVIRTUAL1(_backspace, int)
 	GDVIRTUAL1(_cut, int)
 	GDVIRTUAL1(_cut, int)
@@ -765,6 +790,9 @@ public:
 	void set_indent_wrapped_lines(bool p_enabled);
 	void set_indent_wrapped_lines(bool p_enabled);
 	bool is_indent_wrapped_lines() const;
 	bool is_indent_wrapped_lines() const;
 
 
+	void set_tab_input_mode(bool p_enabled);
+	bool get_tab_input_mode() const;
+
 	// User controls
 	// User controls
 	void set_overtype_mode_enabled(bool p_enabled);
 	void set_overtype_mode_enabled(bool p_enabled);
 	bool is_overtype_mode_enabled() const;
 	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) {
 void TextureProgressBar::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_DRAW: {
 			if (under.is_valid()) {
 			if (under.is_valid()) {
 				if (nine_patch_stretch) {
 				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

Some files were not shown because too many files changed in this diff