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.
This commit is contained in:
Andreas Kling 2021-10-23 14:58:45 +02:00
parent 0d8373287c
commit 79f2e8ae2a
Notes: sideshowbarker 2024-07-18 02:01:01 +09:00

View file

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