1use std::collections::HashMap;
80use std::num::NonZeroU32;
81use std::sync::Arc;
82use std::sync::mpsc;
83use std::time::Duration;
84
85use winit::application::ApplicationHandler;
86use winit::event::{ElementState, WindowEvent};
87use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
88use winit::keyboard::{KeyCode, PhysicalKey};
89use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
90use winit::window::{Window, WindowAttributes, WindowId};
91
92use fovea::image::ImageView;
93
94use crate::strategy::{DisplayStrategy, Framebuffer};
95
96enum WindowCommand {
102 Show {
104 title: String,
105 framebuffer: Framebuffer,
106 },
107 Exit,
109}
110
111#[derive(Debug)]
113#[allow(dead_code)]
114enum WindowEvent_ {
115 KeyPressed { key: KeyCode, window_title: String },
117 WindowClosed { title: String },
119 AllClosed,
121}
122
123#[derive(Debug)]
125enum UserEvent {
126 CommandAvailable,
128}
129
130struct Notifier(Box<dyn Fn() -> bool + Send + Sync>);
140
141impl Notifier {
142 fn from_proxy(proxy: EventLoopProxy<UserEvent>) -> Self {
144 Notifier(Box::new(move || {
145 proxy.send_event(UserEvent::CommandAvailable).is_ok()
146 }))
147 }
148
149 fn notify(&self) -> bool {
152 (self.0)()
153 }
154}
155
156pub struct DisplayContext {
179 cmd_tx: mpsc::Sender<WindowCommand>,
180 event_rx: mpsc::Receiver<WindowEvent_>,
181 notifier: Notifier,
182}
183
184impl DisplayContext {
185 pub fn show<V, S>(&self, title: &str, image: &V, strategy: S)
200 where
201 V: ImageView,
202 V::Pixel: Copy,
203 S: DisplayStrategy<V::Pixel>,
204 {
205 let fb = Framebuffer::from_image(image, strategy);
206 self.show_framebuffer(title, fb);
207 }
208
209 pub(crate) fn show_framebuffer(&self, title: &str, fb: Framebuffer) {
214 let w = fb.width;
215 let h = fb.height;
216 let title_owned = title.to_string();
217
218 if self
219 .cmd_tx
220 .send(WindowCommand::Show {
221 title: title_owned.clone(),
222 framebuffer: fb,
223 })
224 .is_err()
225 {
226 log::warn!("command channel closed — event loop has exited");
227 return;
228 }
229
230 log::debug!("show: sent framebuffer for \"{title_owned}\" ({w}×{h})");
231
232 if !self.notifier.notify() {
233 log::warn!("event loop proxy send failed (event loop has exited)");
234 }
235 }
236
237 #[must_use]
259 pub fn wait_key(&self) -> Option<KeyCode> {
260 loop {
261 match self.event_rx.recv() {
262 Ok(WindowEvent_::KeyPressed { key, .. }) => return Some(key),
263 Ok(WindowEvent_::AllClosed) => return None,
264 Ok(WindowEvent_::WindowClosed { .. }) => {
265 continue;
268 }
269 Err(_) => {
270 return None;
272 }
273 }
274 }
275 }
276
277 #[must_use]
282 pub fn wait_key_timeout(&self, timeout: Duration) -> Option<KeyCode> {
283 let deadline = std::time::Instant::now() + timeout;
284 loop {
285 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
286 if remaining.is_zero() {
287 return None;
288 }
289 match self.event_rx.recv_timeout(remaining) {
290 Ok(WindowEvent_::KeyPressed { key, .. }) => return Some(key),
291 Ok(WindowEvent_::AllClosed) => return None,
292 Ok(WindowEvent_::WindowClosed { .. }) => {
293 continue;
295 }
296 Err(mpsc::RecvTimeoutError::Timeout) => return None,
297 Err(mpsc::RecvTimeoutError::Disconnected) => return None,
298 }
299 }
300 }
301
302 pub fn exit(&self) {
307 let _ = self.cmd_tx.send(WindowCommand::Exit);
308 if !self.notifier.notify() {
309 log::warn!("event loop proxy send failed (event loop has exited)");
310 }
311 }
312
313 #[cfg(test)]
315 fn new_for_test(
316 cmd_tx: mpsc::Sender<WindowCommand>,
317 event_rx: mpsc::Receiver<WindowEvent_>,
318 ) -> Self {
319 DisplayContext {
320 cmd_tx,
321 event_rx,
322 notifier: Notifier(Box::new(|| true)),
323 }
324 }
325}
326
327pub struct DebugDisplay;
337
338impl DebugDisplay {
339 pub fn run<F>(user_fn: F)
368 where
369 F: FnOnce(&DisplayContext) + Send + 'static,
370 {
371 let event_loop = EventLoop::<UserEvent>::with_user_event()
373 .build()
374 .expect("failed to create event loop");
375
376 let proxy = event_loop.create_proxy();
377
378 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
380
381 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
383
384 let ctx = DisplayContext {
385 cmd_tx: cmd_tx.clone(),
386 event_rx,
387 notifier: Notifier::from_proxy(proxy.clone()),
388 };
389
390 let bg_cmd_tx = cmd_tx;
392 let bg_notifier = Notifier::from_proxy(proxy);
393 std::thread::spawn(move || {
394 user_fn(&ctx);
395
396 let _ = bg_cmd_tx.send(WindowCommand::Exit);
398 if !bg_notifier.notify() {
399 log::warn!("event loop proxy send failed (event loop has exited)");
400 }
401 });
402
403 let mut app = App {
405 cmd_rx,
406 event_tx,
407 context: None,
408 windows: HashMap::new(),
409 };
410
411 event_loop
412 .run_app(&mut app)
413 .expect("event loop terminated with error");
414 }
415}
416
417struct App {
423 cmd_rx: mpsc::Receiver<WindowCommand>,
424 event_tx: mpsc::Sender<WindowEvent_>,
425 context: Option<softbuffer::Context<Arc<Window>>>,
428 windows: HashMap<String, WindowState>,
430}
431
432impl ApplicationHandler<UserEvent> for App {
433 fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
434 }
437
438 fn user_event(&mut self, event_loop: &ActiveEventLoop, _event: UserEvent) {
439 while let Ok(cmd) = self.cmd_rx.try_recv() {
441 match cmd {
442 WindowCommand::Show { title, framebuffer } => {
443 self.handle_show(event_loop, title, framebuffer);
444 }
445 WindowCommand::Exit => {
446 self.handle_exit(event_loop);
447 return;
448 }
449 }
450 }
451 }
452
453 fn window_event(
454 &mut self,
455 event_loop: &ActiveEventLoop,
456 window_id: WindowId,
457 event: WindowEvent,
458 ) {
459 match event {
460 WindowEvent::CloseRequested => {
461 self.handle_close(event_loop, window_id);
462 }
463 WindowEvent::RedrawRequested => {
464 self.handle_redraw(window_id);
465 }
466 WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Pressed => {
467 if let PhysicalKey::Code(key) = event.physical_key {
468 let title = self.title_for_window(window_id);
470 if let Some(title) = title {
471 let _ = self.event_tx.send(WindowEvent_::KeyPressed {
472 key,
473 window_title: title,
474 });
475 }
476 }
477 }
478 _ => {}
479 }
480 }
481}
482
483impl App {
484 fn handle_show(
486 &mut self,
487 event_loop: &ActiveEventLoop,
488 title: String,
489 framebuffer: Framebuffer,
490 ) {
491 if let Some(state) = self.windows.get_mut(&title) {
492 let w = framebuffer.width;
494 let h = framebuffer.height;
495 state.framebuffer = framebuffer;
496 state.window.request_redraw();
497 log::debug!("window updated: \"{title}\" ({w}×{h})");
498 } else {
499 let w = framebuffer.width;
501 let h = framebuffer.height;
502
503 let attrs = WindowAttributes::default()
504 .with_title(&title)
505 .with_inner_size(winit::dpi::LogicalSize::new(w, h));
506
507 let window = match event_loop.create_window(attrs) {
508 Ok(win) => Arc::new(win),
509 Err(e) => {
510 log::warn!("failed to create window \"{title}\": {e}");
511 return;
512 }
513 };
514
515 if self.context.is_none() {
517 match softbuffer::Context::new(window.clone()) {
518 Ok(ctx) => self.context = Some(ctx),
519 Err(e) => {
520 log::warn!("failed to create softbuffer context: {e}");
521 return;
522 }
523 }
524 }
525
526 let context = self.context.as_ref().unwrap();
527
528 let surface = match softbuffer::Surface::new(context, window.clone()) {
529 Ok(s) => s,
530 Err(e) => {
531 log::warn!("failed to create softbuffer surface for \"{title}\": {e}");
532 return;
533 }
534 };
535
536 let mut state = WindowState {
537 window,
538 surface,
539 framebuffer,
540 };
541
542 state.blit(&title);
544
545 log::debug!("window created: \"{title}\" ({w}×{h})");
546
547 self.windows.insert(title, state);
548 }
549 }
550
551 fn handle_close(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId) {
553 let title = self.title_for_window(window_id);
554 if let Some(title) = title {
555 log::debug!("window closed: \"{title}\"");
556 self.windows.remove(&title);
557 let _ = self.event_tx.send(WindowEvent_::WindowClosed { title });
558 }
559
560 if self.windows.is_empty() {
561 log::debug!("all windows closed, exiting event loop");
562 let _ = self.event_tx.send(WindowEvent_::AllClosed);
563 event_loop.exit();
564 }
565 }
566
567 fn handle_redraw(&mut self, window_id: WindowId) {
569 let title = self.title_for_window(window_id);
570 if let Some(title) = title {
571 if let Some(state) = self.windows.get_mut(&title) {
572 state.blit(&title);
573 }
574 }
575 }
576
577 fn handle_exit(&mut self, event_loop: &ActiveEventLoop) {
579 log::debug!("exit command received, closing all windows");
580 self.windows.clear();
581 let _ = self.event_tx.send(WindowEvent_::AllClosed);
582 event_loop.exit();
583 }
584
585 fn title_for_window(&self, window_id: WindowId) -> Option<String> {
589 for (title, state) in &self.windows {
590 if state.window.id() == window_id {
591 return Some(title.clone());
592 }
593 }
594 None
595 }
596}
597
598struct WindowState {
604 window: Arc<Window>,
605 surface: softbuffer::Surface<Arc<Window>, Arc<Window>>,
606 framebuffer: Framebuffer,
607}
608
609impl WindowState {
610 fn blit(&mut self, title: &str) {
615 let size = self.window.inner_size();
616 let win_w = size.width;
617 let win_h = size.height;
618
619 if win_w == 0 || win_h == 0 {
621 return;
622 }
623
624 let nz_w = NonZeroU32::new(win_w).unwrap();
626 let nz_h = NonZeroU32::new(win_h).unwrap();
627
628 if let Err(e) = self.surface.resize(nz_w, nz_h) {
629 log::warn!("failed to resize surface for \"{title}\": {e}");
630 return;
631 }
632
633 log::trace!(
634 "surface resized: \"{}\" {}×{} → {}×{}",
635 title,
636 self.framebuffer.width,
637 self.framebuffer.height,
638 win_w,
639 win_h
640 );
641
642 let mut buffer = match self.surface.buffer_mut() {
643 Ok(buf) => buf,
644 Err(e) => {
645 log::warn!("failed to get buffer for \"{title}\": {e}");
646 return;
647 }
648 };
649
650 if win_w == self.framebuffer.width && win_h == self.framebuffer.height {
651 buffer[..self.framebuffer.data.len()].copy_from_slice(&self.framebuffer.data);
653 } else {
654 scale_blit(&self.framebuffer, &mut buffer, win_w, win_h);
656 }
657
658 if let Err(e) = buffer.present() {
659 log::warn!("failed to present buffer for \"{title}\": {e}");
660 }
661 }
662}
663
664fn scale_blit(src: &Framebuffer, dst: &mut [u32], dst_w: u32, dst_h: u32) {
674 if src.width == 0 || src.height == 0 {
676 for pixel in dst.iter_mut() {
678 *pixel = 0;
679 }
680 return;
681 }
682
683 for dy in 0..dst_h {
684 let sy = (dy as u64 * src.height as u64 / dst_h as u64) as u32;
685 let dst_row_start = (dy * dst_w) as usize;
686 let src_row_start = (sy * src.width) as usize;
687
688 for dx in 0..dst_w {
689 let sx = (dx as u64 * src.width as u64 / dst_w as u64) as u32;
690 dst[dst_row_start + dx as usize] = src.data[src_row_start + sx as usize];
691 }
692 }
693}
694
695pub fn show<V, S>(title: &str, image: &V, strategy: S)
737where
738 V: ImageView,
739 V::Pixel: Copy,
740 S: DisplayStrategy<V::Pixel>,
741{
742 use std::cell::RefCell;
743
744 thread_local! {
745 static SHOW_EVENT_LOOP: RefCell<EventLoop<UserEvent>> = RefCell::new(
749 EventLoop::<UserEvent>::with_user_event()
750 .build()
751 .expect("failed to create event loop for show()")
752 );
753 }
754
755 let fb = Framebuffer::from_image(image, strategy);
759 let title = title.to_string();
760
761 SHOW_EVENT_LOOP.with(|cell| {
762 let mut event_loop = cell.borrow_mut();
763
764 let proxy = event_loop.create_proxy();
765
766 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
768
769 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
771
772 let ctx = DisplayContext {
773 cmd_tx: cmd_tx.clone(),
774 event_rx,
775 notifier: Notifier::from_proxy(proxy.clone()),
776 };
777
778 let bg_cmd_tx = cmd_tx;
780 let bg_notifier = Notifier::from_proxy(proxy);
781 std::thread::spawn(move || {
782 ctx.show_framebuffer(&title, fb);
783 let _ = ctx.wait_key();
784
785 let _ = bg_cmd_tx.send(WindowCommand::Exit);
787 if !bg_notifier.notify() {
788 log::warn!("event loop proxy send failed (event loop has exited)");
789 }
790 });
791
792 let mut app = App {
794 cmd_rx,
795 event_tx,
796 context: None,
797 windows: HashMap::new(),
798 };
799
800 event_loop
801 .run_app_on_demand(&mut app)
802 .expect("event loop terminated with error");
803 });
804}
805
806#[cfg(test)]
811mod tests {
812 use super::*;
813 use crate::Identity;
814 use crate::strategy::Framebuffer;
815 use fovea::image::Image;
816 use fovea::pixel::Srgba8;
817
818 fn make_test_ctx() -> (
823 DisplayContext,
824 mpsc::Receiver<WindowCommand>,
825 mpsc::Sender<WindowEvent_>,
826 ) {
827 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
828 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
829 let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
830 (ctx, cmd_rx, event_tx)
831 }
832
833 #[test]
836 fn scale_blit_identity() {
837 let src = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
838 let mut dst = vec![0u32; 4];
839 scale_blit(&src, &mut dst, 2, 2);
840 assert_eq!(dst, vec![0xAA, 0xBB, 0xCC, 0xDD]);
841 }
842
843 #[test]
844 fn scale_blit_upscale_2x() {
845 let src = Framebuffer::from_raw(1, 1, vec![0xFF0000]);
847 let mut dst = vec![0u32; 4];
848 scale_blit(&src, &mut dst, 2, 2);
849 assert_eq!(dst, vec![0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000]);
850 }
851
852 #[test]
853 fn scale_blit_downscale() {
854 let src = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
856 let mut dst = vec![0u32; 1];
857 scale_blit(&src, &mut dst, 1, 1);
858 assert_eq!(dst, vec![0xAA]);
859 }
860
861 #[test]
862 fn scale_blit_empty_source() {
863 let src = Framebuffer::from_raw(0, 0, vec![]);
864 let mut dst = vec![0x12345678u32; 4];
865 scale_blit(&src, &mut dst, 2, 2);
866 assert_eq!(dst, vec![0, 0, 0, 0]);
868 }
869
870 #[test]
871 fn scale_blit_non_square_upscale() {
872 let src = Framebuffer::from_raw(2, 1, vec![0xAA, 0xBB]);
874 let mut dst = vec![0u32; 8];
875 scale_blit(&src, &mut dst, 4, 2);
876 assert_eq!(dst, vec![0xAA, 0xAA, 0xBB, 0xBB, 0xAA, 0xAA, 0xBB, 0xBB]);
879 }
880
881 #[test]
882 fn scale_blit_3x2_to_6x4() {
883 let src = Framebuffer::from_raw(3, 2, vec![1, 2, 3, 4, 5, 6]);
887 let mut dst = vec![0u32; 24]; scale_blit(&src, &mut dst, 6, 4);
889
890 let expected = vec![
896 1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 4, 4, 5, 5, 6, 6,
897 ];
898 assert_eq!(dst, expected);
899 }
900
901 #[test]
904 fn notifier_noop_returns_true() {
905 let n = Notifier(Box::new(|| true));
906 assert!(n.notify());
907 }
908
909 #[test]
910 fn notifier_failing_returns_false() {
911 let n = Notifier(Box::new(|| false));
912 assert!(!n.notify());
913 }
914
915 #[test]
918 fn show_framebuffer_sends_command() {
919 let (ctx, cmd_rx, _event_tx) = make_test_ctx();
920 let fb = Framebuffer::from_raw(2, 2, vec![0xAA, 0xBB, 0xCC, 0xDD]);
921 ctx.show_framebuffer("my title", fb);
922
923 match cmd_rx.recv().unwrap() {
924 WindowCommand::Show { title, framebuffer } => {
925 assert_eq!(title, "my title");
926 assert_eq!(framebuffer.width, 2);
927 assert_eq!(framebuffer.height, 2);
928 assert_eq!(framebuffer.data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
929 }
930 WindowCommand::Exit => panic!("expected Show, got Exit"),
931 }
932 }
933
934 #[test]
935 fn show_framebuffer_with_closed_channel_does_not_panic() {
936 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
937 let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
938 let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
939
940 drop(cmd_rx);
942
943 let fb = Framebuffer::from_raw(1, 1, vec![0]);
945 ctx.show_framebuffer("dead", fb);
946 }
947
948 #[test]
949 fn show_framebuffer_notifier_failure_does_not_panic() {
950 let (cmd_tx, _cmd_rx) = mpsc::channel::<WindowCommand>();
951 let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
952 let ctx = DisplayContext {
954 cmd_tx,
955 event_rx,
956 notifier: Notifier(Box::new(|| false)),
957 };
958
959 let fb = Framebuffer::from_raw(1, 1, vec![0]);
960 ctx.show_framebuffer("fail-notify", fb);
961 }
963
964 #[test]
967 fn show_converts_image_and_sends_command() {
968 let (ctx, cmd_rx, _event_tx) = make_test_ctx();
969
970 let img = Image::fill(2, 2, Srgba8::new(255, 0, 0, 255));
971 ctx.show("red", &img, Identity);
972
973 match cmd_rx.recv().unwrap() {
974 WindowCommand::Show { title, framebuffer } => {
975 assert_eq!(title, "red");
976 assert_eq!(framebuffer.width, 2);
977 assert_eq!(framebuffer.height, 2);
978 assert!(framebuffer.data.iter().all(|&p| p == 0x00FF0000));
980 }
981 WindowCommand::Exit => panic!("expected Show, got Exit"),
982 }
983 }
984
985 #[test]
986 fn show_zero_size_image() {
987 let (ctx, cmd_rx, _event_tx) = make_test_ctx();
988
989 let img = Image::<Srgba8>::zero(0, 0);
990 ctx.show("empty", &img, Identity);
991
992 match cmd_rx.recv().unwrap() {
993 WindowCommand::Show { title, framebuffer } => {
994 assert_eq!(title, "empty");
995 assert_eq!(framebuffer.width, 0);
996 assert_eq!(framebuffer.height, 0);
997 assert!(framebuffer.data.is_empty());
998 }
999 WindowCommand::Exit => panic!("expected Show, got Exit"),
1000 }
1001 }
1002
1003 #[test]
1006 fn exit_sends_exit_command() {
1007 let (ctx, cmd_rx, _event_tx) = make_test_ctx();
1008 ctx.exit();
1009
1010 match cmd_rx.recv().unwrap() {
1011 WindowCommand::Exit => {} WindowCommand::Show { .. } => panic!("expected Exit, got Show"),
1013 }
1014 }
1015
1016 #[test]
1017 fn exit_with_closed_channel_does_not_panic() {
1018 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
1019 let (_event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
1020 let ctx = DisplayContext::new_for_test(cmd_tx, event_rx);
1021 drop(cmd_rx);
1022 ctx.exit(); }
1024
1025 #[test]
1028 fn wait_key_returns_key_on_key_pressed() {
1029 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1030
1031 event_tx
1032 .send(WindowEvent_::KeyPressed {
1033 key: KeyCode::Space,
1034 window_title: "test".to_string(),
1035 })
1036 .unwrap();
1037
1038 assert_eq!(ctx.wait_key(), Some(KeyCode::Space));
1039 }
1040
1041 #[test]
1042 fn wait_key_skips_window_closed_waits_for_key() {
1043 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1044
1045 event_tx
1047 .send(WindowEvent_::WindowClosed {
1048 title: "closing".to_string(),
1049 })
1050 .unwrap();
1051 event_tx
1052 .send(WindowEvent_::KeyPressed {
1053 key: KeyCode::Enter,
1054 window_title: "remaining".to_string(),
1055 })
1056 .unwrap();
1057
1058 assert_eq!(ctx.wait_key(), Some(KeyCode::Enter));
1060 }
1061
1062 #[test]
1063 fn wait_key_returns_none_on_all_closed() {
1064 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1065
1066 event_tx.send(WindowEvent_::AllClosed).unwrap();
1067
1068 assert_eq!(ctx.wait_key(), None);
1069 }
1070
1071 #[test]
1072 fn wait_key_returns_none_on_channel_disconnect() {
1073 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1074
1075 drop(event_tx);
1077
1078 assert_eq!(ctx.wait_key(), None);
1079 }
1080
1081 #[test]
1082 fn wait_key_skips_multiple_window_closed() {
1083 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1084
1085 event_tx
1086 .send(WindowEvent_::WindowClosed {
1087 title: "a".to_string(),
1088 })
1089 .unwrap();
1090 event_tx
1091 .send(WindowEvent_::WindowClosed {
1092 title: "b".to_string(),
1093 })
1094 .unwrap();
1095 event_tx
1096 .send(WindowEvent_::KeyPressed {
1097 key: KeyCode::KeyA,
1098 window_title: "c".to_string(),
1099 })
1100 .unwrap();
1101
1102 assert_eq!(ctx.wait_key(), Some(KeyCode::KeyA));
1103 }
1104
1105 #[test]
1108 fn wait_key_timeout_returns_key_before_timeout() {
1109 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1110
1111 event_tx
1112 .send(WindowEvent_::KeyPressed {
1113 key: KeyCode::Escape,
1114 window_title: "w".to_string(),
1115 })
1116 .unwrap();
1117
1118 let result = ctx.wait_key_timeout(Duration::from_secs(5));
1119 assert_eq!(result, Some(KeyCode::Escape));
1120 }
1121
1122 #[test]
1123 fn wait_key_timeout_returns_none_on_timeout() {
1124 let (ctx, _cmd_rx, _event_tx) = make_test_ctx();
1125
1126 let result = ctx.wait_key_timeout(Duration::from_millis(10));
1128 assert_eq!(result, None);
1129 }
1130
1131 #[test]
1132 fn wait_key_timeout_returns_none_on_all_closed() {
1133 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1134
1135 event_tx.send(WindowEvent_::AllClosed).unwrap();
1136
1137 let result = ctx.wait_key_timeout(Duration::from_secs(5));
1138 assert_eq!(result, None);
1139 }
1140
1141 #[test]
1142 fn wait_key_timeout_returns_none_on_disconnect() {
1143 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1144 drop(event_tx);
1145
1146 let result = ctx.wait_key_timeout(Duration::from_secs(5));
1147 assert_eq!(result, None);
1148 }
1149
1150 #[test]
1151 fn wait_key_timeout_skips_window_closed() {
1152 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1153
1154 event_tx
1155 .send(WindowEvent_::WindowClosed {
1156 title: "gone".to_string(),
1157 })
1158 .unwrap();
1159 event_tx
1160 .send(WindowEvent_::KeyPressed {
1161 key: KeyCode::KeyZ,
1162 window_title: "still here".to_string(),
1163 })
1164 .unwrap();
1165
1166 let result = ctx.wait_key_timeout(Duration::from_secs(5));
1167 assert_eq!(result, Some(KeyCode::KeyZ));
1168 }
1169
1170 #[test]
1171 fn wait_key_timeout_zero_returns_none_immediately() {
1172 let (ctx, _cmd_rx, event_tx) = make_test_ctx();
1173
1174 event_tx
1178 .send(WindowEvent_::KeyPressed {
1179 key: KeyCode::KeyX,
1180 window_title: "w".to_string(),
1181 })
1182 .unwrap();
1183
1184 let result = ctx.wait_key_timeout(Duration::ZERO);
1186 assert!(result.is_none() || result == Some(KeyCode::KeyX));
1189 }
1190
1191 #[test]
1194 fn show_then_wait_key_workflow() {
1195 let (ctx, cmd_rx, event_tx) = make_test_ctx();
1196
1197 let img = Image::fill(4, 4, Srgba8::new(0, 255, 0, 255));
1199 ctx.show("green", &img, Identity);
1200
1201 match cmd_rx.recv().unwrap() {
1203 WindowCommand::Show { title, framebuffer } => {
1204 assert_eq!(title, "green");
1205 assert_eq!(framebuffer.width, 4);
1206 assert_eq!(framebuffer.height, 4);
1207 assert_eq!(framebuffer.data.len(), 16);
1208 assert!(framebuffer.data.iter().all(|&p| p == 0x0000FF00));
1210 }
1211 WindowCommand::Exit => panic!("expected Show"),
1212 }
1213
1214 event_tx
1216 .send(WindowEvent_::KeyPressed {
1217 key: KeyCode::KeyQ,
1218 window_title: "green".to_string(),
1219 })
1220 .unwrap();
1221
1222 assert_eq!(ctx.wait_key(), Some(KeyCode::KeyQ));
1223 }
1224
1225 #[test]
1226 fn multiple_shows_then_exit_workflow() {
1227 let (ctx, cmd_rx, _event_tx) = make_test_ctx();
1228
1229 let img1 = Image::fill(1, 1, Srgba8::new(255, 0, 0, 255));
1230 let img2 = Image::fill(1, 1, Srgba8::new(0, 0, 255, 255));
1231 ctx.show("red", &img1, Identity);
1232 ctx.show("blue", &img2, Identity);
1233 ctx.exit();
1234
1235 let mut titles = Vec::new();
1237 let mut got_exit = false;
1238 for _ in 0..3 {
1239 match cmd_rx.recv().unwrap() {
1240 WindowCommand::Show { title, .. } => titles.push(title),
1241 WindowCommand::Exit => got_exit = true,
1242 }
1243 }
1244 assert_eq!(titles, vec!["red", "blue"]);
1245 assert!(got_exit);
1246 }
1247
1248 #[test]
1251 fn channel_show_sends_correct_command() {
1252 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
1253
1254 cmd_tx
1255 .send(WindowCommand::Show {
1256 title: "test".to_string(),
1257 framebuffer: Framebuffer::from_raw(2, 2, vec![0, 0, 0, 0]),
1258 })
1259 .unwrap();
1260
1261 match cmd_rx.recv().unwrap() {
1262 WindowCommand::Show { title, framebuffer } => {
1263 assert_eq!(title, "test");
1264 assert_eq!(framebuffer.width, 2);
1265 assert_eq!(framebuffer.height, 2);
1266 assert_eq!(framebuffer.data.len(), 4);
1267 }
1268 WindowCommand::Exit => panic!("expected Show, got Exit"),
1269 }
1270 }
1271
1272 #[test]
1273 fn channel_exit_command() {
1274 let (cmd_tx, cmd_rx) = mpsc::channel::<WindowCommand>();
1275
1276 cmd_tx.send(WindowCommand::Exit).unwrap();
1277 match cmd_rx.recv().unwrap() {
1278 WindowCommand::Exit => {} WindowCommand::Show { .. } => panic!("expected Exit, got Show"),
1280 }
1281 }
1282
1283 #[test]
1284 fn channel_key_pressed_event() {
1285 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
1286
1287 event_tx
1288 .send(WindowEvent_::KeyPressed {
1289 key: KeyCode::Escape,
1290 window_title: "test window".to_string(),
1291 })
1292 .unwrap();
1293
1294 match event_rx.recv().unwrap() {
1295 WindowEvent_::KeyPressed { key, window_title } => {
1296 assert_eq!(key, KeyCode::Escape);
1297 assert_eq!(window_title, "test window");
1298 }
1299 other => panic!("expected KeyPressed, got {:?}", other),
1300 }
1301 }
1302
1303 #[test]
1304 fn channel_window_closed_event() {
1305 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
1306
1307 event_tx
1308 .send(WindowEvent_::WindowClosed {
1309 title: "my window".to_string(),
1310 })
1311 .unwrap();
1312
1313 match event_rx.recv().unwrap() {
1314 WindowEvent_::WindowClosed { title } => {
1315 assert_eq!(title, "my window");
1316 }
1317 other => panic!("expected WindowClosed, got {:?}", other),
1318 }
1319 }
1320
1321 #[test]
1322 fn channel_all_closed_event() {
1323 let (event_tx, event_rx) = mpsc::channel::<WindowEvent_>();
1324
1325 event_tx.send(WindowEvent_::AllClosed).unwrap();
1326
1327 match event_rx.recv().unwrap() {
1328 WindowEvent_::AllClosed => {} other => panic!("expected AllClosed, got {:?}", other),
1330 }
1331 }
1332
1333 #[test]
1336 fn framebuffer_is_send() {
1337 fn assert_send<T: Send>() {}
1339 assert_send::<Framebuffer>();
1340 }
1341
1342 #[test]
1343 fn display_context_is_send() {
1344 fn assert_send<T: Send>() {}
1345 assert_send::<DisplayContext>();
1346 }
1347}