Skip to content

Menu button#23707

Open
viridia wants to merge 12 commits intobevyengine:mainfrom
viridia:menu_button
Open

Menu button#23707
viridia wants to merge 12 commits intobevyengine:mainfrom
viridia:menu_button

Conversation

@viridia
Copy link
Copy Markdown
Contributor

@viridia viridia commented Apr 7, 2026

Objective

Dropdown menus and menu buttons for feathers.

Part of #19236

Solution

This implements a feathers widget for dropdown menus, including:

  • Keyboard navigation (Enter, Space, Arrow Keys, Escape)
  • Tab focus
  • Accessibility
  • Click outside to cancel
  • Items highlight on hover and press

Testing

Manual testing

Showcase

feathers_menu

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 7, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in UI Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

Copy link
Copy Markdown
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.

I didn't have time to go through every system line-by-line, but the example works well and all the parts I looked at in detail made sense.

fn menubutton_on_key_event(
mut event: On<FocusedInput<KeyboardInput>>,
q_state: Query<Has<InteractionDisabled>, With<MenuButton>>,
q_menu_button: Query<Has<InteractionDisabled>, With<MenuButton>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
q_menu_button: Query<Has<InteractionDisabled>, With<MenuButton>>,
q_menu_button: Query<(), (With<MenuButton>, Without<InteractionDisabled>)>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, but I fixed it a different way - I remembered that even when disabled, the button should consume the key events (otherwise you'd get very unpredictable behavior).

Comment on lines +159 to +189
/// Menu button scene function. This produces a button that has a dropdown arrow.
///
/// # Arguments
/// * `props` - construction properties for the button.
pub fn menu_button(props: MenuButtonProps) -> impl Scene {
bsn! {
:button(ButtonProps {
variant: ButtonVariant::Normal,
corners: props.corners,
})
ActivateOnPress
MenuButton
FeathersMenuButton
Children [
{props.label},
Node {
flex_grow: 1.0,
},
:icon(icons::CHEVRON_DOWN),
]
}
}

/// Menu button scene function. This version does not insert a dropdown arrow, the caller is
/// expected to supply this themselves.
///
/// # Arguments
/// * `props` - construction properties for the button.
// TODO: This would be better done by a boolean flag in the props, but that would require
// conditional children.
pub fn menu_button_without_arrow(props: MenuButtonProps) -> impl Scene {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is really annoying having the two functions, maybe it's acceptable to box it for the nicer API we intend to support eventually anyway?

Suggested change
/// Menu button scene function. This produces a button that has a dropdown arrow.
///
/// # Arguments
/// * `props` - construction properties for the button.
pub fn menu_button(props: MenuButtonProps) -> impl Scene {
bsn! {
:button(ButtonProps {
variant: ButtonVariant::Normal,
corners: props.corners,
})
ActivateOnPress
MenuButton
FeathersMenuButton
Children [
{props.label},
Node {
flex_grow: 1.0,
},
:icon(icons::CHEVRON_DOWN),
]
}
}
/// Menu button scene function. This version does not insert a dropdown arrow, the caller is
/// expected to supply this themselves.
///
/// # Arguments
/// * `props` - construction properties for the button.
// TODO: This would be better done by a boolean flag in the props, but that would require
// conditional children.
pub fn menu_button_without_arrow(props: MenuButtonProps) -> impl Scene {
/// Menu button scene function.
///
/// # Arguments
/// * `props` - construction properties for the button.
pub fn menu_button(props: MenuButtonProps) -> Box<dyn Scene> {
if props.no_arrow {
Box::new(menu_button_without_arrow(props))
} else {
Box::new(menu_button_with_arrow(props))
}
}
/// Menu button scene function. This produces a button that has a dropdown arrow.
///
/// # Arguments
/// * `props` - construction properties for the button.
fn menu_button_with_arrow(props: MenuButtonProps) -> impl Scene {
bsn! {
:button(ButtonProps {
variant: ButtonVariant::Normal,
corners: props.corners,
})
ActivateOnPress
MenuButton
FeathersMenuButton
Children [
{props.label},
Node {
flex_grow: 1.0,
},
:icon(icons::CHEVRON_DOWN),
]
}
}
/// Menu button scene function. This version does not insert a dropdown arrow, the caller is
/// expected to supply this themselves.
///
/// # Arguments
/// * `props` - construction properties for the button.
fn menu_button_without_arrow(props: MenuButtonProps) -> impl Scene {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wrestled with this a bit and (with some help) came up with a more succinct (although still fairly ugly) solution to the problem of conditional children.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh I see, yep that seems like a better solution.

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-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

3 participants