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}