Skip to content

Commit 3dab1f5

Browse files
committed
GUI: Improve Tree accessibility with TreeGrid navigation
Add ROLE_TREE_GRID and implement TreeGrid-style keyboard navigation for Tree controls. Left/right arrows now navigate between cell text and buttons, making tree item buttons keyboard-accessible. Changes: - Add ROLE_TREE_GRID enum and AccessKit mapping - Use TreeGrid role for all Tree controls - Make buttons children of rows to prevent screen readers from announcing button names when focusing cells - Fix cell visibility flag (was incorrectly hiding all cells) - Add active descendant tracking for screen reader focus - Only set expanded/collapsed state on items with children
1 parent 1559ab3 commit 3dab1f5

File tree

4 files changed

+43
-9
lines changed

4 files changed

+43
-9
lines changed

doc/classes/DisplayServer.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2774,6 +2774,9 @@
27742774
<constant name="ROLE_TOOLTIP" value="45" enum="AccessibilityRole">
27752775
Tooltip element.
27762776
</constant>
2777+
<constant name="ROLE_TREE_GRID" value="46" enum="AccessibilityRole">
2778+
Tree grid element (tree with multiple columns).
2779+
</constant>
27772780
<constant name="POPUP_MENU" value="0" enum="AccessibilityPopupType">
27782781
Popup menu.
27792782
</constant>

drivers/accesskit/accessibility_driver_accesskit.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,7 @@ AccessibilityDriverAccessKit::AccessibilityDriverAccessKit() {
16401640
role_map[DisplayServer::AccessibilityRole::ROLE_COLUMN_HEADER] = ACCESSKIT_ROLE_COLUMN_HEADER;
16411641
role_map[DisplayServer::AccessibilityRole::ROLE_TREE] = ACCESSKIT_ROLE_TREE;
16421642
role_map[DisplayServer::AccessibilityRole::ROLE_TREE_ITEM] = ACCESSKIT_ROLE_TREE_ITEM;
1643+
role_map[DisplayServer::AccessibilityRole::ROLE_TREE_GRID] = ACCESSKIT_ROLE_TREE_GRID;
16431644
role_map[DisplayServer::AccessibilityRole::ROLE_LIST] = ACCESSKIT_ROLE_LIST;
16441645
role_map[DisplayServer::AccessibilityRole::ROLE_LIST_ITEM] = ACCESSKIT_ROLE_LIST_ITEM;
16451646
role_map[DisplayServer::AccessibilityRole::ROLE_LIST_BOX] = ACCESSKIT_ROLE_LIST_BOX;

scene/gui/tree.cpp

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3528,7 +3528,7 @@ void Tree::popup_select(int p_option) {
35283528
}
35293529

35303530
void Tree::_go_left() {
3531-
if (get_tree()->is_accessibility_enabled() && selected_button >= 0) {
3531+
if (selected_button >= 0) {
35323532
selected_button--;
35333533
} else if (selected_col == 0) {
35343534
selected_button = -1;
@@ -3562,8 +3562,22 @@ void Tree::_go_left() {
35623562

35633563
void Tree::_go_right() {
35643564
int buttons = (selected_item && selected_col >= 0 && selected_col < columns.size()) ? selected_item->cells[selected_col].buttons.size() : 0;
3565-
if (get_tree()->is_accessibility_enabled() && selected_button < buttons - 1) {
3565+
if (selected_button >= 0 && selected_button < buttons - 1) {
3566+
// On a button, move to next button.
35663567
selected_button++;
3568+
} else if (selected_button == -1 && selected_item->get_first_child() != nullptr) {
3569+
// On cell with children.
3570+
if (selected_item->is_collapsed()) {
3571+
// Expand collapsed item.
3572+
selected_item->set_collapsed(false);
3573+
} else if (selected_item->get_next_visible()) {
3574+
// Already expanded, move to first child.
3575+
selected_col = 0;
3576+
_go_down();
3577+
}
3578+
} else if (selected_button == -1 && buttons > 0) {
3579+
// On cell with no children but has buttons, move to first button.
3580+
selected_button = 0;
35673581
} else if (selected_col == (columns.size() - 1)) {
35683582
selected_button = -1;
35693583
if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) {
@@ -4766,10 +4780,17 @@ void Tree::_accessibility_update_item(Point2 &r_ofs, TreeItem *p_item, int &r_ro
47664780

47674781
DisplayServer::get_singleton()->accessibility_update_set_table_row_index(p_item->accessibility_row_element, r_row);
47684782
DisplayServer::get_singleton()->accessibility_update_set_list_item_level(p_item->accessibility_row_element, p_level);
4769-
DisplayServer::get_singleton()->accessibility_update_set_list_item_expanded(p_item->accessibility_row_element, !p_item->collapsed);
4770-
DisplayServer::get_singleton()->accessibility_update_set_flag(p_item->accessibility_row_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, !(p_item->visible && !p_item->parent_visible_in_tree));
4771-
DisplayServer::get_singleton()->accessibility_update_add_action(p_item->accessibility_row_element, DisplayServer::AccessibilityAction::ACTION_COLLAPSE, callable_mp(this, &Tree::_accessibility_action_collapse).bind(p_item));
4772-
DisplayServer::get_singleton()->accessibility_update_add_action(p_item->accessibility_row_element, DisplayServer::AccessibilityAction::ACTION_EXPAND, callable_mp(this, &Tree::_accessibility_action_expand).bind(p_item));
4783+
4784+
// Only set expanded state on items with children (leaf nodes should not have expanded/collapsed state).
4785+
if (p_item->get_first_child() != nullptr) {
4786+
DisplayServer::get_singleton()->accessibility_update_set_list_item_expanded(p_item->accessibility_row_element, !p_item->collapsed);
4787+
DisplayServer::get_singleton()->accessibility_update_add_action(p_item->accessibility_row_element, DisplayServer::AccessibilityAction::ACTION_COLLAPSE, callable_mp(this, &Tree::_accessibility_action_collapse).bind(p_item));
4788+
DisplayServer::get_singleton()->accessibility_update_add_action(p_item->accessibility_row_element, DisplayServer::AccessibilityAction::ACTION_EXPAND, callable_mp(this, &Tree::_accessibility_action_expand).bind(p_item));
4789+
}
4790+
4791+
// Add focus action to row element so it shows as focusable in accessibility tree.
4792+
DisplayServer::get_singleton()->accessibility_update_add_action(p_item->accessibility_row_element, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &Tree::_accessibility_action_focus).bind(p_item, 0));
4793+
DisplayServer::get_singleton()->accessibility_update_set_flag(p_item->accessibility_row_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, !(p_item->visible && p_item->parent_visible_in_tree));
47734794

47744795
DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(p_item->accessibility_row_element, selected_item == p_item);
47754796
if (p_item == root && is_root_hidden()) {
@@ -4809,7 +4830,7 @@ void Tree::_accessibility_update_item(Point2 &r_ofs, TreeItem *p_item, int &r_ro
48094830
}
48104831

48114832
DisplayServer::get_singleton()->accessibility_update_set_text_align(cell.accessibility_cell_element, cell.text_alignment);
4812-
DisplayServer::get_singleton()->accessibility_update_set_flag(cell.accessibility_cell_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, !(p_item->visible && !p_item->parent_visible_in_tree));
4833+
DisplayServer::get_singleton()->accessibility_update_set_flag(cell.accessibility_cell_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, !(p_item->visible && p_item->parent_visible_in_tree));
48134834
DisplayServer::get_singleton()->accessibility_update_set_flag(cell.accessibility_cell_element, DisplayServer::AccessibilityFlags::FLAG_READONLY, !cell.editable);
48144835
DisplayServer::get_singleton()->accessibility_update_set_tooltip(cell.accessibility_cell_element, cell.tooltip);
48154836
switch (cell.mode) {
@@ -4848,7 +4869,9 @@ void Tree::_accessibility_update_item(Point2 &r_ofs, TreeItem *p_item, int &r_ro
48484869
Vector2 ofst = Vector2(col_offset + cw, 0);
48494870
for (int j = cell.buttons.size() - 1; j >= 0; j--) {
48504871
if (cell.buttons[j].accessibility_button_element.is_null()) {
4851-
cell.buttons[j].accessibility_button_element = DisplayServer::get_singleton()->accessibility_create_sub_element(cell.accessibility_cell_element, DisplayServer::AccessibilityRole::ROLE_BUTTON);
4872+
// Create buttons as children of the row, not the cell.
4873+
// This prevents screen readers from reading button names when focusing cells
4874+
cell.buttons[j].accessibility_button_element = DisplayServer::get_singleton()->accessibility_create_sub_element(p_item->accessibility_row_element, DisplayServer::AccessibilityRole::ROLE_BUTTON);
48524875
}
48534876

48544877
DisplayServer::get_singleton()->accessibility_update_add_action(cell.buttons[j].accessibility_button_element, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &Tree::_accessibility_action_button_press).bind(p_item, i, j));
@@ -4904,7 +4927,7 @@ void Tree::_notification(int p_what) {
49044927
RID ae = get_accessibility_element();
49054928
ERR_FAIL_COND(ae.is_null());
49064929

4907-
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TREE);
4930+
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TREE_GRID);
49084931

49094932
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &Tree::_accessibility_action_scroll_down));
49104933
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &Tree::_accessibility_action_scroll_left));
@@ -4957,6 +4980,12 @@ void Tree::_notification(int p_what) {
49574980
}
49584981
DisplayServer::get_singleton()->accessibility_update_set_table_row_count(ae, rows);
49594982

4983+
// Set active descendant to the focused element so screen readers announce it.
4984+
RID focused = get_focused_accessibility_element();
4985+
if (focused.is_valid() && focused != ae) {
4986+
DisplayServer::get_singleton()->accessibility_update_set_active_descendant(ae, focused);
4987+
}
4988+
49604989
} break;
49614990

49624991
case NOTIFICATION_FOCUS_ENTER: {

servers/display/display_server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ class DisplayServer : public Object {
606606
ROLE_TITLE_BAR,
607607
ROLE_DIALOG,
608608
ROLE_TOOLTIP,
609+
ROLE_TREE_GRID,
609610
};
610611

611612
enum AccessibilityPopupType {

0 commit comments

Comments
 (0)