use std::{fs::File, io::BufReader, ops::Deref};
#[cfg(feature = "with-sdl")]
use std::{thread, time::Duration};
#[cfg(feature = "with-sdl")]
use sdl2::{
event::Event,
keyboard::{Keycode, Mod},
mouse::{MouseButton, MouseWheelDirection},
render,
};
use embedded_graphics::{pixelcolor::Rgb888, prelude::*};
use crate::{
display::SimulatorDisplay, output_image::OutputImage, output_settings::OutputSettings,
};
#[cfg(feature = "with-sdl")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SimulatorEvent {
KeyUp {
keycode: Keycode,
keymod: Mod,
repeat: bool,
},
KeyDown {
keycode: Keycode,
keymod: Mod,
repeat: bool,
},
MouseButtonUp {
mouse_btn: MouseButton,
point: Point,
},
MouseButtonDown {
mouse_btn: MouseButton,
point: Point,
},
MouseWheel {
scroll_delta: Point,
direction: MouseWheelDirection,
},
MouseMove {
point: Point,
},
Quit,
}
#[allow(dead_code)]
pub struct Window {
framebuffer: Option<OutputImage<Rgb888>>,
#[cfg(feature = "with-sdl")]
sdl_window: Option<SdlWindow>,
title: String,
output_settings: OutputSettings,
}
impl Window {
pub fn new(title: &str, output_settings: &OutputSettings) -> Self {
Self {
framebuffer: None,
#[cfg(feature = "with-sdl")]
sdl_window: None,
title: String::from(title),
output_settings: output_settings.clone(),
}
}
pub fn update<C>(&mut self, display: &SimulatorDisplay<C>)
where
C: PixelColor + Into<Rgb888> + From<Rgb888>,
{
if let Ok(path) = std::env::var("EG_SIMULATOR_CHECK") {
let output = display.to_rgb_output_image(&self.output_settings);
let png_file = BufReader::new(File::open(path).unwrap());
let expected = image::load(png_file, image::ImageFormat::Png)
.unwrap()
.to_rgb8();
let png_size = Size::new(expected.width(), expected.height());
assert!(
output.size().eq(&png_size),
"display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
output.size().width,
output.size().height,
png_size.width,
png_size.height
);
assert!(
output
.as_image_buffer()
.as_raw()
.eq(&expected.as_raw().deref()),
"display content doesn't match PNG file",
);
std::process::exit(0);
}
if let Ok(path) = std::env::var("EG_SIMULATOR_CHECK_RAW") {
let expected = SimulatorDisplay::load_png(path).unwrap();
assert!(
display.size().eq(&expected.size()),
"display dimensions don't match PNG dimensions (display: {}x{}, PNG: {}x{})",
display.size().width,
display.size().height,
expected.size().width,
expected.size().height
);
assert!(
display.pixels.eq(&expected.pixels),
"display content doesn't match PNG file",
);
std::process::exit(0);
}
if let Ok(path) = std::env::var("EG_SIMULATOR_DUMP") {
display
.to_rgb_output_image(&self.output_settings)
.save_png(path)
.unwrap();
std::process::exit(0);
}
if let Ok(path) = std::env::var("EG_SIMULATOR_DUMP_RAW") {
display
.to_rgb_output_image(&OutputSettings::default())
.save_png(path)
.unwrap();
std::process::exit(0);
}
#[cfg(feature = "with-sdl")]
{
if self.framebuffer.is_none() {
self.framebuffer = Some(OutputImage::new(display, &self.output_settings));
}
if self.sdl_window.is_none() {
self.sdl_window = Some(SdlWindow::new(display, &self.title, &self.output_settings));
}
let framebuffer = self.framebuffer.as_mut().unwrap();
let sdl_window = self.sdl_window.as_mut().unwrap();
framebuffer.update(display);
sdl_window.update(&framebuffer);
}
}
pub fn show_static<C>(&mut self, display: &SimulatorDisplay<C>)
where
C: PixelColor + Into<Rgb888> + From<Rgb888>,
{
self.update(&display);
#[cfg(feature = "with-sdl")]
'running: loop {
if self.events().any(|e| e == SimulatorEvent::Quit) {
break 'running;
}
thread::sleep(Duration::from_millis(20));
}
}
#[cfg(feature = "with-sdl")]
pub fn events(&mut self) -> impl Iterator<Item = SimulatorEvent> + '_ {
self.sdl_window
.as_mut()
.unwrap()
.events(&self.output_settings)
}
}
#[cfg(feature = "with-sdl")]
struct SdlWindow {
canvas: render::Canvas<sdl2::video::Window>,
event_pump: sdl2::EventPump,
}
#[cfg(feature = "with-sdl")]
impl SdlWindow {
pub fn new<C>(
display: &SimulatorDisplay<C>,
title: &str,
output_settings: &OutputSettings,
) -> Self
where
C: PixelColor + Into<Rgb888>,
{
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let size = output_settings.framebuffer_size(display);
let window = video_subsystem
.window(title, size.width, size.height)
.position_centered()
.build()
.unwrap();
let canvas = window.into_canvas().build().unwrap();
let event_pump = sdl_context.event_pump().unwrap();
Self { canvas, event_pump }
}
pub fn update(&mut self, framebuffer: &OutputImage<Rgb888>) {
let Size { width, height } = framebuffer.size();
let texture_creator = self.canvas.texture_creator();
let mut texture = texture_creator
.create_texture_streaming(sdl2::pixels::PixelFormatEnum::RGB24, width, height)
.unwrap();
texture
.update(None, framebuffer.data.as_ref(), width as usize * 3)
.unwrap();
self.canvas.copy(&texture, None, None).unwrap();
self.canvas.present();
}
pub fn events(
&mut self,
output_settings: &OutputSettings,
) -> impl Iterator<Item = SimulatorEvent> + '_ {
let output_settings = output_settings.clone();
self.event_pump
.poll_iter()
.filter_map(move |event| match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => Some(SimulatorEvent::Quit),
Event::KeyDown {
keycode,
keymod,
repeat,
..
} => {
if let Some(valid_keycode) = keycode {
Some(SimulatorEvent::KeyDown {
keycode: valid_keycode,
keymod,
repeat,
})
} else {
None
}
}
Event::KeyUp {
keycode,
keymod,
repeat,
..
} => {
if let Some(valid_keycode) = keycode {
Some(SimulatorEvent::KeyUp {
keycode: valid_keycode,
keymod,
repeat,
})
} else {
None
}
}
Event::MouseButtonUp {
x, y, mouse_btn, ..
} => {
let point = output_settings.output_to_display(Point::new(x, y));
Some(SimulatorEvent::MouseButtonUp { point, mouse_btn })
}
Event::MouseButtonDown {
x, y, mouse_btn, ..
} => {
let point = output_settings.output_to_display(Point::new(x, y));
Some(SimulatorEvent::MouseButtonDown { point, mouse_btn })
}
Event::MouseWheel {
x, y, direction, ..
} => Some(SimulatorEvent::MouseWheel {
scroll_delta: Point::new(x, y),
direction,
}),
Event::MouseMotion { x, y, .. } => {
let point = output_settings.output_to_display(Point::new(x, y));
Some(SimulatorEvent::MouseMove { point })
}
_ => None,
})
}
}