embedded_graphics_simulator/window/
mod.rs

1use std::{
2    env,
3    fs::File,
4    io::BufReader,
5    ops::Deref,
6    process, thread,
7    time::{Duration, Instant},
8};
9
10use embedded_graphics::{pixelcolor::Rgb888, prelude::*};
11
12use crate::{
13    display::SimulatorDisplay, output_image::OutputImage, output_settings::OutputSettings,
14};
15
16#[cfg(feature = "with-sdl")]
17mod sdl_window;
18
19#[cfg(feature = "with-sdl")]
20pub use sdl_window::{SdlWindow, SimulatorEvent, SimulatorEventsIter};
21
22#[cfg(feature = "with-sdl")]
23mod multi_window;
24
25#[cfg(feature = "with-sdl")]
26pub use multi_window::MultiWindow;
27
28pub(crate) struct FpsLimiter {
29    max_fps: u32,
30    frame_start: Instant,
31}
32
33impl FpsLimiter {
34    pub(crate) fn new() -> Self {
35        Self {
36            max_fps: 60,
37            frame_start: Instant::now(),
38        }
39    }
40
41    fn desired_loop_duration(&self) -> Duration {
42        Duration::from_secs_f32(1.0 / self.max_fps as f32)
43    }
44
45    fn sleep(&mut self) {
46        let sleep_duration = (self.frame_start + self.desired_loop_duration())
47            .saturating_duration_since(Instant::now());
48        thread::sleep(sleep_duration);
49
50        self.frame_start = Instant::now();
51    }
52}
53
54/// Simulator window
55#[allow(dead_code)]
56pub struct Window {
57    framebuffer: Option<OutputImage<Rgb888>>,
58    #[cfg(feature = "with-sdl")]
59    sdl_window: Option<SdlWindow>,
60    title: String,
61    output_settings: OutputSettings,
62    fps_limiter: FpsLimiter,
63}
64
65impl Window {
66    /// Creates a new simulator window.
67    pub fn new(title: &str, output_settings: &OutputSettings) -> Self {
68        Self {
69            framebuffer: None,
70            #[cfg(feature = "with-sdl")]
71            sdl_window: None,
72            title: String::from(title),
73            output_settings: output_settings.clone(),
74            fps_limiter: FpsLimiter::new(),
75        }
76    }
77
78    /// Updates the window.
79    pub fn update<C>(&mut self, display: &SimulatorDisplay<C>)
80    where
81        C: PixelColor + Into<Rgb888> + From<Rgb888>,
82    {
83        if let Ok(path) = env::var("EG_SIMULATOR_CHECK") {
84            let output = display.to_rgb_output_image(&self.output_settings);
85
86            let png_file = BufReader::new(File::open(path).unwrap());
87            let expected = image::load(png_file, image::ImageFormat::Png)
88                .unwrap()
89                .to_rgb8();
90
91            let png_size = Size::new(expected.width(), expected.height());
92
93            assert!(
94                output.size().eq(&png_size),
95                "display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
96                output.size().width,
97                output.size().height,
98                png_size.width,
99                png_size.height
100            );
101
102            assert!(
103                output
104                    .as_image_buffer()
105                    .as_raw()
106                    .eq(&expected.as_raw().deref()),
107                "display content doesn't match PNG file",
108            );
109
110            process::exit(0);
111        }
112
113        if let Ok(path) = env::var("EG_SIMULATOR_CHECK_RAW") {
114            let expected = SimulatorDisplay::load_png(path).unwrap();
115
116            assert!(
117                display.size().eq(&expected.size()),
118                "display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
119                display.size().width,
120                display.size().height,
121                expected.size().width,
122                expected.size().height
123            );
124
125            assert!(
126                display.pixels.eq(&expected.pixels),
127                "display content doesn't match PNG file",
128            );
129
130            process::exit(0);
131        }
132
133        if let Ok(path) = env::var("EG_SIMULATOR_DUMP") {
134            display
135                .to_rgb_output_image(&self.output_settings)
136                .save_png(path)
137                .unwrap();
138            process::exit(0);
139        }
140
141        if let Ok(path) = env::var("EG_SIMULATOR_DUMP_RAW") {
142            display
143                .to_rgb_output_image(&OutputSettings::default())
144                .save_png(path)
145                .unwrap();
146            process::exit(0);
147        }
148
149        #[cfg(feature = "with-sdl")]
150        {
151            let size = display.output_size(&self.output_settings);
152
153            if self.framebuffer.is_none() {
154                self.framebuffer = Some(OutputImage::new(size));
155            }
156
157            if self.sdl_window.is_none() {
158                self.sdl_window = Some(SdlWindow::new(&self.title, size));
159            }
160
161            let framebuffer = self.framebuffer.as_mut().unwrap();
162            let sdl_window = self.sdl_window.as_mut().unwrap();
163
164            framebuffer.draw_display(display, Point::zero(), &self.output_settings);
165            sdl_window.update(framebuffer);
166        }
167
168        self.fps_limiter.sleep();
169    }
170
171    /// Shows a static display.
172    ///
173    /// This methods updates the window once and loops until the simulator window
174    /// is closed.
175    pub fn show_static<C>(&mut self, display: &SimulatorDisplay<C>)
176    where
177        C: PixelColor + Into<Rgb888> + From<Rgb888>,
178    {
179        self.update(display);
180
181        #[cfg(feature = "with-sdl")]
182        'running: loop {
183            if self.events().any(|e| e == SimulatorEvent::Quit) {
184                break 'running;
185            }
186            thread::sleep(Duration::from_millis(20));
187        }
188    }
189
190    /// Returns an iterator of all captured simulator events.
191    ///
192    /// # Panics
193    ///
194    /// Panics if called before [`update`](Self::update) is called at least
195    /// once. Also panics if multiple instances of the iterator are used at the
196    /// same time.
197    #[cfg(feature = "with-sdl")]
198    pub fn events(&self) -> SimulatorEventsIter<'_> {
199        self.sdl_window
200            .as_ref()
201            .unwrap()
202            .events(&self.output_settings)
203    }
204
205    /// Sets the FPS limit of the window.
206    pub fn set_max_fps(&mut self, max_fps: u32) {
207        self.fps_limiter.max_fps = max_fps;
208    }
209}