Backported the shortcut context system from godot4.

This commit is contained in:
Relintai 2023-09-07 12:57:34 +02:00
parent bd9abfc160
commit 4637f73099
4 changed files with 148 additions and 13 deletions

View File

@ -773,6 +773,14 @@
<member name="rect_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>
<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.
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.
</member>
<member name="shortcut_context_path" type="NodePath" setter="set_shortcut_context_path" getter="get_shortcut_context_path">
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.
</member>
<member name="size_flags_horizontal" type="int" setter="set_h_size_flags" getter="get_h_size_flags" default="1">
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.
</member>

View File

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

View File

@ -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<StringName, Ref<Texture>> icon_override;
HashMap<StringName, Ref<Shader>> shader_override;
HashMap<StringName, Ref<StyleBox>> 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;

View File

@ -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<Node *> 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<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() == 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_