use std::collections::HashMap;
use std::num::NonZeroU32;
use std::sync::Arc;
use std::sync::mpsc;
use std::time::Duration;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
use winit::window::{Window, WindowAttributes, WindowId};
use fovea::image::ImageView;
use crate::strategy::{DisplayStrategy, Framebuffer};
enum WindowCommand {
Show {
title: String,
framebuffer: Framebuffer,
},
Exit,
}
#[derive(Debug)]
#[allow(dead_code)]
enum WindowEvent_ {
KeyPressed { key: KeyCode, window_title: String },
WindowClosed { title: String },
AllClosed,
}
#[derive(Debug)]
enum UserEvent {
CommandAvailable,
}
struct Notifier(Box<dyn Fn() -> bool + Send + Sync>);
impl Notifier {
fn from_proxy(proxy: EventLoopProxy<UserEvent>) -> Self {
Notifier(Box::new(move || {
proxy.send_event(UserEvent::CommandAvailable).is_ok()
}))
}
fn notify(&self) -> bool {
(self.0)()
}
}
pub struct DisplayContext {
cmd_tx: mpsc::Sender<WindowCommand>,
event_rx: mpsc::Receiver<WindowEvent_>,
notifier: Notifier,
}
impl DisplayContext {
pub fn show<V, S>(&self, title: &str, image: &V, strategy: S)
where
V: ImageView,
V::Pixel: Copy,
S: DisplayStrategy<V::Pixel>,
{
let fb = Framebuffer::from_image(image, strategy);
self.show_framebuffer(title, fb);
}
pub(crate) fn show_framebuffer(&self, title: &str, fb: Framebuffer) {
let w = fb.width;
let h = fb.height;
let title_owned = title.to_string();
if self
.cmd_tx
.send(WindowCommand::Show {
title: title_owned.clone(),
framebuffer: fb,
})
.is_err()
{
log::warn!("command channel closed — event loop has exited");
return;
}
log::debug!("show: sent framebuffer for \"{title_owned}\" ({w}×{h})");
if !self.notifier.notify() {
log::warn!("event loop proxy send failed (event loop has exited)");
}
}
#[must_use]
pub fn wait_key(&self) -> Option<KeyCode> {
loop {
match self.event_rx.recv() {
Ok(WindowEvent_::KeyPressed { key, .. }) => return Some(key),
Ok(WindowEvent_::AllClosed) => return None,
Ok(WindowEvent_::WindowClosed { .. }) => {
continue;
}
Err(_) => {
return None;
}
}
}
}
#[must_use]
pub fn wait_key_timeout(&self, timeout: Duration) -> Option<KeyCode> {
let deadline = std::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if remaining.is_zero() {
return None;
}
match self.event_rx.recv_timeout(remaining) {
Ok(WindowEvent_::KeyPressed { key, .. }) => return Some(key),
Ok(WindowEvent_::AllClosed) => return None,
Ok(WindowEvent_::WindowClosed { .. }) => {
continue;
}
Err(mpsc::RecvTimeoutError::Timeout) => return None,
Err(mpsc::RecvTimeoutError::Disconnected) => return None,
}
}
}
pub fn exit(&self) {
let _ = self.cmd_tx.send(WindowCommand::Exit);
if !self.notifier.notify() {
log::warn!("event loop proxy send failed (event loop has exited)");
}
}
#[cfg(test)]
fn new_for_test(
cmd_tx: mpsc::Sender<WindowCommand>,
event_rx: mpsc::Receiver<WindowEvent_>,
) -> Self {
DisplayContext {
cmd_tx,
event_rx,
notifier: Notifier(Box::new(|| true)),
}
}
}
pub struct DebugDisplay;
impl DebugDisplay {
pub fn run<F>(user_fn: F)
where
F: FnOnce(&DisplayContext) + Send + 'static,
{
let event_loop = EventLoop::<UserEvent>::with_user_event()
.build()
.expect("failed to create event loop");
let proxy = event_loop.create_proxy();
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext {
cmd_tx: cmd_tx.clone(),
event_rx,
notifier: Notifier::from_proxy(proxy.clone()),
};
let bg_cmd_tx = cmd_tx;
let bg_notifier = Notifier::from_proxy(proxy);
std::thread::spawn(move || {
user_fn(&ctx);
let _ = bg_cmd_tx.send(WindowCommand::Exit);
if !bg_notifier.notify() {
log::warn!("event loop proxy send failed (event loop has exited)");
}
});
let mut app = App {
cmd_rx,
event_tx,
context: None,
windows: HashMap::new(),
};
event_loop
.run_app(&mut app)
.expect("event loop terminated with error");
}
}
struct App {
cmd_rx: mpsc::Receiver<WindowCommand>,
event_tx: mpsc::Sender<WindowEvent_>,
context: Option<softbuffer::Context<Arc<Window>>>,
windows: HashMap<String, WindowState>,
}
impl ApplicationHandler<UserEvent> for App {
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, _event: UserEvent) {
while let Ok(cmd) = self.cmd_rx.try_recv() {
match cmd {
WindowCommand::Show { title, framebuffer } => {
self.handle_show(event_loop, title, framebuffer);
}
WindowCommand::Exit => {
self.handle_exit(event_loop);
return;
}
}
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.handle_close(event_loop, window_id);
}
WindowEvent::RedrawRequested => {
self.handle_redraw(window_id);
}
WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Pressed => {
if let PhysicalKey::Code(key) = event.physical_key {
let title = self.title_for_window(window_id);
if let Some(title) = title {
let _ = self.event_tx.send(WindowEvent_::KeyPressed {
key,
window_title: title,
});
}
}
}
_ => {}
}
}
}
impl App {
fn handle_show(
&mut self,
event_loop: &ActiveEventLoop,
title: String,
framebuffer: Framebuffer,
) {
if let Some(state) = self.windows.get_mut(&title) {
let w = framebuffer.width;
let h = framebuffer.height;
state.framebuffer = framebuffer;
state.window.request_redraw();
log::debug!("window updated: \"{title}\" ({w}×{h})");
} else {
let w = framebuffer.width;
let h = framebuffer.height;
let attrs = WindowAttributes::default()
.with_title(&title)
.with_inner_size(winit::dpi::LogicalSize::new(w, h));
let window = match event_loop.create_window(attrs) {
Ok(win) => Arc::new(win),
Err(e) => {
log::warn!("failed to create window \"{title}\": {e}");
return;
}
};
if self.context.is_none() {
match softbuffer::Context::new(window.clone()) {
Ok(ctx) => self.context = Some(ctx),
Err(e) => {
log::warn!("failed to create softbuffer context: {e}");
return;
}
}
}
let context = self.context.as_ref().unwrap();
let surface = match softbuffer::Surface::new(context, window.clone()) {
Ok(s) => s,
Err(e) => {
log::warn!("failed to create softbuffer surface for \"{title}\": {e}");
return;
}
};
let mut state = WindowState {
window,
surface,
framebuffer,
};
state.blit(&title);
log::debug!("window created: \"{title}\" ({w}×{h})");
self.windows.insert(title, state);
}
}
fn handle_close(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId) {
let title = self.title_for_window(window_id);
if let Some(title) = title {
log::debug!("window closed: \"{title}\"");
self.windows.remove(&title);
let _ = self.event_tx.send(WindowEvent_::WindowClosed { title });
}
if self.windows.is_empty() {
log::debug!("all windows closed, exiting event loop");
let _ = self.event_tx.send(WindowEvent_::AllClosed);
event_loop.exit();
}
}
fn handle_redraw(&mut self, window_id: WindowId) {
let title = self.title_for_window(window_id);
if let Some(title) = title {
if let Some(state) = self.windows.get_mut(&title) {
state.blit(&title);
}
}
}
fn handle_exit(&mut self, event_loop: &ActiveEventLoop) {
log::debug!("exit command received, closing all windows");
self.windows.clear();
let _ = self.event_tx.send(WindowEvent_::AllClosed);
event_loop.exit();
}
fn title_for_window(&self, window_id: WindowId) -> Option<String> {
for (title, state) in &self.windows {
if state.window.id() == window_id {
return Some(title.clone());
}
}
None
}
}
struct WindowState {
window: Arc<Window>,
surface: softbuffer::Surface<Arc<Window>, Arc<Window>>,
framebuffer: Framebuffer,
}
impl WindowState {
fn blit(&mut self, title: &str) {
let size = self.window.inner_size();
let win_w = size.width;
let win_h = size.height;
if win_w == 0 || win_h == 0 {
return;
}
let nz_w = NonZeroU32::new(win_w).unwrap();
let nz_h = NonZeroU32::new(win_h).unwrap();
if let Err(e) = self.surface.resize(nz_w, nz_h) {
log::warn!("failed to resize surface for \"{title}\": {e}");
return;
}
log::trace!(
"surface resized: \"{}\" {}×{} → {}×{}",
title,
self.framebuffer.width,
self.framebuffer.height,
win_w,
win_h
);
let mut buffer = match self.surface.buffer_mut() {
Ok(buf) => buf,
Err(e) => {
log::warn!("failed to get buffer for \"{title}\": {e}");
return;
}
};
if win_w == self.framebuffer.width && win_h == self.framebuffer.height {
buffer[..self.framebuffer.data.len()].copy_from_slice(&self.framebuffer.data);
} else {
scale_blit(&self.framebuffer, &mut buffer, win_w, win_h);
}
if let Err(e) = buffer.present() {
log::warn!("failed to present buffer for \"{title}\": {e}");
}
}
}
fn scale_blit(src: &Framebuffer, dst: &mut [u32], dst_w: u32, dst_h: u32) {
if src.width == 0 || src.height == 0 {
for pixel in dst.iter_mut() {
*pixel = 0;
}
return;
}
for dy in 0..dst_h {
let sy = (dy as u64 * src.height as u64 / dst_h as u64) as u32;
let dst_row_start = (dy * dst_w) as usize;
let src_row_start = (sy * src.width) as usize;
for dx in 0..dst_w {
let sx = (dx as u64 * src.width as u64 / dst_w as u64) as u32;
dst[dst_row_start + dx as usize] = src.data[src_row_start + sx as usize];
}
}
}
pub fn show<V, S>(title: &str, image: &V, strategy: S)
where
V: ImageView,
V::Pixel: Copy,
S: DisplayStrategy<V::Pixel>,
{
use std::cell::RefCell;
thread_local! {
static SHOW_EVENT_LOOP: RefCell<EventLoop<UserEvent>> = RefCell::new(
EventLoop::<UserEvent>::with_user_event()
.build()
.expect("failed to create event loop for show()")
);
}
let fb = Framebuffer::from_image(image, strategy);
let title = title.to_string();
SHOW_EVENT_LOOP.with(|cell| {
let mut event_loop = cell.borrow_mut();
let proxy = event_loop.create_proxy();
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext {
cmd_tx: cmd_tx.clone(),
event_rx,
notifier: Notifier::from_proxy(proxy.clone()),
};
let bg_cmd_tx = cmd_tx;
let bg_notifier = Notifier::from_proxy(proxy);
std::thread::spawn(move || {
ctx.show_framebuffer(&title, fb);
let _ = ctx.wait_key();
let _ = bg_cmd_tx.send(WindowCommand::Exit);
if !bg_notifier.notify() {
log::warn!("event loop proxy send failed (event loop has exited)");
}
});
let mut app = App {
cmd_rx,
event_tx,
context: None,
windows: HashMap::new(),
};
event_loop
.run_app_on_demand(&mut app)
.expect("event loop terminated with error");
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Identity;
use crate::strategy::Framebuffer;
use fovea::image::Image;
use fovea::pixel::Srgba8;
fn make_test_ctx() -> (
DisplayContext,
mpsc::Receiver<WindowCommand>,
mpsc::Sender<WindowEvent_>,
) {
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
(ctx, cmd_rx, event_tx)
}
#[test]
fn scale_blit_identity() {
let src = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
let mut dst = vec![0u32; 4];
scale_blit(&src, &mut dst, 2, 2);
assert_eq!(dst, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn scale_blit_upscale_2x() {
let src = Framebuffer::from_raw(1, 1, vec![0xFF0000]);
let mut dst = vec![0u32; 4];
scale_blit(&src, &mut dst, 2, 2);
assert_eq!(dst, vec![0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000]);
}
#[test]
fn scale_blit_downscale() {
let src = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
let mut dst = vec![0u32; 1];
scale_blit(&src, &mut dst, 1, 1);
assert_eq!(dst, vec![0xAA]);
}
#[test]
fn scale_blit_empty_source() {
let src = Framebuffer::from_raw(0, 0, vec![]);
let mut dst = vec![0x12345678u32; 4];
scale_blit(&src, &mut dst, 2, 2);
assert_eq!(dst, vec![0, 0, 0, 0]);
}
#[test]
fn scale_blit_non_square_upscale() {
let src = Framebuffer::from_raw(2, 1, vec![0xAA, 0xBB]);
let mut dst = vec![0u32; 8];
scale_blit(&src, &mut dst, 4, 2);
assert_eq!(dst, vec![0xAA, 0xAA, 0xBB, 0xBB, 0xAA, 0xAA, 0xBB, 0xBB]);
}
#[test]
fn scale_blit_3x2_to_6x4() {
let src = Framebuffer::from_raw(3, 2, vec![1, 2, 3, 4, 5, 6]);
let mut dst = vec![0u32; 24]; scale_blit(&src, &mut dst, 6, 4);
let expected = vec![
1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 4, 4, 5, 5, 6, 6,
];
assert_eq!(dst, expected);
}
#[test]
fn notifier_noop_returns_true() {
let n = Notifier(Box::new(|| true));
assert!(n.notify());
}
#[test]
fn notifier_failing_returns_false() {
let n = Notifier(Box::new(|| false));
assert!(!n.notify());
}
#[test]
fn show_framebuffer_sends_command() {
let (ctx, cmd_rx, _event_tx) = make_test_ctx();
let fb = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
ctx.show_framebuffer("my title", fb);
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, framebuffer } => {
assert_eq!(title, "my title");
assert_eq!(framebuffer.width, 2);
assert_eq!(framebuffer.height, 2);
assert_eq!(framebuffer.data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
WindowCommand::Exit => panic!("expected Show, got Exit"),
}
}
#[test]
fn show_framebuffer_with_closed_channel_does_not_panic() {
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
drop(cmd_rx);
let fb = Framebuffer::from_raw(1, 1, vec![0]);
ctx.show_framebuffer("dead", fb);
}
#[test]
fn show_framebuffer_notifier_failure_does_not_panic() {
let (cmd_tx, _cmd_rx) = mpsc::channel::<WindowCommand>();
let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext {
cmd_tx,
event_rx,
notifier: Notifier(Box::new(|| false)),
};
let fb = Framebuffer::from_raw(1, 1, vec![0]);
ctx.show_framebuffer("fail-notify", fb);
}
#[test]
fn show_converts_image_and_sends_command() {
let (ctx, cmd_rx, _event_tx) = make_test_ctx();
let img = Image::fill(2, 2, Srgba8::new(255, 0, 0, 255));
ctx.show("red", &img, Identity);
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, framebuffer } => {
assert_eq!(title, "red");
assert_eq!(framebuffer.width, 2);
assert_eq!(framebuffer.height, 2);
assert!(framebuffer.data.iter().all(|&p| p == 0x00FF0000));
}
WindowCommand::Exit => panic!("expected Show, got Exit"),
}
}
#[test]
fn show_zero_size_image() {
let (ctx, cmd_rx, _event_tx) = make_test_ctx();
let img = Image::<Srgba8>::zero(0, 0);
ctx.show("empty", &img, Identity);
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, framebuffer } => {
assert_eq!(title, "empty");
assert_eq!(framebuffer.width, 0);
assert_eq!(framebuffer.height, 0);
assert!(framebuffer.data.is_empty());
}
WindowCommand::Exit => panic!("expected Show, got Exit"),
}
}
#[test]
fn exit_sends_exit_command() {
let (ctx, cmd_rx, _event_tx) = make_test_ctx();
ctx.exit();
match cmd_rx.recv().unwrap() {
WindowCommand::Exit => {} WindowCommand::Show { .. } => panic!("expected Exit, got Show"),
}
}
#[test]
fn exit_with_closed_channel_does_not_panic() {
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
drop(cmd_rx);
ctx.exit(); }
#[test]
fn wait_key_returns_key_on_key_pressed() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::Space,
window_title: "test".to_string(),
})
.unwrap();
assert_eq!(ctx.wait_key(), Some(KeyCode::Space));
}
#[test]
fn wait_key_skips_window_closed_waits_for_key() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::WindowClosed {
title: "closing".to_string(),
})
.unwrap();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::Enter,
window_title: "remaining".to_string(),
})
.unwrap();
assert_eq!(ctx.wait_key(), Some(KeyCode::Enter));
}
#[test]
fn wait_key_returns_none_on_all_closed() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx.send(WindowEvent_::AllClosed).unwrap();
assert_eq!(ctx.wait_key(), None);
}
#[test]
fn wait_key_returns_none_on_channel_disconnect() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
drop(event_tx);
assert_eq!(ctx.wait_key(), None);
}
#[test]
fn wait_key_skips_multiple_window_closed() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::WindowClosed {
title: "a".to_string(),
})
.unwrap();
event_tx
.send(WindowEvent_::WindowClosed {
title: "b".to_string(),
})
.unwrap();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::KeyA,
window_title: "c".to_string(),
})
.unwrap();
assert_eq!(ctx.wait_key(), Some(KeyCode::KeyA));
}
#[test]
fn wait_key_timeout_returns_key_before_timeout() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::Escape,
window_title: "w".to_string(),
})
.unwrap();
let result = ctx.wait_key_timeout(Duration::from_secs(5));
assert_eq!(result, Some(KeyCode::Escape));
}
#[test]
fn wait_key_timeout_returns_none_on_timeout() {
let (ctx, _cmd_rx, _event_tx) = make_test_ctx();
let result = ctx.wait_key_timeout(Duration::from_millis(10));
assert_eq!(result, None);
}
#[test]
fn wait_key_timeout_returns_none_on_all_closed() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx.send(WindowEvent_::AllClosed).unwrap();
let result = ctx.wait_key_timeout(Duration::from_secs(5));
assert_eq!(result, None);
}
#[test]
fn wait_key_timeout_returns_none_on_disconnect() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
drop(event_tx);
let result = ctx.wait_key_timeout(Duration::from_secs(5));
assert_eq!(result, None);
}
#[test]
fn wait_key_timeout_skips_window_closed() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::WindowClosed {
title: "gone".to_string(),
})
.unwrap();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::KeyZ,
window_title: "still here".to_string(),
})
.unwrap();
let result = ctx.wait_key_timeout(Duration::from_secs(5));
assert_eq!(result, Some(KeyCode::KeyZ));
}
#[test]
fn wait_key_timeout_zero_returns_none_immediately() {
let (ctx, _cmd_rx, event_tx) = make_test_ctx();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::KeyX,
window_title: "w".to_string(),
})
.unwrap();
let result = ctx.wait_key_timeout(Duration::ZERO);
assert!(result.is_none() || result == Some(KeyCode::KeyX));
}
#[test]
fn show_then_wait_key_workflow() {
let (ctx, cmd_rx, event_tx) = make_test_ctx();
let img = Image::fill(4, 4, Srgba8::new(0, 255, 0, 255));
ctx.show("green", &img, Identity);
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, framebuffer } => {
assert_eq!(title, "green");
assert_eq!(framebuffer.width, 4);
assert_eq!(framebuffer.height, 4);
assert_eq!(framebuffer.data.len(), 16);
assert!(framebuffer.data.iter().all(|&p| p == 0x0000FF00));
}
WindowCommand::Exit => panic!("expected Show"),
}
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::KeyQ,
window_title: "green".to_string(),
})
.unwrap();
assert_eq!(ctx.wait_key(), Some(KeyCode::KeyQ));
}
#[test]
fn multiple_shows_then_exit_workflow() {
let (ctx, cmd_rx, _event_tx) = make_test_ctx();
let img1 = Image::fill(1, 1, Srgba8::new(255, 0, 0, 255));
let img2 = Image::fill(1, 1, Srgba8::new(0, 0, 255, 255));
ctx.show("red", &img1, Identity);
ctx.show("blue", &img2, Identity);
ctx.exit();
let mut titles = Vec::new();
let mut got_exit = false;
for _ in 0..3 {
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, .. } => titles.push(title),
WindowCommand::Exit => got_exit = true,
}
}
assert_eq!(titles, vec!["red", "blue"]);
assert!(got_exit);
}
#[test]
fn channel_show_sends_correct_command() {
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
cmd_tx
.send(WindowCommand::Show {
title: "test".to_string(),
framebuffer: Framebuffer::from_raw(2, 2, vec![0, 0, 0, 0]),
})
.unwrap();
match cmd_rx.recv().unwrap() {
WindowCommand::Show { title, framebuffer } => {
assert_eq!(title, "test");
assert_eq!(framebuffer.width, 2);
assert_eq!(framebuffer.height, 2);
assert_eq!(framebuffer.data.len(), 4);
}
WindowCommand::Exit => panic!("expected Show, got Exit"),
}
}
#[test]
fn channel_exit_command() {
let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
cmd_tx.send(WindowCommand::Exit).unwrap();
match cmd_rx.recv().unwrap() {
WindowCommand::Exit => {} WindowCommand::Show { .. } => panic!("expected Exit, got Show"),
}
}
#[test]
fn channel_key_pressed_event() {
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
event_tx
.send(WindowEvent_::KeyPressed {
key: KeyCode::Escape,
window_title: "test window".to_string(),
})
.unwrap();
match event_rx.recv().unwrap() {
WindowEvent_::KeyPressed { key, window_title } => {
assert_eq!(key, KeyCode::Escape);
assert_eq!(window_title, "test window");
}
other => panic!("expected KeyPressed, got {:?}", other),
}
}
#[test]
fn channel_window_closed_event() {
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
event_tx
.send(WindowEvent_::WindowClosed {
title: "my window".to_string(),
})
.unwrap();
match event_rx.recv().unwrap() {
WindowEvent_::WindowClosed { title } => {
assert_eq!(title, "my window");
}
other => panic!("expected WindowClosed, got {:?}", other),
}
}
#[test]
fn channel_all_closed_event() {
let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
event_tx.send(WindowEvent_::AllClosed).unwrap();
match event_rx.recv().unwrap() {
WindowEvent_::AllClosed => {} other => panic!("expected AllClosed, got {:?}", other),
}
}
#[test]
fn framebuffer_is_send() {
fn assert_send<T: Send>() {}
assert_send::<Framebuffer>();
}
#[test]
fn display_context_is_send() {
fn assert_send<T: Send>() {}
assert_send::<DisplayContext>();
}
}