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};
21
22/// Simulator window
23#[allow(dead_code)]
24pub struct Window {
25    framebuffer: Option<OutputImage<Rgb888>>,
26    #[cfg(feature = "with-sdl")]
27    sdl_window: Option<SdlWindow>,
28    title: String,
29    output_settings: OutputSettings,
30    desired_loop_duration: Duration,
31    frame_start: Instant,
32}
33
34impl Window {
35    /// Creates a new simulator window.
36    pub fn new(title: &str, output_settings: &OutputSettings) -> Self {
37        Self {
38            framebuffer: None,
39            #[cfg(feature = "with-sdl")]
40            sdl_window: None,
41            title: String::from(title),
42            output_settings: output_settings.clone(),
43            desired_loop_duration: Duration::from_millis(1000 / output_settings.max_fps as u64),
44            frame_start: Instant::now(),
45        }
46    }
47
48    /// Updates the window.
49    pub fn update<C>(&mut self, display: &SimulatorDisplay<C>)
50    where
51        C: PixelColor + Into<Rgb888> + From<Rgb888>,
52    {
53        if let Ok(path) = env::var("EG_SIMULATOR_CHECK") {
54            let output = display.to_rgb_output_image(&self.output_settings);
55
56            let png_file = BufReader::new(File::open(path).unwrap());
57            let expected = image::load(png_file, image::ImageFormat::Png)
58                .unwrap()
59                .to_rgb8();
60
61            let png_size = Size::new(expected.width(), expected.height());
62
63            assert!(
64                output.size().eq(&png_size),
65                "display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
66                output.size().width,
67                output.size().height,
68                png_size.width,
69                png_size.height
70            );
71
72            assert!(
73                output
74                    .as_image_buffer()
75                    .as_raw()
76                    .eq(&expected.as_raw().deref()),
77                "display content doesn't match PNG file",
78            );
79
80            process::exit(0);
81        }
82
83        if let Ok(path) = env::var("EG_SIMULATOR_CHECK_RAW") {
84            let expected = SimulatorDisplay::load_png(path).unwrap();
85
86            assert!(
87                display.size().eq(&expected.size()),
88                "display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
89                display.size().width,
90                display.size().height,
91                expected.size().width,
92                expected.size().height
93            );
94
95            assert!(
96                display.pixels.eq(&expected.pixels),
97                "display content doesn't match PNG file",
98            );
99
100            process::exit(0);
101        }
102
103        if let Ok(path) = env::var("EG_SIMULATOR_DUMP") {
104            display
105                .to_rgb_output_image(&self.output_settings)
106                .save_png(path)
107                .unwrap();
108            process::exit(0);
109        }
110
111        if let Ok(path) = env::var("EG_SIMULATOR_DUMP_RAW") {
112            display
113                .to_rgb_output_image(&OutputSettings::default())
114                .save_png(path)
115                .unwrap();
116            process::exit(0);
117        }
118
119        #[cfg(feature = "with-sdl")]
120        {
121            if self.framebuffer.is_none() {
122                self.framebuffer = Some(OutputImage::new(display, &self.output_settings));
123            }
124
125            if self.sdl_window.is_none() {
126                self.sdl_window = Some(SdlWindow::new(display, &self.title, &self.output_settings));
127            }
128
129            let framebuffer = self.framebuffer.as_mut().unwrap();
130            let sdl_window = self.sdl_window.as_mut().unwrap();
131
132            framebuffer.update(display);
133            sdl_window.update(framebuffer);
134        }
135
136        thread::sleep(
137            (self.frame_start + self.desired_loop_duration)
138                .saturating_duration_since(Instant::now()),
139        );
140
141        self.frame_start = Instant::now();
142    }
143
144    /// Shows a static display.
145    ///
146    /// This methods updates the window once and loops until the simulator window
147    /// is closed.
148    pub fn show_static<C>(&mut self, display: &SimulatorDisplay<C>)
149    where
150        C: PixelColor + Into<Rgb888> + From<Rgb888>,
151    {
152        self.update(display);
153
154        #[cfg(feature = "with-sdl")]
155        'running: loop {
156            if self.events().any(|e| e == SimulatorEvent::Quit) {
157                break 'running;
158            }
159            thread::sleep(Duration::from_millis(20));
160        }
161    }
162
163    /// Returns an iterator of all captured SimulatorEvents.
164    ///
165    /// # Panics
166    ///
167    /// Panics if called before [`update`](Self::update) is called at least once.
168    #[cfg(feature = "with-sdl")]
169    pub fn events(&mut self) -> impl Iterator<Item = SimulatorEvent> + '_ {
170        self.sdl_window
171            .as_mut()
172            .unwrap()
173            .events(&self.output_settings)
174    }
175}