From a3ed9e6f2cfb539418b06d75fc96352634b81352 Mon Sep 17 00:00:00 2001 From: Eric M Date: Sun, 25 Sep 2022 00:32:53 +1000 Subject: [PATCH] Move Shortcut Context to Control and ensure that `shortcut_input` adheres to contexts. Also ensure that controls with no context are only triggered AFTER nodes which do have a context. --- doc/classes/BaseButton.xml | 3 --- doc/classes/Control.xml | 3 +++ doc/classes/MenuBar.xml | 3 --- scene/gui/base_button.cpp | 36 ------------------------------------ scene/gui/base_button.h | 4 ---- scene/gui/control.cpp | 34 ++++++++++++++++++++++++++++++++++ scene/gui/control.h | 6 ++++++ scene/gui/menu_bar.cpp | 36 ------------------------------------ scene/gui/menu_bar.h | 5 ----- scene/gui/menu_button.cpp | 4 ---- scene/main/scene_tree.cpp | 22 +++++++++++++++++++++- 11 files changed, 64 insertions(+), 92 deletions(-) diff --git a/doc/classes/BaseButton.xml b/doc/classes/BaseButton.xml index 629675132a2..47e1f85c976 100644 --- a/doc/classes/BaseButton.xml +++ b/doc/classes/BaseButton.xml @@ -68,9 +68,6 @@ [Shortcut] associated to the button. - - The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused. - If [code]true[/code], the button will add information about its shortcut in the tooltip. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 9c7cb7b0891..7968b03c4b4 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1051,6 +1051,9 @@ [b]Note:[/b] This property is mainly intended to be used for animation purposes. Text inside the Control will look pixelated or blurry when the Control is scaled. To support multiple resolutions in your project, use an appropriate viewport stretch mode as described in the [url=$DOCS_URL/tutorials/viewports/multiple_resolutions.html]documentation[/url] instead of scaling Controls individually. [b]Note:[/b] If the Control node is a child of a [Container] node, the scale will be reset to [code]Vector2(1, 1)[/code] when the scene is instantiated. To set the Control's scale when it's instantiated, wait for one frame using [code]await get_tree().process_frame[/code] then set its [member scale] property. + + The [Node] which must be a parent of the focused [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused. + The size of the node's bounding rectangle, in pixels. [Container] nodes update this property automatically. diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml index 3ef0572e9f4..e8505937ff4 100644 --- a/doc/classes/MenuBar.xml +++ b/doc/classes/MenuBar.xml @@ -106,9 +106,6 @@ If [code]true[/code], [MenuBar] will use system global menu when supported. - - The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused. - Position in the global menu to insert first [MenuBar] item at. diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index af6a99ca62b..d5eaa4f7a51 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -349,10 +349,6 @@ Ref BaseButton::get_shortcut() const { void BaseButton::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) { on_action_event(p_event); accept_event(); @@ -389,34 +385,6 @@ Ref BaseButton::get_button_group() const { return button_group; } -void BaseButton::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } -} - -Node *BaseButton::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to(ctx_obj); - - return ctx_node; -} - -bool BaseButton::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); -} - bool BaseButton::_was_pressed_by_mouse() const { return was_mouse_pressed; } @@ -446,9 +414,6 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group); ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); - GDVIRTUAL_BIND(_pressed); GDVIRTUAL_BIND(_toggled, "button_pressed"); @@ -466,7 +431,6 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); BIND_ENUM_CONSTANT(DRAW_NORMAL); BIND_ENUM_CONSTANT(DRAW_PRESSED); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index c83b08aadf5..78392398000 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -81,7 +81,6 @@ protected: virtual void shortcut_input(const Ref &p_event) override; void _notification(int p_what); - bool _is_focus_owner_in_shortcut_context() const; bool _was_pressed_by_mouse() const; GDVIRTUAL0(_pressed) @@ -132,9 +131,6 @@ public: void set_button_group(const Ref &p_group); Ref get_button_group() const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; - BaseButton(); ~BaseButton(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 488ae762c53..565e450d60a 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1759,6 +1759,34 @@ void Control::warp_mouse(const Point2 &p_position) { get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); } +void Control::set_shortcut_context(const Node *p_node) { + if (p_node != nullptr) { + data.shortcut_context = p_node->get_instance_id(); + } else { + data.shortcut_context = ObjectID(); + } +} + +Node *Control::get_shortcut_context() const { + Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context); + Node *ctx_node = Object::cast_to(ctx_obj); + + return ctx_node; +} + +bool Control::is_focus_owner_in_shortcut_context() const { + if (data.shortcut_context == ObjectID()) { + // No context, therefore global - always "in" context. + return true; + } + + const Node *ctx_node = get_shortcut_context(); + const Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; + + // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +} + // Drag and drop handling. void Control::set_drag_forwarding(Object *p_target) { @@ -3133,6 +3161,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Control::warp_mouse); + ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &Control::set_shortcut_context); + ClassDB::bind_method(D_METHOD("get_shortcut_context"), &Control::get_shortcut_context); + ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size); ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction); @@ -3206,6 +3237,9 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); + ADD_GROUP("Input", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); diff --git a/scene/gui/control.h b/scene/gui/control.h index ee6443c81c6..7e997ef3d51 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -218,6 +218,8 @@ private: NodePath focus_next; NodePath focus_prev; + ObjectID shortcut_context; + // Theming. ThemeOwner *theme_owner = nullptr; @@ -487,6 +489,10 @@ public: void warp_mouse(const Point2 &p_position); + bool is_focus_owner_in_shortcut_context() const; + void set_shortcut_context(const Node *p_node); + Node *get_shortcut_context() const; + // Drag and drop handling. virtual void set_drag_forwarding(Object *p_target); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 742042f65fb..fa1b40739c5 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) { void MenuBar::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (disable_shortcuts) { return; } @@ -175,34 +171,6 @@ void MenuBar::shortcut_input(const Ref &p_event) { } } -void MenuBar::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } -} - -Node *MenuBar::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to(ctx_obj); - - return ctx_node; -} - -bool MenuBar::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); -} - void MenuBar::_popup_visibility_changed(bool p_visible) { if (!p_visible) { active_menu = -1; @@ -694,16 +662,12 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context); - ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index f7ef19e98b4..c057a7c96fa 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -118,8 +118,6 @@ class MenuBar : public Control { void _clear_menu(); void _update_menu(); - bool _is_focus_owner_in_shortcut_context() const; - protected: virtual void shortcut_input(const Ref &p_event) override; @@ -170,9 +168,6 @@ public: void set_menu_hidden(int p_menu, bool p_hidden); bool is_menu_hidden(int p_menu) const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; - PopupMenu *get_menu_popup(int p_menu) const; virtual void get_translatable_strings(List *p_strings) const override; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 78aeab94577..786f23873ef 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -36,10 +36,6 @@ void MenuButton::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (disable_shortcuts) { return; } diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 64eb3b879b6..270e5b7025b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -44,6 +44,7 @@ #include "node.h" #include "scene/animation/tween.h" #include "scene/debugger/scene_debugger.h" +#include "scene/gui/control.h" #include "scene/main/multiplayer_api.h" #include "scene/main/viewport.h" #include "scene/resources/environment.h" @@ -895,6 +896,8 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal call_lock++; + Vector no_context_nodes; + for (int i = gr_node_count - 1; i >= 0; i--) { if (p_viewport->is_input_handled()) { break; @@ -913,9 +916,22 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal case CALL_INPUT_TYPE_INPUT: n->_call_input(p_input); break; - case CALL_INPUT_TYPE_SHORTCUT_INPUT: + case CALL_INPUT_TYPE_SHORTCUT_INPUT: { + const Control *c = Object::cast_to(n); + if (c) { + // If calling shortcut input on a control, ensure it respects the shortcut context. + // Shortcut context (based on focus) only makes sense for controls (UI), so don't need to worry about it for nodes + if (c->get_shortcut_context() == nullptr) { + no_context_nodes.append(n); + continue; + } + if (!c->is_focus_owner_in_shortcut_context()) { + continue; + } + } n->_call_shortcut_input(p_input); break; + } case CALL_INPUT_TYPE_UNHANDLED_INPUT: n->_call_unhandled_input(p_input); break; @@ -925,6 +941,10 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } } + for (Node *n : no_context_nodes) { + n->_call_shortcut_input(p_input); + } + call_lock--; if (call_lock == 0) { call_skip.clear();