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 {
455                xy: Default::default(),
456                buttons: Default::default(),
457            },
458            #[cfg(any(feature = "controller", feature = "controller_xinput"))]
459            controller: GameController::new()
460                .map_err(|e| GraphicsError::ControllerInit(e.to_string()))?,
461        })
462    }
463}
464
465impl<SR: Clone + PartialEq + Debug, SN: Clone + PartialEq + Debug> System for SceneHost<SR, SN> {
466    fn window_prefs(&mut self) -> Option<WindowPreferences> {
467        self.window_prefs.clone()
468    }
469
470    fn update(&mut self, timing: &Timing, window: &Window) {
471        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
472        self.pre_post.pre_update(
473            timing,
474            &self.mouse,
475            &self.held_keys,
476            &mut self.scenes,
477            &self.controller,
478            window,
479        );
480        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
481        self.pre_post.pre_update(
482            timing,
483            &self.mouse,
484            &self.held_keys,
485            &mut self.scenes,
486            window,
487        );
488        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
489        self.controller.update();
490        if let Some(scene) = self.scenes.last_mut() {
491            #[cfg(any(feature = "controller", feature = "controller_xinput"))]
492            let result = scene.update(
493                timing,
494                &self.mouse,
495                &self.held_keys,
496                &self.controller,
497                window,
498            );
499            #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
500            let result = scene.update(timing, &self.mouse, &self.held_keys, window);
501            match result {
502                SceneUpdateResult::Nothing => {}
503                SceneUpdateResult::Push(pop_current, name) => {
504                    if pop_current {
505                        self.scenes.pop();
506                    }
507                    (self.scene_switcher)(&self.style, &mut self.scenes, name);
508                }
509                SceneUpdateResult::Pop(result) => {
510                    self.scenes.pop();
511                    if let Some(previous) = self.scenes.last_mut() {
512                        previous.resuming(result);
513                    }
514                }
515            }
516        }
517        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
518        self.pre_post.post_update(
519            timing,
520            &self.mouse,
521            &self.held_keys,
522            &mut self.scenes,
523            &self.controller,
524            window,
525        );
526        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
527        self.pre_post.post_update(
528            timing,
529            &self.mouse,
530            &self.held_keys,
531            &mut self.scenes,
532            window,
533        );
534        if self.scenes.is_empty() {
535            self.should_exit = true;
536        }
537    }
538
539    fn render(&mut self, graphics: &mut Graphics) {
540        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
541        self.pre_post.pre_render(
542            graphics,
543            &self.mouse,
544            &self.held_keys,
545            &mut self.scenes,
546            &self.controller,
547        );
548        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
549        self.pre_post
550            .pre_render(graphics, &self.mouse, &self.held_keys, &mut self.scenes);
551        if let Some(active) = self.scenes.last() {
552            if active.is_dialog() {
553                match self.scenes.iter().rposition(|scn| !scn.is_dialog()) {
554                    None => graphics.clear(BLACK),
555                    Some(i) => {
556                        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
557                        self.scenes[i].render(
558                            graphics,
559                            &self.mouse,
560                            &self.held_keys,
561                            &self.controller,
562                        );
563                        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
564                        self.scenes[i].render(graphics, &self.mouse, &self.held_keys);
565                    }
566                }
567                #[cfg(any(feature = "controller", feature = "controller_xinput"))]
568                active.render(graphics, &self.mouse, &self.held_keys, &self.controller);
569                #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
570                active.render(graphics, &self.mouse, &self.held_keys);
571            } else {
572                #[cfg(any(feature = "controller", feature = "controller_xinput"))]
573                active.render(graphics, &self.mouse, &self.held_keys, &self.controller);
574                #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
575                active.render(graphics, &self.mouse, &self.held_keys);
576            }
577        }
578        #[cfg(any(feature = "controller", feature = "controller_xinput"))]
579        self.pre_post.post_render(
580            graphics,
581            &self.mouse,
582            &self.held_keys,
583            &mut self.scenes,
584            &self.controller,
585        );
586        #[cfg(not(any(feature = "controller", feature = "controller_xinput")))]
587        self.pre_post
588            .post_render(graphics, &self.mouse, &self.held_keys, &mut self.scenes);
589    }
590
591    fn on_mouse_move(&mut self, mouse: &MouseData) {
592        self.mouse.xy = mouse.xy;
593        if self.mouse.any_held() {
594            if let Some(active) = self.scenes.last_mut() {
595                active.on_mouse_drag(&self.mouse, &self.held_keys)
596            }
597        }
598    }
599
600    fn on_mouse_down(&mut self, mouse: &MouseData, button: MouseButton) {
601        self.mouse.xy = mouse.xy;
602        self.mouse.add_down(self.mouse.xy, button);
603        if let Some(active) = self.scenes.last_mut() {
604            active.on_mouse_down(&self.mouse, button, &self.held_keys);
605        }
606    }
607
608    fn on_mouse_up(&mut self, mouse: &MouseData, button: MouseButton) {
609        self.mouse.xy = mouse.xy;
610        if let Some(active) = self.scenes.last_mut() {
611            active.on_mouse_up(&self.mouse, button, &self.held_keys);
612            if let Some(down) = self.mouse.is_down(button) {
613                active.on_mouse_click(down, &self.mouse, button, &self.held_keys);
614            }
615            self.mouse.add_up(button);
616        }
617    }
618
619    fn on_scroll(&mut self, mouse: &MouseData, x_diff: isize, y_diff: isize) {
620        self.mouse.xy = mouse.xy;
621        if let Some(active) = self.scenes.last_mut() {
622            active.on_scroll(&self.mouse, x_diff, y_diff, &self.held_keys);
623        }
624    }
625
626    fn on_key_down(&mut self, keys: Vec<KeyCode>) {
627        for key in keys {
628            self.held_keys.insert(key);
629            if let Some(active) = self.scenes.last_mut() {
630                active.on_key_down(key, &self.mouse, &self.held_keys);
631            }
632        }
633    }
634
635    fn on_key_up(&mut self, keys: Vec<KeyCode>) {
636        for key in keys {
637            self.held_keys.remove(&key);
638            if let Some(active) = self.scenes.last_mut() {
639                active.on_key_up(key, &self.mouse, &self.held_keys);
640            }
641        }
642    }
643
644    fn should_exit(&mut self) -> bool {
645        self.should_exit
646    }
647}