Skip to content

Core radio button and radio group #19778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 24, 2025
Merged

Conversation

viridia
Copy link
Contributor

@viridia viridia commented Jun 22, 2025

Objective

Core Radio Button and Radio Group widgets. Part of #19236

Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@IceSentry IceSentry added A-UI Graphical user interfaces, styles, layouts, and widgets S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 22, 2025
Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a mechanism that enforces exclusivity, atm more than radio button can be selected at a time.

It's also possible to construct nested radio buttons. That doesn't seem useful, probably they should be ignored after emitting a warning.

.iter_descendants(ev.target())
.filter_map(|child_id| match q_radio.get(child_id) {
Ok((checked, false)) => Some((child_id, checked)),
Ok((_, true)) | Err(_) => None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Ok((_, true)) | Err(_) => None,
_ => None,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of like the fact that we're explicitly telling the person reading the code that there's both an error case and a disabled case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'm just easily distracted, so I'm always obsessed with removing any noise.

/// See <https://www.w3.org/WAI/ARIA/apg/patterns/radio>/
#[derive(Component, Debug)]
#[require(AccessibilityNode(accesskit::Node::new(Role::RadioButton)), Checked)]
pub struct CoreRadio;
Copy link
Contributor

@ickshonpe ickshonpe Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct CoreRadio;
pub struct CoreRadio(Option<Entity>);

Maybe to enforce exclusivity instead of using the Checked component, you could propagate down the entity id of the currently selected radio button and store it in each CoreRadio component. Then to check if a radio button is selected you'd compare its own entity id against the id stored in its CoreRadio component.

Copy link
Contributor

@ickshonpe ickshonpe Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could still result in multiple radio selections within a group if a currently selected radio button is reparented into another group or something though, I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a lengthy doc comment concerning mutual exclusion.

I didn't want to mention reactivity in the comment, but in a reactive framework mutual exclusion is very simple. Each button's Checked state is driven by a reactive formula of the form (radio_button_value == group_value). When the group value changes, the checked states change in response (regardless of whether the change was the result of clicking a button, or because the data changed programmatically for some other reason).

However, since we don't have reactivity (yet), I had to handwave over the issue by saying that the checked states are either updated by the app or some unspecified data-binding scheme.

It's hard to do more than this without resorting to Any types to store the radio button values. (Either that or generic components, but I'd prefer not to go there).

In any case, as long as the app follows this pattern (checked driven by equality comparison) and each radio button has a unique value, then having more than one radio button selected at a time should never happen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I understand it's difficult without reactivity. It's just that mutual exclusivity is the defining characteristic of radio buttons, so expecting users to have to manage and enforce it themselves feels like it defeats the purpose of having this widget.

Maybe it's just not possible or at least not worth the effort at the moment though and this is the best we can do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I share this sentiment. I'm not pleased with the fact that this logic is kinda punted up a layer, but I'm alright with moving forward for now in the absence of a clear alternate proposal.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Straightforward enough :) Happy to continue to push this forward.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it M-Needs-Release-Note Work that should be called out in the blog due to impact and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 23, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 24, 2025
Merged via the queue into bevyengine:main with commit 9f551bb Jun 24, 2025
38 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Jun 27, 2025
This was a mistake in my original PR #19778: a holdover from when
Checked was not a marker component.
@viridia viridia deleted the core_radio branch July 7, 2025 22:19
Trashtalk217 pushed a commit to Trashtalk217/bevy that referenced this pull request Jul 10, 2025
# Objective

Core Radio Button and Radio Group widgets. Part of bevyengine#19236
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants