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_