Crate bevy_quickmenu

Source
Expand description

License: MIT Doc Crate

ยง๐Ÿƒโ€โ™‚๏ธ Bevy Quickmenu

Every game needs menus. Be it for the settings, for a pause screen, or for the main menu when the user enters the game.

While Bevy UI allows building menus, it is a cumbersome process especially if nested menus are needed. Even worse, though, is the effort required if you want your menu to be accessible with a keyboard, a gamepad, or the mouse.

Bevy Quickmenu offers all that. It is a super lightweight way of building in-game menus that can be controlled by all input devices. It even offers a simple way of having hover states. Everything can also be customized.

  • Super simple menu plugin for bevy, building upon Bevy UI
  • Keyboard, Mouse, Gamepad input is processed
  • Support mouse hover states in simplified Stylesheet
  • Many customizations possible (see examples/custom.rs)

ยงUsage

Add to Cargo.toml:

[dependencies]
bevy_quickmenu = "0.1.5"

ยงVersion Compatibility

Bevy VersionCrates Version
0.11.00.2.0
0.10.00.1.6
0.9.00.1.5

ยงDemo

Demo Gif

ยงQuick Examples

ยงAn explanation of the required components

ยงState

A generic type that hosts the state of your menu (e.g. which items are selected, and so on). Whenever this state changes, the menu is automatically redrawn.

ยงAction

(Conforms to ActionTrait): This enum defines all the actions your user can take. Such as SoundOn, SoundOff etc. When a user performs an action (by selecting the corresponding menu entry), the handle method is called on your ActionTrait implementation. ActionTrait has two generic types: Your State as well as a Event which you can define. This allows you to handle your action:

#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
enum Actions {
    Close,
    SoundOn,
    SoundOff,
    Control(usize, ControlDevice),
}

impl ActionTrait for Actions {
    type State = CustomState;
    type Event = MyEvent;
    fn handle(&self, state: &mut CustomState, event_writer: &mut EventWriter<MyEvent>) {
        match self {
            Actions::Close => event_writer.send(MyEvent::CloseSettings),
            Actions::SoundOn => state.sound_on = true,
            Actions::SoundOff => state.sound_on = false,
            Actions::Control(p, d) => {
                state.controls.insert(*p, *d);
            }
        }
    }
}

ยงScreen

(Conforms to the ScreenTrait). Each page or screen in your menu is defined by this enum. Note that menu screens are not nested!. Instead the ScreenTrait has a resolve function that allows you to return the corresponding menu definition for the given enum:

#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
enum Screens {
    Root,
    Controls,
    Sound,
    Player(usize),
}

impl ScreenTrait for Screens {
    type Action = Actions;
    fn resolve(&self, state: &mut CustomState) -> Menu<Screens> {
        match self {
            Screens::Root => root_menu(state),
            Screens::Controls => controls_menu(state),
            Screens::Sound => sound_menu(state),
            Screens::Player(p) => player_controls_menu(state, *p),
        }
    }
}

A menu is just a function that returns a list of MenuItem to be displayed. Each menu needs to have a distinct id. The example shows how the root and the sound menu are defined.

fn root_menu(_state: &mut CustomState) -> Menu<Screens> {
    Menu::new(
        Id::new("root"),
        vec![
            MenuItem::headline("Settings"),
            MenuItem::action("Back", Actions::Close).with_icon(MenuIcon::Back),
            MenuItem::screen("Sound", Screens::Sound).with_icon(MenuIcon::Sound),
            MenuItem::screen("Controls", Screens::Controls).with_icon(MenuIcon::Controls),
        ],
    )
}

fn sound_menu(state: &mut CustomState) -> Menu<Screens> {
    Menu::new(
        Id::new("sound"),
        vec![
            MenuItem::label("Toggles sound and music"),
            MenuItem::action("On", Actions::SoundOn).checked(state.sound_on),
            MenuItem::action("Off", Actions::SoundOff).checked(!state.sound_on),
        ],
    )
}

In order to give you some flexibility, the menu item allows you to return five different types:

  • MenuItem::label: A small text label that cannot be selected
  • MenuItem::headline: A big text label that cannot be selected
  • MenuItem::action: A action that is performed when the user selects it
  • MenuItem::screen: Dive into a screen when the user selects this
  • MenuItem::image: A single image (including an optional Style)

In addition, a menu-item can have one of a couple of pre-defined icons or a custom icon

MenuItem::screen("Controls", Screens::Controls).with_icon(MenuIcon::Controls)
MenuItem::screen("Save", Screens::Save).with_icon(MenuIcon::Other(icons.save.clone()))

MenuItems can also be checked or unchecked:

MenuItem::action("On", Actions::SoundOn).checked(state.sound_on)
MenuItem::action("Off", Actions::SoundOff).checked(!state.sound_on)

ยงDisplaying a Menu

Hereโ€™s a the annoated setup function from the example:

impl Plugin for SettingsPlugin {
    fn build(&self, app: &mut App) {
        app
            // Register a event that can be called from your action handler
            .add_event::<BasicEvent>()
            // The plugin
            .add_plugins(QuickMenuPlugin::<Screens>::new())
            // Some systems
            .add_startup_system(setup)
            .add_system(event_reader);
    }
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera3dBundle::default());
    commands.insert_resource(MenuState::new(
        BasicState::default(),
        Screens::Root,
        Some(StyleSheet::default()),
    ))
}

ยงRemoving a Menu

In order to remove a menu, thereโ€™s the bevy_quickmenu::cleanup function. Usually, it is best to use it with the event that Bevy Quickmenu allows you to register:

#[derive(Debug)]
enum BasicEvent {
    Close,
}

impl ActionTrait for Actions {
    fn handle(&self, state: &mut BasicState, event_writer: &mut EventWriter<BasicEvent>) {
        match self {
            Actions::Close => event_writer.send(BasicEvent::Close),
        }
    }
}

fn event_reader(mut commands: Commands, mut event_reader: EventReader<BasicEvent>) {
    for event in event_reader.iter() {
        match event {
            BasicEvent::Close => bevy_quickmenu::cleanup(&mut commands),
        }
    }
}

ยงScreenshot from the customized screen

data/customized.png

Modulesยง

style
Lightweight abstractions over styles Instead of using the bevy styles with all their properties, these simplified styles are mostly used to define the looks of menus and the different control states of buttons.

Structsยง

ButtonComponent
Each Button in the UI can be queried via this component in order to further change the appearance
Menu
Create a menu with an identifier and a Vec of MenuItem entries
MenuOptions
Changing these MenuOptions allows overriding the provided images and fonts. Use crate::QuickMenuPlugin::with_options to do this.
MenuState
The primary state resource of the menu
NavigationMenu
PrimaryMenu
The primary horizontal menu can be queried via this component
QuickMenuPlugin
The quickmenu plugin. It requires multiple generic parameters in order to setup. A minimal example. For a full explanation refer to the examples or the README.
RedrawEvent
Whenever a state change in the MenuState is detected, this event is send in order to tell the UI to re-render itself
RichTextEntry
Simplified Rich-Text that assumes the default font
Selections
This map holds the currently selected items in each screen / menu
VerticalMenuComponent
Each vertical menu can be queried via this component

Enumsยง

MenuIcon
The library comes with some pre-defined icons for several screens. Custom icons can be used with MenuIcon::Other or by overriding the existing ones via MenuOptions
MenuItem
Abstraction over MenuItems in a Screen / Menu
MenuSelection
Abstraction over a concrete selection in a screen / menu
NavigationEvent
GamePad and Cursor navigation generates these navigation events which are then processed by a system and applied to the menu. Navigation can be customized by sending these events into a EventWriter<NavigationEvent>

Traitsยง

ActionTrait
A type conforming to this trait is used to handle the events that are generated as the user interacts with the menu
ScreenTrait
Each Menu / Screen uses this trait to define which menu items lead to which other screens

Functionsยง

cleanup
Remove the menu