embedded_graphics_simulator/window/
multi_window.rs

1use std::collections::HashMap;
2
3use embedded_graphics::{pixelcolor::Rgb888, prelude::*};
4
5use crate::{
6    window::{sdl_window::SimulatorEventsIter, FpsLimiter, SdlWindow},
7    OutputImage, OutputSettings, SimulatorDisplay,
8};
9
10/// Simulator window with support for multiple displays.
11///
12/// Multiple [`SimulatorDisplay`]s can be added to the window by using the
13/// [`add_display`](Self::add_display) method. To update the window two steps
14/// are required, first [`update_display`](Self::update_display) needs be called
15/// for all changed displays, then [`flush`](Self::flush) to redraw the window.
16///
17/// To determine if the mouse pointer is over one of the displays the
18/// [`translate_mouse_position`](Self::translate_mouse_position) can be used to
19/// translate window coordinates into display coordinates.
20pub struct MultiWindow {
21    sdl_window: SdlWindow,
22    framebuffer: OutputImage<Rgb888>,
23    displays: HashMap<usize, DisplaySettings>,
24    fps_limiter: FpsLimiter,
25}
26
27impl MultiWindow {
28    /// Creates a new window with support for multiple displays.
29    pub fn new(title: &str, size: Size) -> Self {
30        let mut sdl_window = SdlWindow::new(title, size);
31
32        let framebuffer = OutputImage::new(size);
33
34        sdl_window.update(&framebuffer);
35
36        Self {
37            sdl_window: sdl_window,
38            framebuffer,
39            displays: HashMap::new(),
40            fps_limiter: FpsLimiter::new(),
41        }
42    }
43
44    /// Adds a display to the window.
45    pub fn add_display<C>(
46        &mut self,
47        display: &SimulatorDisplay<C>,
48        offset: Point,
49        output_settings: &OutputSettings,
50    ) {
51        self.displays.insert(
52            display.id,
53            DisplaySettings {
54                offset,
55                output_settings: output_settings.clone(),
56            },
57        );
58    }
59
60    /// Fills the internal framebuffer with the given color.
61    ///
62    /// This method can be used to set the background color for the regions of
63    /// the window that aren't covered by a display.
64    pub fn clear(&mut self, color: Rgb888) {
65        self.framebuffer.clear(color).unwrap();
66    }
67
68    /// Updates one display.
69    ///
70    /// This method only updates the internal framebuffer. Use
71    /// [`flush`](Self::flush) after all displays have been updated to finally
72    /// update the window.
73    pub fn update_display<C>(&mut self, display: &SimulatorDisplay<C>)
74    where
75        C: PixelColor + Into<Rgb888> + From<Rgb888>,
76    {
77        let display_settings = self
78            .displays
79            .get(&display.id)
80            .expect("update_display called for a display that hasn't been added with add_display");
81
82        self.framebuffer.draw_display(
83            display,
84            display_settings.offset,
85            &display_settings.output_settings,
86        );
87    }
88
89    /// Updates the window from the internal framebuffer.
90    pub fn flush(&mut self) {
91        self.sdl_window.update(&self.framebuffer);
92
93        self.fps_limiter.sleep();
94    }
95
96    /// Returns an iterator of all captured simulator events.
97    ///
98    /// The coordinates in mouse events are in raw window coordinates, use
99    /// [`translate_mouse_position`](Self::translate_mouse_position) to
100    /// translate them into display coordinates.
101    ///
102    /// # Panics
103    ///
104    /// Panics if multiple instances of the iterator are used at the same time.
105    pub fn events(&self) -> SimulatorEventsIter<'_> {
106        self.sdl_window.events(&crate::OutputSettings::default())
107    }
108
109    /// Translate a mouse position into display coordinates.
110    ///
111    /// Returns the corresponding position in the display coordinate system if
112    /// the mouse is inside the display area, otherwise `None` is returned.
113    pub fn translate_mouse_position<C>(
114        &self,
115        display: &SimulatorDisplay<C>,
116        position: Point,
117    ) -> Option<Point> {
118        let display_settings = self.displays.get(&display.id).expect(
119            "translate_mouse_position called for a display that hasn't been added with add_display",
120        );
121
122        let delta = position - display_settings.offset;
123        let p = display_settings.output_settings.output_to_display(delta);
124
125        display.bounding_box().contains(p).then_some(p)
126    }
127
128    /// Sets the FPS limit of the window.
129    pub fn set_max_fps(&mut self, max_fps: u32) {
130        self.fps_limiter.max_fps = max_fps;
131    }
132}
133
134struct DisplaySettings {
135    offset: Point,
136    output_settings: OutputSettings,
137}