https://github.com/terhechte/bevy_quickmenu
A simple-to-use menu system for Bevy which supports gamepad, keyboard or mouse navigation
https://github.com/terhechte/bevy_quickmenu
Last synced: 2 months ago
JSON representation
A simple-to-use menu system for Bevy which supports gamepad, keyboard or mouse navigation
- Host: GitHub
- URL: https://github.com/terhechte/bevy_quickmenu
- Owner: terhechte
- License: mit
- Created: 2022-10-22T17:44:07.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2024-03-27T10:28:09.000Z (over 1 year ago)
- Last Synced: 2025-03-13T17:53:53.399Z (10 months ago)
- Language: Rust
- Size: 703 KB
- Stars: 31
- Watchers: 2
- Forks: 7
- Open Issues: 3
-
Metadata Files:
- Readme: Readme.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://opensource.org/licenses/MIT)
[](https://docs.rs/bevy_quickmenu)
[](https://crates.io/crates/bevy_quickmenu)
# 🏃♂️ 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](examples/custom.rs))
## Usage
Add to `Cargo.toml`:
```toml
[dependencies]
bevy_quickmenu = "0.1.5"
```
### Version Compatibility
| Bevy Version | Crates Version |
| ------------ | -------------- |
| 0.14.0 | 0.5.0 |
| 0.13.0 | 0.4.0 |
| 0.12.0 | 0.3.0 |
| 0.11.0 | 0.2.0 |
| 0.10.0 | 0.1.6 |
| 0.9.0 | 0.1.5 |
## Demo

## Quick Examples
- [`examples/basic.rs`](examples/basic.rs): Basic example to show how it works
- [`examples/settings.rs`](examples/settings.rs): Full blown user settings including switching game states and showing the menu again
- [`examples/custom.rs`](examples/custom.rs): Showcase customization options
## 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:
```rs
#[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) {
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:
```rs
#[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 {
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),
}
}
}
```
## `Menu`
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.
```rs
fn root_menu(_state: &mut CustomState) -> Menu {
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 {
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),
],
)
}
```
## `MenuItem`
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
```rs
MenuItem::screen("Controls", Screens::Controls).with_icon(MenuIcon::Controls)
MenuItem::screen("Save", Screens::Save).with_icon(MenuIcon::Other(icons.save.clone()))
```
`MenuItem`s can also be checked or unchecked:
```rs
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:
```rs
impl Plugin for SettingsPlugin {
fn build(&self, app: &mut App) {
app
// Register a event that can be called from your action handler
.add_event::()
// The plugin
.add_plugins(QuickMenuPlugin::::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:
```rs
#[derive(Debug)]
enum BasicEvent {
Close,
}
impl ActionTrait for Actions {
fn handle(&self, state: &mut BasicState, event_writer: &mut EventWriter) {
match self {
Actions::Close => event_writer.send(BasicEvent::Close),
}
}
}
fn event_reader(mut commands: Commands, mut event_reader: EventReader) {
for event in event_reader.read() {
match event {
BasicEvent::Close => bevy_quickmenu::cleanup(&mut commands),
}
}
}
```
### Screenshot from the customized screen
