Skip to main content

pixels_graphics_lib/
scenes.rs

1use crate::integration::softbuffer_winit::run;
2use crate::prelude::*;
3use crate::ui::styles::UiStyle;
4use rustc_hash::FxHashSet;
5use std::fmt::Debug;
6use winit::event::MouseButton;
7use winit::keyboard::KeyCode;
8use winit::window::Window;
9
10/// Convenience method for programs built using [Scene]s
11///
12/// If you're not using scenes consider [run]
13///
14/// # Arguments
15/// * `width` - Width of the whole window canvas in pixels
16/// * `height` - Height of the whole window canvas in pixels
17/// * `title` - Window title
18/// * `window_prefs` - Optionally program info, if passed the window position and size will be persisted
19/// * `scene_switcher` - [SceneSwitcher] Adds new scenes to the stack
20/// * `init_scene` - The initial [Scene] to use
21/// * `options` - [Options] controls how fast the program can update, [UiElement] styling, etc
22#[allow(clippy::too_many_arguments)]
23pub fn run_scenes<
24    SR: Clone + PartialEq + Debug + 'static,
25    SN: Clone + PartialEq + Debug + 'static,
26>(
27    width: usize,
28    height: usize,
29    title: &str,
30    window_prefs: Option<WindowPreferences>,
31    scene_switcher: SceneSwitcher<SR, SN>,
32    init_scene: Box<dyn Scene<SR, SN>>,
33    options: Options,
34    pre_post: Box<dyn PrePost<SR, SN>>,
35) -> Result<(), GraphicsError> {
36    let system = Box::new(SceneHost::new(
37        init_scene,
38        window_prefs,
39        scene_switcher,
40        options.style.clone(),
41        pre_post,
42    )?);
43    run(width, height, title, system, options)?;
44    Ok(())
45}
46
47/// Creates new scenes.
48///
49/// # Important
50/// This method must add the new scene to `scenes`
51///
52/// # Arguments
53/// * `style` - Style data for [UiElement]s, can be ignored if UI is custom
54/// * `scenes` - The current scene stack
55/// * `new_scene` - The name and data for a new scene
56pub type SceneSwitcher<SR, SN> =
57    fn(style: &UiStyle, scenes: &mut Vec<Box<dyn Scene<SR, SN>>>, new_scene: SN);
58
59/// When a scene wants to add or remove a scene from the stack it should return [Push][SceneUpdateResult::Push] or [Pop][SceneUpdateResult::Pop] from `Scene.update`
60#[derive(Debug, Clone, PartialEq)]
61pub enum SceneUpdateResult<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> {
62    /// Do nothing, the current scene remains the foreground scene
63    Nothing,
64    /// Open a child scene
65    /// # Arguments
66    /// * `0` `bool` - If true this scene will be closed as well as opening the new scene
67    /// * `1` `SN` - Data for [SceneSwitcher] so it can create the new scene
68    Push(bool, SN),
69    /// Close this scene, data may be included to be returned to the parent scene
70    Pop(Option<SR>),
71}
72
73/// Scenes represent a mode/feature of a programs UI
74/// For example in an image editor you could have the main menu, editor, and save dialog as scenes
75/// and in an RPG you could have the field, battle and menu screens as scenes
76///
77/// Scenes can be fullscreen or smaller, such as a dialog
78///
79/// # Common mistakes
80///
81/// * If you use a field to store the [SceneUpdateResult] and return in [update()][Scene::update]
82///   and then forget to clear it in [resuming][Scene::resuming] after a child returns then the child
83///   will immediately reopen
84pub trait Scene<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> {
85    fn id(&self) -> u32 {
86        0
87    }
88
89    /// Render scene contents using `graphics`
90    ///
91    /// If this is a fullscreen scene it should draw a color over the whole screen otherwise
92    /// you may see rendering issues (use `graphics.clear(Color)`).
93    /// # Note
94    /// mouse will be empty if this screen is in the background and a non full screen scene is active
95    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
96    #[allow(unused_variables)]
97    fn render(
98        &self,
99        graphics: &mut Graphics,
100        mouse: &MouseData,
101        held_keys: &FxHashSet<KeyCode>,
102        controller: &GameController,
103    );
104    /// Render scene contents using `graphics`
105    ///
106    /// If this is a fullscreen scene it should draw a color over the whole screen otherwise
107    /// you may see rendering issues (use `graphics.clear(Color)`).
108    /// # Note
109    /// mouse will be empty if this screen is in the background and a non full screen scene is active
110    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
111    #[allow(unused_variables)]
112    fn render(&self, graphics: &mut Graphics, mouse: &MouseData, held_keys: &FxHashSet<KeyCode>) {}
113    /// Called when a keyboard key is being pressed down
114    ///
115    /// # Arguments
116    /// * `key` - The latest pressed key
117    /// * `mouse` - position, held state of mouse
118    /// * `held_keys` - Any other keys that are being pressed down
119    #[allow(unused_variables)]
120    fn on_key_down(&mut self, key: KeyCode, mouse: &MouseData, held_keys: &FxHashSet<KeyCode>) {}
121    /// Called when a keyboard key has been released
122    ///
123    /// # Arguments
124    /// * `key` - The latest pressed key
125    /// * `mouse` - position, held state of mouse
126    /// * `held_keys` - Any other keys that are being pressed down
127    #[allow(unused_variables)]
128    fn on_key_up(&mut self, key: KeyCode, mouse: &MouseData, held_keys: &FxHashSet<KeyCode>) {}
129    /// Called when a mouse button has been pressed down
130    ///
131    /// # Arguments
132    /// * `mouse` - position, held state of mouse
133    /// * `mouse_button` = which button was pressed
134    /// * `held_keys` - Any keyboards keys that are being pressed down
135    #[allow(unused_variables)]
136    fn on_mouse_down(
137        &mut self,
138        mouse: &MouseData,
139        mouse_button: MouseButton,
140        held_keys: &FxHashSet<KeyCode>,
141    ) {
142    }
143    /// Called when a mouse button has been released
144    ///
145    /// [on_mouse_click] will also be called after
146    ///
147    /// # Arguments
148    /// * `mouse` - position, held state of mouse
149    /// * `mouse_button` = which button was released
150    /// * `held_keys` - Any keyboards keys that are being pressed down
151    #[allow(unused_variables)]
152    fn on_mouse_up(
153        &mut self,
154        mouse: &MouseData,
155        mouse_button: MouseButton,
156        held_keys: &FxHashSet<KeyCode>,
157    ) {
158    }
159    /// Called when a mouse button has been pressed and released
160    ///
161    /// [on_mouse_up] will also be called before
162    ///
163    /// # Arguments
164    /// * `down_at` - position where mouse button was clicked
165    /// * `mouse` - position, held state of mouse
166    /// * `mouse_button` = which button was clicked
167    /// * `held_keys` - Any keyboards keys that are being pressed down
168    #[allow(unused_variables)]
169    fn on_mouse_click(
170        &mut self,
171        down_at: Coord,
172        mouse: &MouseData,
173        mouse_button: MouseButton,
174        held_keys: &FxHashSet<KeyCode>,
175    ) {
176    }
177    /// Called when the mouse moved while any button is held down
178    ///
179    /// # Arguments
180    /// * `mouse` - position, held state of mouse
181    /// * `held_keys` - Any keyboards keys that are being pressed down
182    #[allow(unused_variables)]
183    fn on_mouse_drag(&mut self, mouse: &MouseData, held_keys: &FxHashSet<KeyCode>) {}
184    /// Called when the mouse scroll function has been used
185    ///
186    /// # Arguments
187    /// * `xy` - The on screen coord of the cursor
188    /// * `y_diff` - The distance scrolled vertically
189    /// * `x_diff` - The distance scrolled horizontally
190    /// * `held_keys` - Any keyboards keys that are being pressed down
191    #[allow(unused_variables)]
192    fn on_scroll(
193        &mut self,
194        mouse: &MouseData,
195        x_diff: isize,
196        y_diff: isize,
197        held_keys: &FxHashSet<KeyCode>,
198    ) {
199    }
200    /// During this method the scene should update animations and anything else that relies on time
201    /// or on held keys
202    ///
203    /// # Arguments
204    /// * `timing` - Deltas and other timing info, generally you should use the `fixed_time_step` field
205    /// * `xy` - The on screen coord of the mouse cursor
206    /// * `held_keys` - Any keyboards keys that are being pressed down
207    ///
208    /// # Returns
209    ///
210    /// [SceneUpdateResult]
211    /// * In normal function this is will be [Nothing][SceneUpdateResult::Nothing]
212    /// * To close this scene return [Pop][SceneUpdateResult::Pop]
213    /// * To open a child scene return [Push][SceneUpdateResult::Push]
214    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
215    fn update(
216        &mut self,
217        timing: &Timing,
218        mouse: &MouseData,
219        held_keys: &FxHashSet<KeyCode>,
220        controller: &GameController,
221        window: &Window,
222    ) -> SceneUpdateResult<SR, SN>;
223    /// During this method the scene should update animations and anything else that relies on time
224    /// or on held keys
225    ///
226    /// # Arguments
227    /// * `timing` - Deltas and other timing info, generally you should use the `fixed_time_step` field
228    /// * `xy` - The on screen coord of the mouse cursor
229    /// * `held_keys` - Any keyboards keys that are being pressed down
230    ///
231    /// # Returns
232    ///
233    /// [SceneUpdateResult]
234    /// * In normal function this is will be [Nothing][SceneUpdateResult::Nothing]
235    /// * To close this scene return [Pop][SceneUpdateResult::Pop]
236    /// * To open a child scene return [Push][SceneUpdateResult::Push]
237    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
238    fn update(
239        &mut self,
240        timing: &Timing,
241        mouse: &MouseData,
242        held_keys: &FxHashSet<KeyCode>,
243        window: &Window,
244    ) -> SceneUpdateResult<SR, SN>;
245    /// Called when a child scene is closing
246    ///
247    /// # Arguments
248    /// * `result` - Optional data from child scene
249    #[allow(unused_variables)]
250    fn resuming(&mut self, result: Option<SR>) {}
251    /// Return true if this scene doesn't fill the screen or is transparent
252    /// If this returns false the previous fullscreen scene will render as well
253    fn is_dialog(&self) -> bool {
254        false
255    }
256}
257
258pub trait PrePost<SR, SN> {
259    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
260    fn pre_render(
261        &mut self,
262        graphics: &mut Graphics,
263        mouse: &MouseData,
264        held_keys: &FxHashSet<KeyCode>,
265        scenes: &mut [Box<dyn Scene<SR, SN>>],
266    );
267    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
268    fn post_render(
269        &mut self,
270        graphics: &mut Graphics,
271        mouse: &MouseData,
272        held_keys: &FxHashSet<KeyCode>,
273        scenes: &mut [Box<dyn Scene<SR, SN>>],
274    );
275    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
276    fn pre_render(
277        &mut self,
278        graphics: &mut Graphics,
279        mouse: &MouseData,
280        held_keys: &FxHashSet<KeyCode>,
281        scenes: &mut [Box<dyn Scene<SR, SN>>],
282        controller: &GameController,
283    );
284    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
285    fn post_render(
286        &mut self,
287        graphics: &mut Graphics,
288        mouse: &MouseData,
289        held_keys: &FxHashSet<KeyCode>,
290        scenes: &mut [Box<dyn Scene<SR, SN>>],
291        controller: &GameController,
292    );
293    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
294    fn pre_update(
295        &mut self,
296        timing: &Timing,
297        mouse: &MouseData,
298        held_keys: &FxHashSet<KeyCode>,
299        scenes: &mut [Box<dyn Scene<SR, SN>>],
300        window: &Window,
301    );
302    #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
303    fn post_update(
304        &mut self,
305        timing: &Timing,
306        mouse: &MouseData,
307        held_keys: &FxHashSet<KeyCode>,
308        scenes: &mut [Box<dyn Scene<SR, SN>>],
309        window: &Window,
310    );
311    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
312    fn pre_update(
313        &mut self,
314        timing: &Timing,
315        mouse: &MouseData,
316        held_keys: &FxHashSet<KeyCode>,
317        scenes: &mut [Box<dyn Scene<SR, SN>>],
318        controller: &GameController,
319        window: &Window,
320    );
321    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
322    fn post_update(
323        &mut self,
324        timing: &Timing,
325        mouse: &MouseData,
326        held_keys: &FxHashSet<KeyCode>,
327        scenes: &mut [Box<dyn Scene<SR, SN>>],
328        controller: &GameController,
329        window: &Window,
330    );
331}
332#[cfg(any(feature = "controller", feature = "controller_xinput"))]
333pub fn empty_pre_post<SR, SN>() -> Box<dyn PrePost<SR, SN>> {
334    struct Empty {}
335    impl<SR, SN> PrePost<SR, SN> for Empty {
336        fn pre_render(
337            &mut self,
338            _: &mut Graphics,
339            _: &MouseData,
340            _: &FxHashSet<KeyCode>,
341            _: &mut [Box<dyn Scene<SR, SN>>],
342            _: &GameController,
343        ) {
344        }
345
346        fn post_render(
347            &mut self,
348            _: &mut Graphics,
349            _: &MouseData,
350            _: &FxHashSet<KeyCode>,
351            _: &mut [Box<dyn Scene<SR, SN>>],
352            _: &GameController,
353        ) {
354        }
355
356        fn pre_update(
357            &mut self,
358            _: &Timing,
359            _: &MouseData,
360            _: &FxHashSet<KeyCode>,
361            _: &mut [Box<dyn Scene<SR, SN>>],
362            _: &GameController,
363            _: &Window,
364        ) {
365        }
366
367        fn post_update(
368            &mut self,
369            _: &Timing,
370            _: &MouseData,
371            _: &FxHashSet<KeyCode>,
372            _: &mut [Box<dyn Scene<SR, SN>>],
373            _: &GameController,
374            _: &Window,
375        ) {
376        }
377    }
378    Box::new(Empty {})
379}
380#[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
381pub fn empty_pre_post<SR, SN>() -> Box<dyn PrePost<SR, SN>> {
382    struct Empty {}
383    impl<SR, SN> PrePost<SR, SN> for Empty {
384        fn pre_render(
385            &mut self,
386            _: &mut Graphics,
387            _: &MouseData,
388            _: &FxHashSet<KeyCode>,
389            _: &mut [Box<dyn Scene<SR, SN>>],
390        ) {
391        }
392
393        fn post_render(
394            &mut self,
395            _: &mut Graphics,
396            _: &MouseData,
397            _: &FxHashSet<KeyCode>,
398            _: &mut [Box<dyn Scene<SR, SN>>],
399        ) {
400        }
401
402        fn pre_update(
403            &mut self,
404            _: &Timing,
405            _: &MouseData,
406            _: &FxHashSet<KeyCode>,
407            _: &mut [Box<dyn Scene<SR, SN>>],
408            _: &Window,
409        ) {
410        }
411
412        fn post_update(
413            &mut self,
414            _: &Timing,
415            _: &MouseData,
416            _: &FxHashSet<KeyCode>,
417            _: &mut [Box<dyn Scene<SR, SN>>],
418            _: &Window,
419        ) {
420        }
421    }
422    Box::new(Empty {})
423}
424
425struct SceneHost<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> {
426    should_exit: bool,
427    held_keys: FxHashSet<KeyCode>,
428    scenes: Vec<Box<dyn Scene<SR, SN>>>,
429    window_prefs: Option<WindowPreferences>,
430    scene_switcher: SceneSwitcher<SR, SN>,
431    style: UiStyle,
432    #[cfg(any(feature = "controller", feature = "controller_xinput"))]
433    controller: GameController,
434    mouse: MouseData,
435    pre_post: Box<dyn PrePost<SR, SN>>,
436}
437
438impl<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> SceneHost<SR, SN> {
439    pub fn new(
440        init_scene: Box<dyn Scene<SR, SN>>,
441        window_prefs: Option<WindowPreferences>,
442        scene_switcher: SceneSwitcher<SR, SN>,
443        style: UiStyle,
444        pre_post: Box<dyn PrePost<SR, SN>>,
445    ) -> Result<Self, GraphicsError> {
446        Ok(Self {
447            pre_post,
448            should_exit: false,
449            held_keys: FxHashSet::default(),
450            scenes: vec![init_scene],
451            window_prefs,
452            scene_switcher,
453            style,
454            mouse: MouseData::default(),
455            #[cfg(any(feature = "controller", feature = "controller_xinput"))]
456            controller: GameController::new()
457                .map_err(|e| GraphicsError::ControllerInit(e.to_string()))?,
458        })
459    }
460}
461
462impl<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> System for SceneHost<SR, SN> {
463    fn window_prefs(&mut self) -> Option<WindowPreferences> {
464        self.window_prefs.clone()
465    }
466
467    fn update(&mut self, timing: &Timing, window: &Window) {
468        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
469        self.pre_post.pre_update(
470            timing,
471            &self.mouse,
472            &self.held_keys,
473            &mut self.scenes,
474            &self.controller,
475            window,
476        );
477        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
478        self.pre_post.pre_update(
479            timing,
480            &self.mouse,
481            &self.held_keys,
482            &mut self.scenes,
483            window,
484        );
485        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
486        self.controller.update();
487        if let Some(scene) = self.scenes.last_mut() {
488            #[cfg(any(feature = "controller", feature = "controller_xinput"))]
489            let result = scene.update(
490                timing,
491                &self.mouse,
492                &self.held_keys,
493                &self.controller,
494                window,
495            );
496            #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
497            let result = scene.update(timing, &self.mouse, &self.held_keys, window);
498            match result {
499                SceneUpdateResult::Nothing => {}
500                SceneUpdateResult::Push(pop_current, name) => {
501                    if pop_current {
502                        self.scenes.pop();
503                    }
504                    (self.scene_switcher)(&self.style, &mut self.scenes, name);
505                }
506                SceneUpdateResult::Pop(result) => {
507                    self.scenes.pop();
508                    if let Some(previous) = self.scenes.last_mut() {
509                        previous.resuming(result);
510                    }
511                }
512            }
513        }
514        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
515        self.pre_post.post_update(
516            timing,
517            &self.mouse,
518            &self.held_keys,
519            &mut self.scenes,
520            &self.controller,
521            window,
522        );
523        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
524        self.pre_post.post_update(
525            timing,
526            &self.mouse,
527            &self.held_keys,
528            &mut self.scenes,
529            window,
530        );
531        if self.scenes.is_empty() {
532            self.should_exit = true;
533        }
534    }
535
536    fn render(&mut self, graphics: &mut Graphics) {
537        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
538        self.pre_post.pre_render(
539            graphics,
540            &self.mouse,
541            &self.held_keys,
542            &mut self.scenes,
543            &self.controller,
544        );
545        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
546        self.pre_post
547            .pre_render(graphics, &self.mouse, &self.held_keys, &mut self.scenes);
548        if let Some(active) = self.scenes.last() {
549            if active.is_dialog() {
550                match self.scenes.iter().rposition(|scn| !scn.is_dialog()) {
551                    None => graphics.clear(BLACK),
552                    Some(i) => {
553                        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
554                        self.scenes[i].render(
555                            graphics,
556                            &self.mouse,
557                            &self.held_keys,
558                            &self.controller,
559                        );
560                        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
561                        self.scenes[i].render(graphics, &self.mouse, &self.held_keys);
562                    }
563                }
564                #[cfg(any(feature = "controller", feature = "controller_xinput"))]
565                active.render(graphics, &self.mouse, &self.held_keys, &self.controller);
566                #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
567                active.render(graphics, &self.mouse, &self.held_keys);
568            } else {
569                #[cfg(any(feature = "controller", feature = "controller_xinput"))]
570                active.render(graphics, &self.mouse, &self.held_keys, &self.controller);
571                #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
572                active.render(graphics, &self.mouse, &self.held_keys);
573            }
574        }
575        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
576        self.pre_post.post_render(
577            graphics,
578            &self.mouse,
579            &self.held_keys,
580            &mut self.scenes,
581            &self.controller,
582        );
583        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
584        self.pre_post
585            .post_render(graphics, &self.mouse, &self.held_keys, &mut self.scenes);
586    }
587
588    fn on_mouse_move(&mut self, mouse_data: &MouseData) {
589        self.mouse = mouse_data.clone();
590        if self.mouse.any_held() {
591            if let Some(active) = self.scenes.last_mut() {
592                active.on_mouse_drag(&self.mouse, &self.held_keys)
593            }
594        }
595    }
596
597    fn on_mouse_down(&mut self, mouse: &MouseData, button: MouseButton) {
598        self.mouse = mouse.clone();
599        self.mouse.add_down(self.mouse.xy, button);
600        if let Some(active) = self.scenes.last_mut() {
601            active.on_mouse_down(&self.mouse, button, &self.held_keys);
602        }
603    }
604
605    fn on_mouse_up(&mut self, mouse: &MouseData, button: MouseButton) {
606        self.mouse = mouse.clone();
607        if let Some(active) = self.scenes.last_mut() {
608            active.on_mouse_up(&self.mouse, button, &self.held_keys);
609            if let Some(down) = self.mouse.is_down(button) {
610                active.on_mouse_click(down, &self.mouse, button, &self.held_keys);
611            }
612            self.mouse.add_up(button);
613        }
614    }
615
616    fn on_scroll(&mut self, mouse: &MouseData, x_diff: isize, y_diff: isize) {
617        self.mouse = mouse.clone();
618        if let Some(active) = self.scenes.last_mut() {
619            active.on_scroll(&self.mouse, x_diff, y_diff, &self.held_keys);
620        }
621    }
622
623    fn on_key_down(&mut self, keys: Vec<KeyCode>) {
624        for key in keys {
625            self.held_keys.insert(key);
626            if let Some(active) = self.scenes.last_mut() {
627                active.on_key_down(key, &self.mouse, &self.held_keys);
628            }
629        }
630    }
631
632    fn on_key_up(&mut self, keys: Vec<KeyCode>) {
633        for key in keys {
634            self.held_keys.remove(&key);
635            if let Some(active) = self.scenes.last_mut() {
636                active.on_key_up(key, &self.mouse, &self.held_keys);
637            }
638        }
639    }
640
641    fn should_exit(&mut self) -> bool {
642        self.should_exit
643    }
644}