diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index c028d3790..e4f8f3b1d 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -773,6 +773,14 @@ The size of the node's bounding rectangle, in pixels. [Container] nodes update this property automatically. + + 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. + Setting this will override the node [member shortcut_context_path] is pointing. You can get rid of this override by setting this back to null. + + + The [NodePath] for 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 shortcut_context] can be used to temporarily override this when needed. + Tells the parent [Container] nodes how they should resize and place the node on the X axis. Use one of the [enum SizeFlags] constants to change the flags. See the constants to learn what each does. diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 9366b3ad7..8100608cb 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2503,6 +2503,68 @@ void Control::warp_mouse(const Point2 &p_to_pos) { get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos)); } +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 { + if (unlikely(data.shortcut_context)) { + Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context); + Node *ctx_node = Object::cast_to(ctx_obj); + + return ctx_node; + } + + if (likely(data.shortcut_context_path.is_empty())) { + return NULL; + } + + if (data.shortcut_context_path_cache) { + Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context_path_cache); + + if (ctx_obj) { + Node *ctx_node = Object::cast_to(ctx_obj); + + return ctx_node; + } else { + data.shortcut_context_path_cache = ObjectID(); + } + } + + Node *n = get_node_or_null(data.shortcut_context_path); + + if (n) { + data.shortcut_context_path_cache = n->get_instance_id(); + } + + return n; +} + +void Control::set_shortcut_context_path(const NodePath &p_node_path) { + data.shortcut_context_path = p_node_path; + data.shortcut_context_path_cache = 0; +} +NodePath Control::get_shortcut_context_path() const { + return data.shortcut_context_path; +} + +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() : NULL; + + // 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_a_parent_of(vp_focus)); +} + bool Control::is_text_field() const { return false; } @@ -2817,6 +2879,12 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "to_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("set_shortcut_context_path", "path"), &Control::set_shortcut_context_path); + ClassDB::bind_method(D_METHOD("get_shortcut_context_path"), &Control::get_shortcut_context_path); + ClassDB::bind_method(D_METHOD("minimum_size_changed"), &Control::minimum_size_changed); ClassDB::bind_method(D_METHOD("_theme_changed"), &Control::_theme_changed); @@ -2881,6 +2949,8 @@ void Control::_bind_methods() { ADD_GROUP("Input", "input_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_pass_on_modal_close_click"), "set_pass_on_modal_close_click", "get_pass_on_modal_close_click"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "shortcut_context_path"), "set_shortcut_context_path", "get_shortcut_context_path"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NONE, "", 0), "set_shortcut_context", "get_shortcut_context"); ADD_GROUP("Size Flags", "size_flags_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags"); @@ -3007,7 +3077,9 @@ Control::Control() { data.margin[i] = 0; } data.focus_mode = FOCUS_NONE; - data.modal_prev_focus_owner = 0; + data.modal_prev_focus_owner = ObjectID(); + data.shortcut_context = ObjectID(); + data.shortcut_context_path_cache = ObjectID(); set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } diff --git a/scene/gui/control.h b/scene/gui/control.h index 2e9ea866c..f6e7109a8 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -199,6 +199,10 @@ private: NodePath focus_next; NodePath focus_prev; + ObjectID shortcut_context; + NodePath shortcut_context_path; + mutable ObjectID shortcut_context_path_cache; + HashMap> icon_override; HashMap> shader_override; HashMap> style_override; @@ -494,6 +498,14 @@ public: void warp_mouse(const Point2 &p_to_pos); + void set_shortcut_context(const Node *p_node); + Node *get_shortcut_context() const; + + void set_shortcut_context_path(const NodePath &p_node_path); + NodePath get_shortcut_context_path() const; + + bool is_focus_owner_in_shortcut_context() const; + virtual bool is_text_field() const; Control *get_root_parent_control() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 5aeb78f84..4568fcb4a 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -45,6 +45,7 @@ #include "scene/3d/spatial.h" #include "scene/animation/scene_tree_tween.h" #include "scene/debugger/script_debugger_remote.h" +#include "scene/gui/control.h" #include "scene/resources/dynamic_font.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" @@ -1279,6 +1280,8 @@ void SceneTree::_call_input_pause(const StringName &p_group, const CallInputType call_lock++; _THREAD_SAFE_UNLOCK_ + Vector no_context_nodes; + StringName method; switch (p_call_type) { @@ -1296,22 +1299,62 @@ void SceneTree::_call_input_pause(const StringName &p_group, const CallInputType break; } - for (int i = node_count - 1; i >= 0; i--) { - if (input_handled) { - break; + if (p_call_type != CALL_INPUT_TYPE_SHORTCUT_INPUT) { + for (int i = node_count - 1; i >= 0; i--) { + if (input_handled) { + break; + } + + Node *n = nodes[i]; + if (call_lock && call_skip.has(n)) { + continue; + } + + if (!n->can_process()) { + continue; + } + + n->call_multilevel(method, (const Variant **)v, 1); + //ERR_FAIL_COND(node_count != g.nodes.size()); + } + } else { + for (int i = node_count - 1; i >= 0; i--) { + if (input_handled) { + break; + } + + Node *n = nodes[i]; + if (call_lock && call_skip.has(n)) { + continue; + } + + if (!n->can_process()) { + continue; + } + + 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() == NULL) { + no_context_nodes.push_back(n); + continue; + } + if (!c->is_focus_owner_in_shortcut_context()) { + continue; + } + } + + n->call_multilevel(method, (const Variant **)v, 1); + //ERR_FAIL_COND(node_count != g.nodes.size()); } - Node *n = nodes[i]; - if (call_lock && call_skip.has(n)) { - continue; - } + int ncns = no_context_nodes.size(); - if (!n->can_process()) { - continue; + for (int i = 0; i < ncns; ++i) { + Node *n = no_context_nodes[i]; + n->call_multilevel(method, (const Variant **)v, 1); } - - n->call_multilevel(method, (const Variant **)v, 1); - //ERR_FAIL_COND(node_count != g.nodes.size()); } _THREAD_SAFE_LOCK_