summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGUI
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-10-23 14:58:45 +0200
committerAndreas Kling <kling@serenityos.org>2021-10-23 16:10:44 +0200
commit79f2e8ae2a9ed276b4ff6bb80e1ed3bdfebb27c0 (patch)
treee07ad942bf6c6d6d084928b854b54511218a2065 /Userland/Libraries/LibGUI
parent0d8373287ccc01089f558a33d9150281fd63a8a8 (diff)
downloadserenity-79f2e8ae2a9ed276b4ff6bb80e1ed3bdfebb27c0.zip
LibGUI: Make exclusive button group act as a single focusable unit
Before this change, using the Tab key would cycle through all the individual buttons in an exclusive group (e.g radio buttons.) This felt wrong, since a group of exclusive buttons is really a single logical input with a limited number of possible choices. This patch makes such groups behave as a single focusable unit instead, by dynamically updating the focus policies so that only the currently checked button is focusable. We also allow keyboard navigation within the button group via the arrow keys. This had to be specialized in GUI::AbstractButton, since the default behavior of arrow keys is to traverse the focus chain.
Diffstat (limited to 'Userland/Libraries/LibGUI')
-rw-r--r--Userland/Libraries/LibGUI/AbstractButton.cpp34
1 files changed, 34 insertions, 0 deletions
diff --git a/Userland/Libraries/LibGUI/AbstractButton.cpp b/Userland/Libraries/LibGUI/AbstractButton.cpp
index 88e6cef661..c3a66d61e2 100644
--- a/Userland/Libraries/LibGUI/AbstractButton.cpp
+++ b/Userland/Libraries/LibGUI/AbstractButton.cpp
@@ -70,6 +70,15 @@ void AbstractButton::set_checked(bool checked, AllowCallback allow_callback)
set_focus(true);
}
+ if (is_exclusive() && parent_widget()) {
+ // In a group of exclusive checkable buttons, only the currently checked button is focusable.
+ parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& button) {
+ if (button.is_exclusive() && button.is_checkable())
+ button.set_focus_policy(button.is_checked() ? GUI::FocusPolicy::StrongFocus : GUI::FocusPolicy::NoFocus);
+ return IterationDecision::Continue;
+ });
+ }
+
update();
if (on_checked && allow_callback == AllowCallback::Yes)
on_checked(checked);
@@ -171,6 +180,31 @@ void AbstractButton::keydown_event(KeyEvent& event)
event.accept();
return;
}
+
+ // Arrow keys switch the currently checked option within an exclusive group of checkable buttons.
+ if (event.is_arrow_key() && !event.modifiers() && is_exclusive() && is_checkable() && parent_widget()) {
+ event.accept();
+ Vector<GUI::AbstractButton&> exclusive_siblings;
+ size_t this_index = 0;
+ parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& sibling) {
+ if (&sibling == this) {
+ VERIFY(is_enabled());
+ this_index = exclusive_siblings.size();
+ }
+ if (sibling.is_exclusive() && sibling.is_checkable() && sibling.is_enabled())
+ exclusive_siblings.append(sibling);
+ return IterationDecision::Continue;
+ });
+ if (exclusive_siblings.size() <= 1)
+ return;
+ size_t new_checked_index;
+ if (event.key() == KeyCode::Key_Left || event.key() == KeyCode::Key_Up)
+ new_checked_index = this_index == 0 ? exclusive_siblings.size() - 1 : this_index - 1;
+ else
+ new_checked_index = this_index == exclusive_siblings.size() - 1 ? 0 : this_index + 1;
+ exclusive_siblings[new_checked_index].set_checked(true);
+ return;
+ }
Widget::keydown_event(event);
}