From 2ed77d5e53e8c7b8bc043452559b59130045a2a4 Mon Sep 17 00:00:00 2001 From: HolonProduction Date: Tue, 11 Mar 2025 12:46:46 +0100 Subject: [PATCH] Fix focus cycle through window --- scene/gui/control.cpp | 48 +++++++++++++++++++++++++++++++- scene/gui/dialogs.cpp | 2 +- tests/scene/test_control.h | 56 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 389c29a240b9..0dbc61e0b0ce 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2091,6 +2091,9 @@ Control *Control::find_next_valid_focus() const { Control *from = const_cast(this); + // Index of the current `Control` subtree within the containing `Window` + int window_next = -1; + while (true) { // Find next child. @@ -2121,6 +2124,25 @@ Control *Control::find_next_valid_focus() const { } next_child = next_child->data.parent_control; } + + Window *win = next_child == nullptr ? nullptr : next_child->data.parent_window; + if (win) { // Cycle through `Control` subtrees of the parent window + if (window_next == -1) { + window_next = next_child->get_index(); + ERR_FAIL_INDEX_V(window_next, win->get_child_count(), nullptr); + } + + for (int i = 1; i < win->get_child_count() + 1; i++) { + int next = Math::wrapi(window_next + i, 0, win->get_child_count()); + Control *c = Object::cast_to(win->get_child(next)); + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { + continue; + } + window_next = next; + next_child = c; + break; + } + } } } @@ -2172,6 +2194,9 @@ Control *Control::find_prev_valid_focus() const { Control *from = const_cast(this); + // Index of the current `Control` subtree within the containing `Window` + int window_prev = -1; + while (true) { // Find prev child. @@ -2180,7 +2205,28 @@ Control *Control::find_prev_valid_focus() const { if (from->is_set_as_top_level() || !from->data.parent_control) { // Find last of the children. - prev_child = _prev_control(from); // Wrap start here. + Window *win = from->data.parent_window; + if (win) { // Cycle through `Control` subtrees of the parent window + if (window_prev == -1) { + window_prev = from->get_index(); + ERR_FAIL_INDEX_V(window_prev, win->get_child_count(), nullptr); + } + + for (int i = 1; i < win->get_child_count() + 1; i++) { + int prev = Math::wrapi(window_prev - i, 0, win->get_child_count()); + Control *c = Object::cast_to(win->get_child(prev)); + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { + continue; + } + window_prev = prev; + prev_child = _prev_control(c); + break; + } + } + + if (!prev_child) { + prev_child = _prev_control(from); // Wrap start here. + } } else { for (int i = (from->get_index() - 1); i >= 0; i--) { diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 6c21318ea83a..c002c309765c 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -438,7 +438,7 @@ AcceptDialog::AcceptDialog() { message_label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); add_child(message_label, false, INTERNAL_MODE_FRONT); - add_child(buttons_hbox, false, INTERNAL_MODE_FRONT); + add_child(buttons_hbox, false, INTERNAL_MODE_BACK); buttons_hbox->add_spacer(); ok_button = memnew(Button); diff --git a/tests/scene/test_control.h b/tests/scene/test_control.h index 69aa6f69b38f..2d72e3545f8f 100644 --- a/tests/scene/test_control.h +++ b/tests/scene/test_control.h @@ -143,8 +143,10 @@ TEST_CASE("[SceneTree][Control] Focus") { } TEST_CASE("[SceneTree][Control] Find next/prev valid focus") { + Node *intermediate = memnew(Node); Control *ctrl = memnew(Control); - SceneTree::get_singleton()->get_root()->add_child(ctrl); + intermediate->add_child(ctrl); + SceneTree::get_singleton()->get_root()->add_child(intermediate); SUBCASE("[SceneTree][Control] In FOCUS_CLICK mode") { ctrl->set_focus_mode(Control::FocusMode::FOCUS_CLICK); @@ -161,9 +163,56 @@ TEST_CASE("[SceneTree][Control] Find next/prev valid focus") { CHECK_UNARY(ctrl->has_focus()); } - SUBCASE("[SceneTree][Control] Has a sibling control but the parent node is not a control") { + SUBCASE("[SceneTree][Control] Has a sibling control and the parent is a window") { + Control *ctrl1 = memnew(Control); + Control *ctrl2 = memnew(Control); + Control *ctrl3 = memnew(Control); + Window *win = SceneTree::get_singleton()->get_root(); + + ctrl1->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl2->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl3->set_focus_mode(Control::FocusMode::FOCUS_ALL); + + ctrl2->add_child(ctrl3); + win->add_child(ctrl1); + win->add_child(ctrl2); + + SUBCASE("[SceneTree][Control] Focus Next") { + ctrl1->grab_focus(); + CHECK_UNARY(ctrl1->has_focus()); + + SEND_GUI_ACTION("ui_focus_next"); + CHECK_UNARY(ctrl2->has_focus()); + + SEND_GUI_ACTION("ui_focus_next"); + CHECK_UNARY(ctrl3->has_focus()); + + SEND_GUI_ACTION("ui_focus_next"); + CHECK_UNARY(ctrl1->has_focus()); + } + + SUBCASE("[SceneTree][Control] Focus Prev") { + ctrl1->grab_focus(); + CHECK_UNARY(ctrl1->has_focus()); + + SEND_GUI_ACTION("ui_focus_prev"); + CHECK_UNARY(ctrl3->has_focus()); + + SEND_GUI_ACTION("ui_focus_prev"); + CHECK_UNARY(ctrl2->has_focus()); + + SEND_GUI_ACTION("ui_focus_prev"); + CHECK_UNARY(ctrl1->has_focus()); + } + + memdelete(ctrl3); + memdelete(ctrl1); + memdelete(ctrl2); + } + + SUBCASE("[SceneTree][Control] Has a sibling control but the parent node is not a control or window") { Control *other_ctrl = memnew(Control); - SceneTree::get_singleton()->get_root()->add_child(other_ctrl); + intermediate->add_child(other_ctrl); SUBCASE("[SceneTree][Control] Has a sibling control with FOCUS_ALL") { other_ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL); @@ -898,6 +947,7 @@ TEST_CASE("[SceneTree][Control] Find next/prev valid focus") { } memdelete(ctrl); + memdelete(intermediate); } TEST_CASE("[SceneTree][Control] Anchoring") {