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.

This commit is contained in:
Eric M 2022-09-25 00:32:53 +10:00
parent fd4572cc45
commit a3ed9e6f2c
11 changed files with 64 additions and 92 deletions

View File

@ -68,9 +68,6 @@
<member name="shortcut" type="Shortcut" setter="set_shortcut" getter="get_shortcut">
[Shortcut] associated to the button.
</member>
<member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
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.
</member>
<member name="shortcut_in_tooltip" type="bool" setter="set_shortcut_in_tooltip" getter="is_shortcut_in_tooltip_enabled">
If [code]true[/code], the button will add information about its shortcut in the tooltip.
</member>

View File

@ -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.
</member>
<member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
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.
</member>
<member name="size" type="Vector2" setter="_set_size" getter="get_size" default="Vector2(0, 0)">
The size of the node's bounding rectangle, in pixels. [Container] nodes update this property automatically.
</member>

View File

@ -106,9 +106,6 @@
<member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true">
If [code]true[/code], [MenuBar] will use system global menu when supported.
</member>
<member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
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.
</member>
<member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1">
Position in the global menu to insert first [MenuBar] item at.
</member>

View File

@ -349,10 +349,6 @@ Ref<Shortcut> BaseButton::get_shortcut() const {
void BaseButton::shortcut_input(const Ref<InputEvent> &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<ButtonGroup> 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<Node>(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);

View File

@ -81,7 +81,6 @@ protected:
virtual void shortcut_input(const Ref<InputEvent> &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<ButtonGroup> &p_group);
Ref<ButtonGroup> get_button_group() const;
void set_shortcut_context(Node *p_node);
Node *get_shortcut_context() const;
BaseButton();
~BaseButton();
};

View File

@ -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<Node>(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");

View File

@ -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);

View File

@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) {
void MenuBar::shortcut_input(const Ref<InputEvent> &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<InputEvent> &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<Node>(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");

View File

@ -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<InputEvent> &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<String> *p_strings) const override;

View File

@ -36,10 +36,6 @@
void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!_is_focus_owner_in_shortcut_context()) {
return;
}
if (disable_shortcuts) {
return;
}

View File

@ -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<Node *> 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<Control>(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();