bevy_ui_bits 0.14.0

A tiny and opinionated collection of UI components for Bevy
Documentation
use bevy::{color::palettes, input::keyboard::KeyboardInput, prelude::*, text::LineHeight};
use bevy_ui_bits::*;

const PLAY_BUTTON_ID: usize = 1;
const QUIT_BUTTON_ID: usize = 2;

#[derive(Resource)]
struct SelectedButton {
    pub id: usize,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Main Menu Example".to_string(),
                ..default()
            }),
            ..default()
        }))
        .insert_resource(SelectedButton { id: PLAY_BUTTON_ID })
        .add_systems(Startup, (spawn_camera, spawn_main_menu))
        .add_systems(
            Update,
            (handle_keyboard_input, handle_mouse_input, update_buttons).chain(),
        )
        .run();
}

fn spawn_camera(mut commands: Commands) {
    commands.spawn(Camera2d::default());
}

fn spawn_main_menu(mut commands: Commands) {
    let font = &Handle::default();

    let root = Root::new();
    let container = Container::height(Val::Px(550.0)).justify_between();
    let top = Container::new();
    let bottom = Container::height(Val::Px(200.0)).justify_between();
    let actions = Container::new();
    let footer = Container::new();

    let title = EmbossedText::extra_large("My\nGame", font)
        .color(palettes::css::GOLDENROD.into())
        .line_height(LineHeight::RelativeToFont(1.0));
    let play = UiButton::rectangle()
        .id(PLAY_BUTTON_ID)
        .background_color(palettes::css::GOLDENROD.into());
    let play_text = EmbossedText::medium("Play", font);
    let quit = UiButton::rectangle().id(QUIT_BUTTON_ID);
    let quit_text = EmbossedText::medium("Quit", font);
    let instructions = SimpleText::small(
        "Use mouse or the arrow keys to interact with the buttons",
        font,
    );

    commands.spawn((
        root,
        children![(
            container,
            children![
                (top, children![title]),
                (
                    bottom,
                    children![
                        (
                            actions,
                            children![(play, children![play_text]), (quit, children![quit_text])]
                        ),
                        (footer, children![instructions])
                    ]
                )
            ]
        )],
    ));
}

fn handle_keyboard_input(
    mut keyboard_input_events: EventReader<KeyboardInput>,
    mut selected_button: ResMut<SelectedButton>,
    query: Query<&UiButtonData>,
) {
    for event in keyboard_input_events.read() {
        if event.state.is_pressed() {
            match event.key_code {
                KeyCode::ArrowUp | KeyCode::ArrowDown => {
                    for button_data in &query {
                        if button_data.id == selected_button.id {
                            selected_button.id = if button_data.id == PLAY_BUTTON_ID {
                                QUIT_BUTTON_ID
                            } else {
                                PLAY_BUTTON_ID
                            };

                            break;
                        }
                    }
                }
                _ => break,
            }
        }
    }
}

fn handle_mouse_input(
    mut selected_button: ResMut<SelectedButton>,
    query: Query<(&UiButtonData, &Interaction), (Changed<Interaction>, With<UiButtonData>)>,
) {
    for (button_data, interaction) in &query {
        match *interaction {
            Interaction::Hovered => selected_button.id = button_data.id,
            _ => {}
        }
    }
}

fn update_buttons(
    selected_button: Res<SelectedButton>,
    mut query: Query<(&UiButtonData, &mut BackgroundColor)>,
) {
    for (button_data, mut background_color) in &mut query {
        if button_data.id == selected_button.id {
            *background_color = palettes::css::GOLDENROD.into();
        } else {
            *background_color = Color::NONE.into();
        }
    }
}