Merge pull request #110250 from YeldhamDev/i_just_cant_keep_focused

Hide `Control` focus when given via mouse input
This commit is contained in:
Thaddeus Crews 2025-09-22 13:28:44 -05:00
commit be421bcdd4
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
53 changed files with 229 additions and 105 deletions

View file

@ -1675,6 +1675,7 @@ ProjectSettings::ProjectSettings() {
#endif
GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true);
GLOBAL_DEF("gui/common/always_show_focus_state", false);
GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true);
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/vsync/frame_queue_size", PROPERTY_HINT_RANGE, "2,3,1"), 2);

View file

@ -11,7 +11,7 @@
Godot propagates input events via viewports. Each [Viewport] is responsible for propagating [InputEvent]s to their child nodes. As the [member SceneTree.root] is a [Window], this already happens automatically for all UI elements in your game.
Input events are propagated through the [SceneTree] from the root node to all child nodes by calling [method Node._input]. For UI elements specifically, it makes more sense to override the virtual method [method _gui_input], which filters out unrelated input events, such as by checking z-order, [member mouse_filter], focus, or if the event was inside of the control's bounding box.
Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it.
Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus.
Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Focus will not be represented visually if gained via mouse/touch input, only appearing with keyboard/gamepad input (for accessibility), or via [method grab_focus].
Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button.
[Theme] resources change the control's appearance. The [member theme] of a [Control] node affects all of its direct and indirect children (as long as a chain of controls is uninterrupted). To override some of the theme items, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can also override theme items in the Inspector.
[b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class.
@ -618,15 +618,19 @@
</method>
<method name="grab_focus">
<return type="void" />
<param index="0" name="hide_focus" type="bool" default="false" />
<description>
Steal the focus from another control and become the focused control (see [member focus_mode]).
If [param hide_focus] is [code]true[/code], the control will not visually show its focused state. Has no effect if [member ProjectSettings.gui/common/always_show_focus_state] is set to [code]true[/code].
[b]Note:[/b] Using this method together with [method Callable.call_deferred] makes it more reliable, especially when called inside [method Node._ready].
</description>
</method>
<method name="has_focus" qualifiers="const">
<return type="bool" />
<param index="0" name="ignore_hidden_focus" type="bool" default="false" />
<description>
Returns [code]true[/code] if this is the current focused control. See [member focus_mode].
If [param ignore_hidden_focus] is [code]true[/code], controls that have their focus hidden will always return [code]false[/code]. Hidden focus happens automatically when controls gain focus via mouse input, or manually using [method grab_focus] with [code]hide_focus[/code] set to [code]true[/code].
</description>
</method>
<method name="has_theme_color" qualifiers="const">

View file

@ -1177,6 +1177,9 @@
<member name="filesystem/import/fbx2gltf/enabled.web" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
</member>
<member name="gui/common/always_show_focus_state" type="bool" setter="" getter="" default="false">
If [code]true[/code], [Control]s will always show if they're focused, even if said focus was gained via mouse/touch input.
</member>
<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
</member>

View file

@ -303,7 +303,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));
const int v_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));
if (has_focus()) {
if (has_focus(true)) {
draw_rect(Rect2(Point2(), get_size()), focus_color, false, Math::round(EDSCALE));
}

View file

@ -448,7 +448,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
Size2 s = blend_space_draw->get_size();
if (blend_space_draw->has_focus()) {
if (blend_space_draw->has_focus(true)) {
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
}

View file

@ -952,7 +952,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
travel_path = playback->get_travel_path();
}
if (state_machine_draw->has_focus()) {
if (state_machine_draw->has_focus(true)) {
state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), theme_cache.focus_color, false);
}
int sep = 3 * EDSCALE;

View file

@ -775,13 +775,13 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa
item = item->get_next();
}
if (p_grab_focus) {
tree->grab_focus();
tree->grab_focus(true);
}
} else {
(*directory_ptr)->select(0);
_update_file_list(false);
if (p_grab_focus) {
files->grab_focus();
files->grab_focus(true);
}
}
tree->ensure_cursor_is_visible();
@ -1397,7 +1397,7 @@ void FileSystemDock::_update_history() {
if (tree->is_visible()) {
_update_tree(get_uncollapsed_paths());
tree->grab_focus();
tree->grab_focus(true);
}
if (file_list_vb->is_visible()) {
@ -3537,7 +3537,7 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button
if (p_button != MouseButton::RIGHT) {
return;
}
tree->grab_focus();
tree->grab_focus(true);
// Right click is pressed in the tree.
Vector<String> paths = _tree_get_selected(false);

View file

@ -1553,7 +1553,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
editor_selection->clear();
editor_selection->add_node(new_node);
scene_tree->get_scene_tree()->grab_focus();
scene_tree->get_scene_tree()->grab_focus(true);
} break;
default: {
@ -3146,7 +3146,7 @@ void SceneTreeDock::_create() {
undo_redo->commit_action();
}
scene_tree->get_scene_tree()->grab_focus();
scene_tree->get_scene_tree()->grab_focus(true);
}
void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node) {

View file

@ -599,10 +599,10 @@ void FindReplaceBar::_show_search(bool p_with_replace, bool p_show_only) {
if (focus_replace) {
search_text->deselect();
callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred();
callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(false);
} else {
replace_text->deselect();
callable_mp((Control *)search_text, &Control::grab_focus).call_deferred();
callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(false);
}
if (on_one_line) {

View file

@ -563,7 +563,7 @@ void CreateDialog::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible.
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(false); // Still not visible.
search_box->select_all();
} else {
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));

View file

@ -483,9 +483,9 @@ void EditorFileDialog::_post_popup() {
set_current_dir(current);
if (mode == FILE_MODE_SAVE_FILE) {
file->grab_focus();
file->grab_focus(true);
} else {
item_list->grab_focus();
item_list->grab_focus(true);
}
bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR;

View file

@ -162,7 +162,7 @@ void EditorSpinSlider::_grab_end() {
grabbing_spinner = false;
emit_signal("ungrabbed");
} else {
_focus_entered();
_focus_entered(true);
}
grabbing_spinner_attempt = false;
@ -204,7 +204,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
grabbing_ratio = get_as_ratio();
grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
}
grab_focus();
grab_focus(true);
emit_signal("grabbed");
} else {
grabbing_grabber = false;
@ -340,7 +340,7 @@ void EditorSpinSlider::_draw_spin_slider() {
}
}
if (has_focus()) {
if (has_focus(true)) {
Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
draw_style_box(focus, Rect2(Vector2(), size));
}
@ -672,7 +672,7 @@ bool EditorSpinSlider::is_grabbing() const {
return grabbing_grabber || grabbing_spinner;
}
void EditorSpinSlider::_focus_entered() {
void EditorSpinSlider::_focus_entered(bool p_hide_focus) {
if (read_only) {
return;
}
@ -683,7 +683,7 @@ void EditorSpinSlider::_focus_entered() {
value_input->set_focus_next(find_next_valid_focus()->get_path());
value_input->set_focus_previous(find_prev_valid_focus()->get_path());
callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred();
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred();
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(p_hide_focus);
callable_mp(value_input, &LineEdit ::select_all).call_deferred();
emit_signal("value_focus_entered");
}

View file

@ -98,7 +98,7 @@ protected:
static void _bind_methods();
void _grabber_mouse_entered();
void _grabber_mouse_exited();
void _focus_entered();
void _focus_entered(bool p_hide_focus = false);
public:
String get_tooltip(const Point2 &p_pos) const override;

View file

@ -942,9 +942,9 @@ void EditorProperty::grab_focus(int p_focusable) {
if (p_focusable >= 0) {
ERR_FAIL_INDEX(p_focusable, focusables.size());
focusables[p_focusable]->grab_focus();
focusables[p_focusable]->grab_focus(true);
} else {
focusables[0]->grab_focus();
focusables[0]->grab_focus(true);
}
}
@ -956,7 +956,7 @@ void EditorProperty::select(int p_focusable) {
if (p_focusable >= 0) {
ERR_FAIL_INDEX(p_focusable, focusables.size());
focusables[p_focusable]->grab_focus();
focusables[p_focusable]->grab_focus(true);
} else {
selected = true;
queue_redraw();

View file

@ -2904,7 +2904,7 @@ void EditorPropertyNodePath::_menu_option(int p_idx) {
const NodePath &np = _get_node_path();
edit->set_text(String(np));
edit->show();
callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
callable_mp((Control *)edit, &Control::grab_focus).call_deferred(false);
} break;
case ACTION_SELECT: {

View file

@ -813,7 +813,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
renderer_container->hide();
default_files_container->hide();
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred();
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false);
callable_mp(project_name, &LineEdit::select_all).call_deferred();
} else {
if (p_reset_name) {
@ -882,7 +882,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
renderer_container->show();
default_files_container->show();
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred();
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false);
callable_mp(project_name, &LineEdit::select_all).call_deferred();
} else if (mode == MODE_INSTALL) {
set_title(TTR("Install Project:") + " " + zip_title);
@ -895,7 +895,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
renderer_container->hide();
default_files_container->hide();
callable_mp((Control *)project_path, &Control::grab_focus).call_deferred();
callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(false);
} else if (mode == MODE_DUPLICATE) {
set_title(TTRC("Duplicate Project"));
set_ok_button_text(TTRC("Duplicate"));
@ -908,7 +908,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
edit_check_box->hide();
}
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred();
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false);
callable_mp(project_name, &LineEdit::select_all).call_deferred();
}

View file

@ -363,7 +363,8 @@ void ProjectManager::_select_main_view(int p_id) {
if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) {
// Automatically grab focus when the user moves from the Templates tab
// back to the Projects tab.
search_box->grab_focus();
// Needs to be deferred, otherwise the focus outline is always drawn.
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(true);
}
// The Templates tab's search field is focused on display in the asset

View file

@ -415,7 +415,7 @@ void EmbeddedProcess::_check_focused_process_id() {
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
modal_window->set_mode(Window::MODE_WINDOWED);
}
callable_mp(modal_window, &Window::grab_focus).call_deferred();
callable_mp(modal_window, &Window::grab_focus).call_deferred(false);
}
}
}

View file

@ -997,7 +997,7 @@ void TileSourceInspectorPlugin::_show_id_edit_dialog(Object *p_for_source) {
edited_source = p_for_source;
id_input->set_value(p_for_source->get("id"));
id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE);
callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred();
callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(false);
}
void TileSourceInspectorPlugin::_confirm_change_id() {

View file

@ -2796,7 +2796,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
// Grab focus
if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
callable_mp((Control *)viewport, &Control::grab_focus).call_deferred();
callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(false);
}
}

View file

@ -485,7 +485,7 @@ void ConnectDialog::_update_warning_label() {
}
void ConnectDialog::_post_popup() {
callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred();
callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(false);
callable_mp(dst_method, &LineEdit::select_all).call_deferred();
}

View file

@ -66,7 +66,7 @@ void SceneCreateDialog::config(const String &p_dir) {
directory = p_dir;
root_name_edit->set_text("");
scene_name_edit->set_text("");
callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred();
callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(false);
validation_panel->update();
Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();

View file

@ -2279,7 +2279,7 @@ void SceneTreeDialog::_notification(int p_what) {
tree->update_tree();
// Select the search bar by default.
callable_mp((Control *)filter, &Control::grab_focus).call_deferred();
callable_mp((Control *)filter, &Control::grab_focus).call_deferred(false);
}
} break;

View file

@ -467,16 +467,16 @@ void FindInFilesDialog::set_search_text(const String &text) {
_search_text_line_edit->set_text(text);
_on_search_text_modified(text);
}
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred();
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(false);
_search_text_line_edit->select_all();
} else if (_mode == REPLACE_MODE) {
if (!text.is_empty()) {
_search_text_line_edit->set_text(text);
callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred();
callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(false);
_replace_text_line_edit->select_all();
_on_search_text_modified(text);
} else {
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred();
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(false);
_search_text_line_edit->select_all();
}
}

View file

@ -1728,27 +1728,27 @@ void ScriptTextEditor::_edit_option(int p_op) {
switch (p_op) {
case EDIT_UNDO: {
tx->undo();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_REDO: {
tx->redo();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_CUT: {
tx->cut();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_COPY: {
tx->copy();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_PASTE: {
tx->paste();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_SELECT_ALL: {
tx->select_all();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_MOVE_LINE_UP: {
code_editor->get_text_editor()->move_lines_up();

View file

@ -362,27 +362,27 @@ void TextEditor::_edit_option(int p_op) {
switch (p_op) {
case EDIT_UNDO: {
tx->undo();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_REDO: {
tx->redo();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_CUT: {
tx->cut();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_COPY: {
tx->copy();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_PASTE: {
tx->paste();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_SELECT_ALL: {
tx->select_all();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
} break;
case EDIT_MOVE_LINE_UP: {
code_editor->get_text_editor()->move_lines_up();

View file

@ -787,7 +787,7 @@ void TextShaderEditor::_menu_option(int p_option) {
} break;
}
if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred();
callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(false);
}
}

View file

@ -7,3 +7,10 @@ should instead be used to justify these changes and describe how users should wo
Add new entries at the end of the file.
## Changes between 4.5-stable and 4.6-stable
GH-110250
---------
Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/grab_focus': arguments
Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/has_focus': arguments
Optional argument added. Compatibility methods registered.

View file

@ -251,7 +251,7 @@ void Button::_notification(int p_what) {
style->draw(ci, Rect2(Point2(), size));
}
if (has_focus()) {
if (has_focus(true)) {
theme_cache.focus->draw(ci, Rect2(Point2(), size));
}
@ -315,7 +315,7 @@ void Button::_notification(int p_what) {
switch (get_draw_mode()) {
case DRAW_NORMAL: {
// Focus colors only take precedence over normal state.
if (has_focus()) {
if (has_focus(true)) {
font_color = theme_cache.font_focus_color;
if (has_theme_color(SNAME("icon_focus_color"))) {
icon_modulate_color = theme_cache.icon_focus_color;

View file

@ -364,7 +364,7 @@ void ColorPicker::finish_shaders() {
}
void ColorPicker::set_focus_on_line_edit() {
callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(false);
}
void ColorPicker::set_focus_on_picker_shape() {
@ -1491,7 +1491,7 @@ void ColorPicker::_sample_draw() {
sample->draw_rect(rect_new, color);
if (display_old_color && !old_color.is_equal_approx(color) && sample->has_focus()) {
if (display_old_color && !old_color.is_equal_approx(color) && sample->has_focus(true)) {
RID ci = sample->get_canvas_item();
theme_cache.sample_focus->draw(ci, rect_old);
}
@ -2663,7 +2663,7 @@ void ColorPresetButton::_notification(int p_what) {
WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead.");
}
if (has_focus()) {
if (has_focus(true)) {
RID ci = get_canvas_item();
theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
}

View file

@ -84,7 +84,7 @@ void ColorPickerShape::cancel_event() {
}
void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) {
if (!p_control->has_focus()) {
if (!p_control->has_focus(true)) {
return;
}
@ -103,7 +103,7 @@ void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect)
}
void ColorPickerShape::draw_focus_circle(Control *p_control) {
if (!p_control->has_focus()) {
if (!p_control->has_focus(true)) {
return;
}

View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* control.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
bool Control::_has_focus_bind_compat_110250() const {
return has_focus(false);
}
void Control::_grab_focus_bind_compat_110250() {
return grab_focus(false);
}
void Control::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("has_focus"), &Control::_has_focus_bind_compat_110250);
ClassDB::bind_compatibility_method(D_METHOD("grab_focus"), &Control::_grab_focus_bind_compat_110250);
}
#endif // DISABLE_DEPRECATED

View file

@ -29,6 +29,7 @@
/**************************************************************************/
#include "control.h"
#include "control.compat.inc"
#include "container.h"
#include "core/config/project_settings.h"
@ -2315,12 +2316,12 @@ void Control::_propagate_focus_behavior_recursive_recursively(bool p_enabled, bo
}
}
bool Control::has_focus() const {
bool Control::has_focus(bool p_ignore_hidden_focus) const {
ERR_READ_THREAD_GUARD_V(false);
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this);
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this, p_ignore_hidden_focus);
}
void Control::grab_focus() {
void Control::grab_focus(bool p_hide_focus) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
@ -2329,7 +2330,7 @@ void Control::grab_focus() {
return;
}
get_viewport()->_gui_control_grab_focus(this);
get_viewport()->_gui_control_grab_focus(this, p_hide_focus);
}
void Control::grab_click_focus() {
@ -4002,8 +4003,8 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_focus_mode_with_override"), &Control::get_focus_mode_with_override);
ClassDB::bind_method(D_METHOD("set_focus_behavior_recursive", "focus_behavior_recursive"), &Control::set_focus_behavior_recursive);
ClassDB::bind_method(D_METHOD("get_focus_behavior_recursive"), &Control::get_focus_behavior_recursive);
ClassDB::bind_method(D_METHOD("has_focus"), &Control::has_focus);
ClassDB::bind_method(D_METHOD("grab_focus"), &Control::grab_focus);
ClassDB::bind_method(D_METHOD("has_focus", "ignore_hidden_focus"), &Control::has_focus, DEFVAL(false));
ClassDB::bind_method(D_METHOD("grab_focus", "hide_focus"), &Control::grab_focus, DEFVAL(false));
ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus);
ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus);
ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus);

View file

@ -394,6 +394,12 @@ protected:
void _accessibility_action_hide_tooltip(const Variant &p_data);
void _accessibility_action_scroll_into_view(const Variant &p_data);
#ifndef DISABLE_DEPRECATED
bool _has_focus_bind_compat_110250() const;
void _grab_focus_bind_compat_110250();
static void _bind_compatibility_methods();
#endif //DISABLE_DEPRECATED
// Exposed virtual methods.
GDVIRTUAL1RC(bool, _has_point, Vector2)
@ -592,8 +598,8 @@ public:
FocusMode get_focus_mode_with_override() const;
void set_focus_behavior_recursive(FocusBehaviorRecursive p_focus_behavior_recursive);
FocusBehaviorRecursive get_focus_behavior_recursive() const;
bool has_focus() const;
void grab_focus();
bool has_focus(bool p_ignore_hidden_focus = false) const;
void grab_focus(bool p_hide_focus = false);
void grab_click_focus();
void release_focus();

View file

@ -421,9 +421,9 @@ void FileDialog::_save_confirm_pressed() {
void FileDialog::_post_popup() {
ConfirmationDialog::_post_popup();
if (mode == FILE_MODE_SAVE_FILE) {
filename_edit->grab_focus();
filename_edit->grab_focus(true);
} else {
file_list->grab_focus();
file_list->grab_focus(true);
}
set_process_shortcut_input(true);
@ -2035,7 +2035,7 @@ void FileDialog::set_show_filename_filter(bool p_show) {
filename_filter->grab_focus();
} else {
if (filename_filter->has_focus()) {
callable_mp((Control *)file_list, &Control::grab_focus).call_deferred();
callable_mp((Control *)file_list, &Control::grab_focus).call_deferred(false);
}
}
show_filename_filter = p_show;

View file

@ -317,7 +317,7 @@ void FoldableContainer::_notification(int p_what) {
_draw_flippable_stylebox(theme_cache.panel_style, panel_rect);
}
if (has_focus()) {
if (has_focus(true)) {
Rect2 focus_rect = folded ? title_rect : Rect2(Point2(), size);
_draw_flippable_stylebox(theme_cache.focus_style, focus_rect);
}

View file

@ -861,7 +861,7 @@ void GraphEdit::_notification(int p_what) {
// Draw background fill.
draw_style_box(theme_cache.panel, Rect2(Point2(), get_size()));
if (has_focus()) {
if (has_focus(true)) {
draw_style_box(theme_cache.panel_focus, Rect2(Point2(), get_size()));
}

View file

@ -1383,7 +1383,7 @@ void ItemList::_notification(int p_what) {
Ref<StyleBox> sbsel;
Ref<StyleBox> cursor;
if (has_focus()) {
if (has_focus(true)) {
sbsel = theme_cache.selected_focus_style;
cursor = theme_cache.cursor_focus_style;
} else {
@ -1507,7 +1507,7 @@ void ItemList::_notification(int p_what) {
draw_style_box(sbsel, r);
}
if (should_draw_hovered_selected_bg) {
if (has_focus()) {
if (has_focus(true)) {
draw_style_box(theme_cache.hovered_selected_focus_style, r);
} else {
draw_style_box(theme_cache.hovered_selected_style, r);
@ -1695,7 +1695,7 @@ void ItemList::_notification(int p_what) {
draw_style_box(cursor, cursor_rcache);
}
if (has_focus()) {
if (has_focus(true)) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true);
size.x -= (scroll_bar_h->get_max() - scroll_bar_h->get_page());
draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));

View file

@ -762,7 +762,7 @@ void Label::_notification(int p_what) {
Vector<LabelSettings::StackedShadowData> stacked_shadow_datas = has_settings ? settings->get_stacked_shadow_data() : Vector<LabelSettings::StackedShadowData>();
bool rtl_layout = is_layout_rtl();
if (has_focus()) {
if (has_focus(true)) {
theme_cache.focus_style->draw(ci, Rect2(Point2(0, 0), get_size()));
} else {
theme_cache.normal_style->draw(ci, Rect2(Point2(0, 0), get_size()));

View file

@ -1343,7 +1343,7 @@ void LineEdit::_notification(int p_what) {
style->draw(ci, Rect2(Point2(), size));
}
if (has_focus()) {
if (has_focus(true)) {
theme_cache.focus->draw(ci, Rect2(Point2(), size));
}

View file

@ -191,7 +191,7 @@ void LinkButton::_notification(int p_what) {
switch (get_draw_mode()) {
case DRAW_NORMAL: {
if (has_focus()) {
if (has_focus(true)) {
color = theme_cache.font_focus_color;
} else {
color = theme_cache.font_color;
@ -222,7 +222,7 @@ void LinkButton::_notification(int p_what) {
} break;
}
if (has_focus()) {
if (has_focus(true)) {
Ref<StyleBox> style = theme_cache.focus;
style->draw(ci, Rect2(Point2(), size));
}

View file

@ -493,7 +493,7 @@ void MenuBar::_draw_menu_item(int p_index) {
style->draw(ci, item_rect);
}
// Focus colors only take precedence over normal state.
if (has_focus()) {
if (has_focus(true)) {
color = theme_cache.font_focus_color;
} else {
color = theme_cache.font_color;

View file

@ -114,7 +114,7 @@ void OptionButton::_notification(int p_what) {
clr = theme_cache.font_disabled_color;
break;
default:
if (has_focus()) {
if (has_focus(true)) {
clr = theme_cache.font_focus_color;
} else {
clr = theme_cache.font_color;

View file

@ -2469,7 +2469,7 @@ void RichTextLabel::_notification(int p_what) {
draw_style_box(theme_cache.normal_style, Rect2(Point2(), size));
if (has_focus()) {
if (has_focus(true)) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);

View file

@ -279,7 +279,7 @@ void ScrollBar::_notification(int p_what) {
area.height -= incr->get_height() + decr->get_height();
}
if (has_focus()) {
if (has_focus(true)) {
theme_cache.scroll_focus_style->draw(ci, Rect2(ofs, area));
} else {
theme_cache.scroll_style->draw(ci, Rect2(ofs, area));

View file

@ -316,7 +316,7 @@ void ScrollContainer::_gui_focus_changed(Control *p_control) {
ensure_control_visible(p_control);
}
if (draw_focus_border) {
const bool _should_draw_focus_border = has_focus() || child_has_focus();
const bool _should_draw_focus_border = has_focus(true) || child_has_focus();
if (focus_border_is_drawn != _should_draw_focus_border) {
queue_redraw();
}
@ -484,7 +484,7 @@ void ScrollContainer::_notification(int p_what) {
case NOTIFICATION_DRAW: {
draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size()));
focus_border_is_drawn = draw_focus_border && (has_focus() || child_has_focus());
focus_border_is_drawn = draw_focus_border && (has_focus(true) || child_has_focus());
focus_panel->set_visible(focus_border_is_drawn);
} break;
@ -815,7 +815,7 @@ bool ScrollContainer::get_draw_focus_border() {
bool ScrollContainer::child_has_focus() {
const Control *focus_owner = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
return focus_owner && is_ancestor_of(focus_owner);
return focus_owner && focus_owner->has_focus(true) && is_ancestor_of(focus_owner);
}
ScrollContainer::ScrollContainer() {

View file

@ -56,7 +56,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
Ref<Texture2D> grabber;
if (mouse_inside || has_focus()) {
if (mouse_inside || has_focus(true)) {
grabber = theme_cache.grabber_hl_icon;
} else {
grabber = theme_cache.grabber_icon;
@ -275,7 +275,7 @@ void Slider::_notification(int p_what) {
Ref<StyleBox> style = theme_cache.slider_style;
Ref<Texture2D> tick = theme_cache.tick_icon;
bool highlighted = editable && (mouse_inside || has_focus());
bool highlighted = editable && (mouse_inside || has_focus(true));
Ref<Texture2D> grabber;
if (editable) {
if (highlighted) {

View file

@ -543,7 +543,7 @@ void TabBar::_notification(int p_what) {
if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) {
Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style;
_draw_tab(sb, theme_cache.font_selected_color, current, rtl ? (size.width - tabs[current].ofs_cache - tabs[current].size_cache) : tabs[current].ofs_cache, has_focus());
_draw_tab(sb, theme_cache.font_selected_color, current, rtl ? (size.width - tabs[current].ofs_cache - tabs[current].size_cache) : tabs[current].ofs_cache, has_focus(true));
}
if (buttons_visible) {

View file

@ -945,7 +945,7 @@ void TextEdit::_notification(int p_what) {
theme_cache.style_readonly->draw(ci, Rect2(Point2(), size));
draw_caret = is_drawing_caret_when_editable_disabled();
}
if (has_focus()) {
if (has_focus(true)) {
theme_cache.style_focus->draw(ci, Rect2(Point2(), size));
}

View file

@ -168,7 +168,7 @@ void TextureButton::_notification(int p_what) {
Point2 ofs;
Size2 size;
bool draw_focus = (has_focus() && focused.is_valid());
bool draw_focus = (has_focus(true) && focused.is_valid());
// If no other texture is valid, try using focused texture.
bool draw_focus_only = draw_focus && texdraw.is_null();

View file

@ -2327,13 +2327,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[0].selected) {
if (is_row_hovered) {
if (has_focus()) {
if (has_focus(true)) {
theme_cache.hovered_selected_focus->draw(ci, row_rect);
} else {
theme_cache.hovered_selected->draw(ci, row_rect);
}
} else {
if (has_focus()) {
if (has_focus(true)) {
theme_cache.selected_focus->draw(ci, row_rect);
} else {
theme_cache.selected->draw(ci, row_rect);
@ -2375,13 +2375,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (p_item->cells[i].selected) {
if (is_cell_hovered) {
if (has_focus()) {
if (has_focus(true)) {
theme_cache.hovered_selected_focus->draw(ci, r);
} else {
theme_cache.hovered_selected->draw(ci, r);
}
} else {
if (has_focus()) {
if (has_focus(true)) {
theme_cache.selected_focus->draw(ci, r);
} else {
theme_cache.selected->draw(ci, r);
@ -2675,7 +2675,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (is_layout_rtl()) {
cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x;
}
if (has_focus()) {
if (has_focus(true)) {
theme_cache.cursor->draw(ci, cell_rect);
} else {
theme_cache.cursor_unfocus->draw(ci, cell_rect);
@ -5080,7 +5080,7 @@ void Tree::_notification(int p_what) {
// Draw the focus outline last, so that it is drawn in front of the section headings.
// Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
if (has_focus()) {
if (has_focus(true)) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);

View file

@ -531,6 +531,22 @@ void Viewport::_update_viewport_path() {
}
}
bool Viewport::_can_hide_focus_state() {
return Engine::get_singleton()->is_editor_hint() || !GLOBAL_GET_CACHED(bool, "gui/common/always_show_focus_state");
}
void Viewport::_on_settings_changed() {
if (!gui.hide_focus && _can_hide_focus_state()) {
return;
}
gui.hide_focus = false;
// Show previously hidden focus.
if (gui.key_focus) {
gui.key_focus->queue_redraw();
}
}
void Viewport::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
@ -1914,6 +1930,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
gui.mouse_focus = gui_find_control(mpos);
if (!gui.mouse_focus) {
// Focus should be hidden on click even if the focus holder didn't change.
if (gui.key_focus && mb->get_button_index() == MouseButton::LEFT && _can_hide_focus_state()) {
gui.hide_focus = true;
gui.key_focus->queue_redraw();
}
return;
}
@ -1946,8 +1968,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (control->_is_focusable()) {
// Grabbing unhovered focus can cause issues when mouse is dragged
// with another button held down.
if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) {
control->grab_focus();
if (gui.mouse_over_hierarchy.has(control)) {
// Hide the focus when it comes from a click.
control->grab_focus(true);
}
break;
}
@ -2301,6 +2324,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (from && p_event->is_pressed()) {
Control *next = nullptr;
bool show_focus = false;
Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
if (joypadmotion_event.is_valid()) {
@ -2308,10 +2332,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_next"), p_event)) {
next = from->find_next_valid_focus();
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_prev"), p_event)) {
next = from->find_prev_valid_focus();
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed_by_event(SNAME("ui_accessibility_drag_and_drop"), p_event)) {
@ -2324,26 +2350,32 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed_by_event(SNAME("ui_up"), p_event)) {
next = from->_get_focus_neighbor(SIDE_TOP);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed_by_event(SNAME("ui_left"), p_event)) {
next = from->_get_focus_neighbor(SIDE_LEFT);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed_by_event(SNAME("ui_right"), p_event)) {
next = from->_get_focus_neighbor(SIDE_RIGHT);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed_by_event(SNAME("ui_down"), p_event)) {
next = from->_get_focus_neighbor(SIDE_BOTTOM);
show_focus = true;
}
} else {
if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) {
next = from->find_next_valid_focus();
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) {
next = from->find_prev_valid_focus();
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop"), true, true)) {
@ -2356,23 +2388,32 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) {
next = from->_get_focus_neighbor(SIDE_TOP);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) {
next = from->_get_focus_neighbor(SIDE_LEFT);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) {
next = from->_get_focus_neighbor(SIDE_RIGHT);
show_focus = true;
}
if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) {
next = from->_get_focus_neighbor(SIDE_BOTTOM);
show_focus = true;
}
}
if (next) {
next->grab_focus();
set_input_as_handled();
} else if (show_focus && gui.hide_focus && gui.key_focus) {
// Show focus even it the holder didn't change, as visual feedback.
gui.hide_focus = false;
gui.key_focus->queue_redraw();
}
}
}
@ -2655,18 +2696,26 @@ void Viewport::_gui_remove_focus_for_window(Node *p_window) {
}
}
bool Viewport::_gui_control_has_focus(const Control *p_control) {
return gui.key_focus == p_control;
bool Viewport::_gui_control_has_focus(const Control *p_control, bool p_ignore_hidden_focus) {
return (!p_ignore_hidden_focus || !gui.hide_focus) && gui.key_focus == p_control;
}
void Viewport::_gui_control_grab_focus(Control *p_control) {
void Viewport::_gui_control_grab_focus(Control *p_control, bool p_hide_focus) {
if (gui.key_focus && gui.key_focus == p_control) {
// No need for change.
// Only worry about the focus visibility change.
if (p_hide_focus != gui.hide_focus && _can_hide_focus_state()) {
gui.hide_focus = p_hide_focus;
p_control->queue_redraw();
}
return;
}
get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window());
if (p_control->is_inside_tree() && p_control->get_viewport() == this) {
gui.key_focus = p_control;
if (_can_hide_focus_state()) {
gui.hide_focus = p_hide_focus;
}
emit_signal(SNAME("gui_focus_changed"), p_control);
p_control->notification(Control::NOTIFICATION_FOCUS_ENTER);
p_control->queue_redraw();
@ -5386,6 +5435,8 @@ Viewport::Viewport() {
// Viewports can thus inherit physics interpolation OFF, which is unexpected.
// Setting to ON allows each viewport to have a fresh interpolation state.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON);
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Viewport::_on_settings_changed));
}
Viewport::~Viewport() {

View file

@ -330,6 +330,9 @@ private:
void _update_viewport_path();
bool _can_hide_focus_state();
void _on_settings_changed();
SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT;
SDFScale sdf_scale = SDF_SCALE_50_PERCENT;
@ -374,6 +377,7 @@ private:
Control *mouse_click_grabber = nullptr;
BitField<MouseButtonMask> mouse_focus_mask = MouseButtonMask::NONE;
Control *key_focus = nullptr;
bool hide_focus = false;
Control *mouse_over = nullptr;
LocalVector<Control *> mouse_over_hierarchy;
bool sending_mouse_enter_exit_notifications = false;
@ -459,8 +463,8 @@ private:
void _gui_remove_focus_for_window(Node *p_window);
void _gui_unfocus_control(Control *p_control);
bool _gui_control_has_focus(const Control *p_control);
void _gui_control_grab_focus(Control *p_control);
bool _gui_control_has_focus(const Control *p_control, bool p_ignore_hidden_focus = false);
void _gui_control_grab_focus(Control *p_control, bool p_hide_focus = false);
void _gui_grab_click_focus(Control *p_control);
void _post_gui_grab_click_focus();
void _gui_accept_event();