bevy_quickmenu/
lib.rs

1#![doc = include_str!("../Readme.md")]
2
3// pub mod helpers;
4mod navigation_menu;
5pub mod style;
6mod systems;
7mod types;
8mod widgets;
9
10use bevy::prelude::*;
11use style::Stylesheet;
12use types::{CleanUpUI, MenuAssets};
13
14use std::fmt::Debug;
15use std::hash::Hash;
16
17pub use navigation_menu::NavigationMenu;
18pub use types::{
19    ButtonComponent, Menu, MenuIcon, MenuItem, MenuOptions, MenuSelection, NavigationEvent,
20    PrimaryMenu, RedrawEvent, RichTextEntry, Selections, VerticalMenuComponent,
21};
22
23/// The quickmenu plugin.
24/// It requires multiple generic parameters in order to setup. A minimal example.
25/// For a full explanation refer to the examples or the README.
26/// ```
27/// use bevy::prelude::*;
28///
29/// use bevy_quickmenu::{
30///     style::Stylesheet, ActionTrait, Menu, MenuIcon, MenuItem, MenuState, QuickMenuPlugin,
31///     ScreenTrait,
32/// };
33///
34/// fn main() {
35///     App::new()
36///         .add_plugins(DefaultPlugins)
37///         .add_plugins(BasicPlugin);
38///         //.run();
39/// }
40///
41/// /// This custom event can be emitted by the action handler (below) in order to
42/// /// process actions with access to the bevy ECS
43/// #[derive(Debug, Event)]
44/// enum BasicEvent {
45///     Close,
46/// }
47///
48/// /// This state represents the UI. Mutations to this state (via `MenuState::state_mut`)
49/// /// cause a re-render of the menu UI
50/// #[derive(Debug, Clone, Default)]
51/// struct BasicState {
52///     boolean1: bool,
53///     boolean2: bool,
54/// }
55///
56/// pub struct BasicPlugin;
57///
58/// impl Plugin for BasicPlugin {
59///     fn build(&self, app: &mut App) {
60///         app
61///             // Register a event that can be called from your action handler
62///             .add_event::<BasicEvent>()
63///             // The plugin
64///             .add_plugins(QuickMenuPlugin::<Screens>::new())
65///             // Some systems
66///             .add_systems(Startup, setup)
67///             .add_systems(Update, event_reader);
68///     }
69/// }
70///
71/// fn setup(mut commands: Commands) {
72///     commands.spawn(Camera3dBundle::default());
73///     // Create a default stylesheet. You can customize these as you wish
74///     let sheet = Stylesheet::default();
75///
76///     commands.insert_resource(MenuState::new(
77///         BasicState::default(),
78///         Screens::Root,
79///         Some(sheet),
80///     ))
81/// }
82///
83/// /// The possible actions in our settings
84/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
85/// enum Actions {
86///     Close,
87///     Toggle1,
88///     Toggle2,
89/// }
90///
91/// /// Handle the possible actions
92/// impl ActionTrait for Actions {
93///     type State = BasicState;
94///     type Event = BasicEvent;
95///     fn handle(&self, state: &mut BasicState, event_writer: &mut EventWriter<BasicEvent>) {
96///         match self {
97///             Actions::Close => event_writer.send(BasicEvent::Close),
98///             Actions::Toggle1 => state.boolean1 = !state.boolean1,
99///             Actions::Toggle2 => state.boolean2 = !state.boolean2,
100///         }
101///     }
102/// }
103///
104/// /// All possible screens in our example
105/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
106/// enum Screens {
107///     Root,
108///     Booleans,
109/// }
110///
111/// /// Map from from `Screens` to the actual menu
112/// impl ScreenTrait for Screens {
113///     type Action = Actions;
114///     type State = BasicState;
115///     fn resolve(&self, state: &BasicState) -> Menu<Screens> {
116///         match self {
117///             Screens::Root => root_menu(state),
118///             Screens::Booleans => boolean_menu(state),
119///         }
120///     }
121/// }
122///
123/// /// The `root` menu that is displayed first
124/// fn root_menu(_state: &BasicState) -> Menu<Screens> {
125///     Menu::new(
126///         "root",
127///         vec![
128///             MenuItem::headline("Basic Example"),
129///             MenuItem::action("Close", Actions::Close).with_icon(MenuIcon::Back),
130///             MenuItem::label("A submenu"),
131///             MenuItem::screen("Boolean", Screens::Booleans),
132///         ],
133///     )
134/// }
135///
136/// /// The boolean menu which is accessed from the `Screens::Boolean` entry in the root_menu
137/// fn boolean_menu(state: &BasicState) -> Menu<Screens> {
138///     Menu::new(
139///         "boolean",
140///         vec![
141///             MenuItem::label("Toggles some booleans"),
142///             MenuItem::action("Toggle Boolean 1", Actions::Toggle1).checked(state.boolean1),
143///             MenuItem::action("Toggle Boolean 2", Actions::Toggle2).checked(state.boolean2),
144///         ],
145///     )
146/// }
147///
148/// /// This allows to react to actions with custom bevy resources or eventwriters or queries.
149/// /// In this example we use it to close the menu
150/// fn event_reader(mut commands: Commands, mut event_reader: EventReader<BasicEvent>) {
151///     for event in event_reader.iter() {
152///         match event {
153///             BasicEvent::Close => bevy_quickmenu::cleanup(&mut commands),
154///         }
155///     }
156/// }
157/// ```
158pub struct QuickMenuPlugin<S>
159where
160    S: ScreenTrait + 'static,
161{
162    s: std::marker::PhantomData<S>,
163    options: Option<MenuOptions>,
164}
165
166impl<S> QuickMenuPlugin<S>
167where
168    S: ScreenTrait + 'static,
169{
170    #[allow(clippy::new_without_default)]
171    pub fn new() -> Self {
172        Self {
173            s: Default::default(),
174            options: None,
175        }
176    }
177
178    pub fn with_options(options: MenuOptions) -> Self {
179        Self {
180            s: Default::default(),
181            options: Some(options),
182        }
183    }
184}
185
186impl<State, A, S> Plugin for QuickMenuPlugin<S>
187where
188    State: 'static + Send + Sync,
189    A: ActionTrait<State = State> + 'static,
190    S: ScreenTrait<Action = A, State = State> + 'static,
191{
192    fn build(&self, app: &mut bevy::prelude::App) {
193        app.insert_resource(self.options.unwrap_or_default())
194            .init_resource::<MenuAssets>()
195            .insert_resource(Selections::default())
196            .add_event::<NavigationEvent>()
197            .add_event::<RedrawEvent>()
198            .add_systems(
199                Update,
200                systems::cleanup_system::<S>.run_if(resource_exists::<CleanUpUI>()),
201            )
202            .add_systems(
203                Update,
204                (
205                    systems::mouse_system::<S>.run_if(resource_exists::<MenuState<S>>()),
206                    systems::input_system::<S>.run_if(resource_exists::<MenuState<S>>()),
207                    systems::redraw_system::<S>.run_if(resource_exists::<MenuState<S>>()),
208                    systems::keyboard_input_system.run_if(resource_exists::<MenuState<S>>()),
209                ),
210            );
211    }
212}
213
214/// Remove the menu
215pub fn cleanup(commands: &mut Commands) {
216    commands.init_resource::<CleanUpUI>();
217}
218
219/// A type conforming to this trait is used to handle the events that
220/// are generated as the user interacts with the menu
221pub trait ActionTrait: Debug + PartialEq + Eq + Clone + Copy + Hash + Send + Sync {
222    type State;
223    type Event: Event + Send + Sync + 'static;
224    fn handle(&self, state: &mut Self::State, event_writer: &mut EventWriter<Self::Event>);
225}
226
227/// Each Menu / Screen uses this trait to define which menu items lead
228/// to which other screens
229pub trait ScreenTrait: Debug + PartialEq + Eq + Clone + Copy + Hash + Send + Sync {
230    type Action: ActionTrait<State = Self::State>;
231    type State: Send + Sync + 'static;
232    fn resolve(&self, state: &<<Self as ScreenTrait>::Action as ActionTrait>::State) -> Menu<Self>;
233}
234
235/// The primary state resource of the menu
236#[derive(Resource)]
237pub struct MenuState<S>
238where
239    S: ScreenTrait + 'static,
240{
241    menu: NavigationMenu<S>,
242    pub initial_render_done: bool,
243}
244
245impl<S> MenuState<S>
246where
247    S: ScreenTrait + 'static,
248{
249    pub fn new(state: S::State, screen: S, sheet: Option<Stylesheet>) -> Self {
250        Self {
251            menu: NavigationMenu::new(state, screen, sheet),
252            initial_render_done: false,
253        }
254    }
255
256    /// Get a mutable reference to the state in order to change it.
257    /// Changing something here will cause a re-render in the next frame.
258    /// Due to the way bevy works, just getting this reference, without actually performing
259    /// a change is enough to cause a re-render.
260    pub fn state_mut(&mut self) -> &mut S::State {
261        &mut self.menu.state
262    }
263
264    /// Can a immutable reference to the state.
265    pub fn state(&self) -> &S::State {
266        &self.menu.state
267    }
268}