Fix various accessibility issues in PopupMenu
This commit is contained in:
+71
-22
@@ -534,6 +534,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
control->grab_focus();
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
queue_accessibility_update();
|
||||
@@ -550,6 +551,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
control->grab_focus();
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
queue_accessibility_update();
|
||||
@@ -577,6 +579,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
control->grab_focus();
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
queue_accessibility_update();
|
||||
@@ -593,6 +596,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
control->grab_focus();
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
queue_accessibility_update();
|
||||
@@ -1085,12 +1089,20 @@ void PopupMenu::_update_search_bar_visibility() {
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_search_bar_input(const Ref<InputEvent> &p_event) {
|
||||
// Redirect navigational key events to the tree.
|
||||
Ref<InputEventKey> key = p_event;
|
||||
if (key.is_valid()) {
|
||||
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
|
||||
search_bar->accept_event();
|
||||
void PopupMenu::_items_focus_entered() {
|
||||
if (mouse_over != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1105,6 +1117,13 @@ void PopupMenu::_search_bar_text_changed(const String &p_new_text) {
|
||||
_menu_changed();
|
||||
}
|
||||
|
||||
void PopupMenu::_search_bar_focus_entered() {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = -1;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
|
||||
void PopupMenu::_filter_items(const String &p_query) {
|
||||
for (PopupMenu::Item &item : items) {
|
||||
if (item.submenu) {
|
||||
@@ -1369,7 +1388,14 @@ void PopupMenu::_notification(int p_what) {
|
||||
ERR_FAIL_COND(ae.is_null());
|
||||
|
||||
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_MENU);
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_count(ae, items.size());
|
||||
|
||||
int item_count = 0;
|
||||
for (const Item &item : items) {
|
||||
if (item.visible && !item.separator) {
|
||||
item_count++;
|
||||
}
|
||||
}
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_count(ae, item_count);
|
||||
|
||||
if (accessibility_scroll_element.is_null()) {
|
||||
accessibility_scroll_element = AccessibilityServer::get_singleton()->create_sub_element(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_CONTAINER);
|
||||
@@ -1380,25 +1406,26 @@ void PopupMenu::_notification(int p_what) {
|
||||
AccessibilityServer::get_singleton()->update_set_transform(accessibility_scroll_element, scroll_xform);
|
||||
AccessibilityServer::get_singleton()->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;
|
||||
float display_width = control->get_size().width;
|
||||
Point2 ofs = scroll_container->get_global_position();
|
||||
|
||||
int item_index = 0;
|
||||
bool first_visible = true;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
const Item &item = items.write[i];
|
||||
if (!item.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid discrepancy between min size and drawn items due to rounding.
|
||||
ofs.y += i > 0 ? theme_cache.v_separation : theme_cache.v_separation - (theme_cache.v_separation / 2);
|
||||
|
||||
Point2 item_ofs = ofs;
|
||||
if (item.accessibility_item_element.is_null()) {
|
||||
item.accessibility_item_element = AccessibilityServer::get_singleton()->create_sub_element(accessibility_scroll_element, AccessibilityServerEnums::AccessibilityRole::ROLE_MENU_ITEM);
|
||||
item.accessibility_item_dirty = true;
|
||||
}
|
||||
|
||||
if (item.visible) {
|
||||
// Avoid discrepancy between min size and drawn items due to rounding.
|
||||
ofs.y += first_visible ? 0 : theme_cache.v_separation;
|
||||
first_visible = false;
|
||||
}
|
||||
|
||||
Point2 item_ofs = ofs;
|
||||
item_ofs.x += item.indent * theme_cache.indent;
|
||||
float h = _get_item_height(i);
|
||||
|
||||
@@ -1418,18 +1445,26 @@ void PopupMenu::_notification(int p_what) {
|
||||
}
|
||||
|
||||
AccessibilityServer::get_singleton()->update_add_action(item.accessibility_item_element, AccessibilityServerEnums::AccessibilityAction::ACTION_CLICK, callable_mp(this, &PopupMenu::_accessibility_action_click).bind(i));
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_index(item.accessibility_item_element, i);
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_index(item.accessibility_item_element, item_index);
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_level(item.accessibility_item_element, 0);
|
||||
AccessibilityServer::get_singleton()->update_set_list_item_selected(item.accessibility_item_element, i == mouse_over);
|
||||
AccessibilityServer::get_singleton()->update_set_name(item.accessibility_item_element, item.xl_text);
|
||||
AccessibilityServer::get_singleton()->update_set_flag(item.accessibility_item_element, AccessibilityServerEnums::AccessibilityFlags::FLAG_DISABLED, item.disabled);
|
||||
AccessibilityServer::get_singleton()->update_set_flag(item.accessibility_item_element, AccessibilityServerEnums::AccessibilityFlags::FLAG_HIDDEN, !item.visible);
|
||||
AccessibilityServer::get_singleton()->update_set_tooltip(item.accessibility_item_element, item.tooltip);
|
||||
|
||||
AccessibilityServer::get_singleton()->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;
|
||||
|
||||
if (item.visible) {
|
||||
ofs.y += h;
|
||||
|
||||
if (!item.separator) {
|
||||
item_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev_mouse_over = -1;
|
||||
|
||||
@@ -3705,7 +3740,7 @@ void PopupMenu::set_visible(bool p_visible) {
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
if (!parent_popup) {
|
||||
if (search_bar->is_visible()) {
|
||||
search_bar->edit(true);
|
||||
search_bar->edit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3724,6 +3759,18 @@ void PopupMenu::set_visible(bool p_visible) {
|
||||
}
|
||||
}
|
||||
|
||||
RID PopupMenuItems::get_focused_accessibility_element() const {
|
||||
return popup->get_focused_accessibility_element();
|
||||
}
|
||||
|
||||
void PopupMenuItems::gui_input(const Ref<InputEvent> &p_event) {
|
||||
if (p_event->is_action("ui_up") || p_event->is_action("ui_down")) {
|
||||
// We've already handled these in `PopupMenu::_input_from_window`, and these events will be handled as focus navigation if we don't stop them from propagating, causing focus to jump to the search bar.
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PopupMenu::PopupMenu() {
|
||||
set_flag(FLAG_TRANSPARENT, true);
|
||||
set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
|
||||
@@ -3746,7 +3793,7 @@ PopupMenu::PopupMenu() {
|
||||
search_bar->set_placeholder(ETR("Search"));
|
||||
search_bar->set_keep_editing_on_text_submit(true);
|
||||
search_bar->connect(SceneStringName(text_changed), callable_mp(this, &PopupMenu::_search_bar_text_changed));
|
||||
search_bar->connect(SceneStringName(gui_input), callable_mp(this, &PopupMenu::_search_bar_input));
|
||||
search_bar->connect(SceneStringName(focus_entered), callable_mp(this, &PopupMenu::_search_bar_focus_entered));
|
||||
vbox_container->add_child(search_bar, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
// Scroll Container
|
||||
@@ -3758,13 +3805,15 @@ PopupMenu::PopupMenu() {
|
||||
scroll_container->add_theme_style_override(SceneStringName(panel), memnew(StyleBoxEmpty));
|
||||
|
||||
// The control which will display the items
|
||||
control = memnew(Control);
|
||||
control = memnew(PopupMenuItems(this));
|
||||
control->set_clip_contents(false);
|
||||
control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
control->set_focus_mode(Control::FOCUS_ALL);
|
||||
scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
|
||||
control->connect(SceneStringName(draw), callable_mp(this, &PopupMenu::_draw_items));
|
||||
control->connect(SceneStringName(focus_entered), callable_mp(this, &PopupMenu::_items_focus_entered));
|
||||
|
||||
submenu_timer = memnew(Timer);
|
||||
submenu_timer->set_wait_time(submenu_timer_popup_delay); // Default is 0.2.
|
||||
|
||||
+18
-2
@@ -41,10 +41,13 @@ class PanelContainer;
|
||||
class VBoxContainer;
|
||||
class LineEdit;
|
||||
class Timer;
|
||||
class PopupMenuItems;
|
||||
|
||||
class PopupMenu : public Popup {
|
||||
GDCLASS(PopupMenu, Popup);
|
||||
|
||||
friend class PopupMenuItems;
|
||||
|
||||
static HashMap<NativeMenu::SystemMenus, PopupMenu *> system_menus;
|
||||
|
||||
struct Item {
|
||||
@@ -187,7 +190,7 @@ class PopupMenu : public Popup {
|
||||
VBoxContainer *vbox_container = nullptr;
|
||||
LineEdit *search_bar = nullptr;
|
||||
ScrollContainer *scroll_container = nullptr;
|
||||
Control *control = nullptr;
|
||||
PopupMenuItems *control = nullptr;
|
||||
|
||||
const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
|
||||
const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
|
||||
@@ -243,8 +246,9 @@ class PopupMenu : public Popup {
|
||||
|
||||
void _draw_items();
|
||||
void _update_search_bar_visibility();
|
||||
void _search_bar_input(const Ref<InputEvent> &p_event);
|
||||
void _items_focus_entered();
|
||||
void _search_bar_text_changed(const String &p_new_text);
|
||||
void _search_bar_focus_entered();
|
||||
void _filter_items(const String &p_query);
|
||||
|
||||
void _close_pressed();
|
||||
@@ -454,3 +458,15 @@ public:
|
||||
PopupMenu();
|
||||
~PopupMenu();
|
||||
};
|
||||
|
||||
class PopupMenuItems : public Control {
|
||||
GDCLASS(PopupMenuItems, Control);
|
||||
|
||||
PopupMenu *popup = nullptr;
|
||||
|
||||
public:
|
||||
PopupMenuItems(PopupMenu *p_popup) : popup(p_popup) {}
|
||||
|
||||
virtual RID get_focused_accessibility_element() const override;
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user