embedded_graphics_simulator/window/
mod.rs1use 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#[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 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 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 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 #[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 pub fn set_max_fps(&mut self, max_fps: u32) {
207 self.fps_limiter.max_fps = max_fps;
208 }
209}