From 1f2ecbcf4b0fac5eada79f890369a532d19b3b53 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 20 Jul 2023 12:35:42 -0700 Subject: [PATCH 1/5] Move SDL objects to TLS, allowing multiple windows to co-exist. Fixes #47. Co-Authored-By: Adam Greig --- src/window/sdl_window.rs | 44 ++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/window/sdl_window.rs b/src/window/sdl_window.rs index ed7e1c4..77e52dd 100644 --- a/src/window/sdl_window.rs +++ b/src/window/sdl_window.rs @@ -14,6 +14,24 @@ use sdl2::{ use crate::{OutputImage, OutputSettings, SimulatorDisplay}; +struct SdlContext { + video_subsystem: sdl2::VideoSubsystem, + event_pump: EventPump, +} + +thread_local! { + static SDL_CONTEXT: std::cell::RefCell = { + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let event_pump = sdl_context.event_pump().unwrap(); + + std::cell::RefCell::new(SdlContext { + video_subsystem, + event_pump, + }) + }; +} + /// A derivation of [`sdl2::event::Event`] mapped to embedded-graphics coordinates #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum SimulatorEvent { @@ -67,7 +85,6 @@ pub enum SimulatorEvent { pub struct SdlWindow { canvas: Canvas, - event_pump: EventPump, window_texture: SdlWindowTexture, size: Size, } @@ -81,19 +98,18 @@ impl SdlWindow { where C: PixelColor + Into, { - 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 window = SDL_CONTEXT.with(|ctx| { + ctx.borrow_mut() + .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(); let window_texture = SdlWindowTextureBuilder { texture_creator: canvas.texture_creator(), @@ -107,7 +123,6 @@ impl SdlWindow { Self { canvas, - event_pump, window_texture, size, } @@ -138,8 +153,11 @@ impl SdlWindow { output_settings: &OutputSettings, ) -> impl Iterator + '_ { let output_settings = output_settings.clone(); - self.event_pump - .poll_iter() + let events: Vec = + SDL_CONTEXT.with(|ctx| ctx.borrow_mut().event_pump.poll_iter().collect()); + events + .into_iter() + .filter(|e| e.get_window_id() == Some(self.canvas.window().id())) .filter_map(move |event| match event { Event::Quit { .. } | Event::KeyDown { From c920b5728dad116d27a1e251f4b9e57245f04f22 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 20 Jul 2023 12:37:08 -0700 Subject: [PATCH 2/5] Adding CHANGELOG entry. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8158c99..3771d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ## [Unreleased] - ReleaseDate +### Changed + +- [#48] Allowed for multiple window to co-exist in the same application. + ## [0.5.0] - 2023-05-14 ### Changed From e33d171d3f38a242e5cb6a85f6dc36b8e689f403 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 20 Jul 2023 12:51:05 -0700 Subject: [PATCH 3/5] Fixing some event issue --- src/window/sdl_window.rs | 45 +++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/window/sdl_window.rs b/src/window/sdl_window.rs index 77e52dd..c6060a5 100644 --- a/src/window/sdl_window.rs +++ b/src/window/sdl_window.rs @@ -155,9 +155,10 @@ impl SdlWindow { let output_settings = output_settings.clone(); let events: Vec = SDL_CONTEXT.with(|ctx| ctx.borrow_mut().event_pump.poll_iter().collect()); + let window_id = self.canvas.window().id(); events .into_iter() - .filter(|e| e.get_window_id() == Some(self.canvas.window().id())) + .filter(move |e| e.get_window_id() == Some(window_id) || e.get_window_id().is_none()) .filter_map(move |event| match event { Event::Quit { .. } | Event::KeyDown { @@ -197,24 +198,52 @@ impl SdlWindow { } } Event::MouseButtonUp { - x, y, mouse_btn, .. + x, + y, + mouse_btn, + window_id, + .. } => { + if window_id != window_id { + return None; + } let point = output_settings.output_to_display(Point::new(x, y)); Some(SimulatorEvent::MouseButtonUp { point, mouse_btn }) } Event::MouseButtonDown { - x, y, mouse_btn, .. + x, + y, + mouse_btn, + window_id, + .. } => { + if window_id != window_id { + return None; + } 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), + x, + y, direction, - }), - Event::MouseMotion { x, y, .. } => { + window_id, + .. + } => { + if window_id != window_id { + return None; + } + Some(SimulatorEvent::MouseWheel { + scroll_delta: Point::new(x, y), + direction, + }) + } + Event::MouseMotion { + x, y, window_id, .. + } => { + if window_id != window_id { + return None; + } let point = output_settings.output_to_display(Point::new(x, y)); Some(SimulatorEvent::MouseMove { point }) } From aa4b9ea494fcb0aa3414281654e084b3105442b6 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 5 Jun 2024 10:01:07 -0700 Subject: [PATCH 4/5] Add example, fix missing events for other windows --- examples/input-handling-multiple.rs | 125 +++++++++++++++++++ src/window/sdl_window.rs | 181 +++++++++++++--------------- 2 files changed, 210 insertions(+), 96 deletions(-) create mode 100644 examples/input-handling-multiple.rs diff --git a/examples/input-handling-multiple.rs b/examples/input-handling-multiple.rs new file mode 100644 index 0000000..59768d3 --- /dev/null +++ b/examples/input-handling-multiple.rs @@ -0,0 +1,125 @@ +//! # Example: Input Handling +//! +//! This example allows you to move a red circle to the location of a click on the simulator +//! screen, or move the circle using the arrow keys. Although input handling is not a part of the +//! embedded-graphics API, the simulator can be used to emulate input controls in order to +//! represent more complex UI systems such as touch screens. + +extern crate embedded_graphics; +extern crate embedded_graphics_simulator; + +use embedded_graphics::{ + pixelcolor::Rgb888, + prelude::*, + primitives::{Circle, PrimitiveStyle}, +}; +use embedded_graphics_simulator::{ + sdl2::Keycode, OutputSettings, SimulatorDisplay, SimulatorEvent, Window, +}; + +const BACKGROUND_COLOR: Rgb888 = Rgb888::BLACK; +const FOREGROUND_COLOR: Rgb888 = Rgb888::RED; +const KEYBOARD_DELTA: i32 = 20; + +fn move_circle( + display: &mut SimulatorDisplay, + old_center: Point, + new_center: Point, +) -> Result<(), core::convert::Infallible> { + // Clear old circle + Circle::with_center(old_center, 200) + .into_styled(PrimitiveStyle::with_fill(BACKGROUND_COLOR)) + .draw(display)?; + + // Draw circle at new location + Circle::with_center(new_center, 200) + .into_styled(PrimitiveStyle::with_fill(FOREGROUND_COLOR)) + .draw(display)?; + + Ok(()) +} + +enum LoopResult { + Continue, + AddNewWindow, + Quit, +} + +fn handle_loop( + window: &mut Window, + display: &mut SimulatorDisplay, + position: &mut Point, +) -> Result { + window.update(display); + + let events = window.events(); + for event in events { + match event { + SimulatorEvent::Quit => return Ok(LoopResult::Quit), + SimulatorEvent::KeyDown { keycode, .. } => { + let delta = match keycode { + Keycode::Left => Point::new(-KEYBOARD_DELTA, 0), + Keycode::Right => Point::new(KEYBOARD_DELTA, 0), + Keycode::Up => Point::new(0, -KEYBOARD_DELTA), + Keycode::Down => Point::new(0, KEYBOARD_DELTA), + Keycode::N => return Ok(LoopResult::AddNewWindow), + _ => Point::zero(), + }; + let new_position = *position + delta; + move_circle(display, *position, new_position)?; + *position = new_position; + } + SimulatorEvent::MouseButtonUp { point, .. } => { + move_circle(display, *position, point)?; + *position = point; + } + _ => {} + } + } + Ok(LoopResult::Continue) +} + +fn main() -> Result<(), core::convert::Infallible> { + let mut windows = vec![( + SimulatorDisplay::new(Size::new(800, 480)), + Window::new( + "Click to move circle (press N for new window)", + &OutputSettings::default(), + ), + Point::new(200, 200), + )]; + + for (display, _, position) in windows.iter_mut() { + Circle::with_center(*position, 200) + .into_styled(PrimitiveStyle::with_fill(FOREGROUND_COLOR)) + .draw(display)?; + } + + 'running: loop { + for (display, window, position) in windows.iter_mut() { + match handle_loop(window, display, position)? { + LoopResult::Continue => {} + LoopResult::AddNewWindow => { + let mut display = SimulatorDisplay::new(Size::new(800, 480)); + let window = Window::new( + "Click to move circle (press N for new window)", + &OutputSettings::default(), + ); + let position = Point::new(200, 200); + + Circle::with_center(position, 200) + .into_styled(PrimitiveStyle::with_fill(FOREGROUND_COLOR)) + .draw(&mut display)?; + + windows.push((display, window, position)); + break; + } + LoopResult::Quit => { + break 'running; + } + } + } + } + + Ok(()) +} diff --git a/src/window/sdl_window.rs b/src/window/sdl_window.rs index c6060a5..b6f7e9a 100644 --- a/src/window/sdl_window.rs +++ b/src/window/sdl_window.rs @@ -19,7 +19,15 @@ struct SdlContext { event_pump: EventPump, } +const MAXIMUM_GLOBAL_EVENT_PUMP_SIZE: usize = 32; + thread_local! { + /// A global event pump that captures all events that are unhandled. + /// This will start dropping events if it's full, to avoid memory leaks. + static GLOBAL_EVENT_PUMP: std::cell::RefCell> = { + std::cell::RefCell::new(Vec::with_capacity(MAXIMUM_GLOBAL_EVENT_PUMP_SIZE)) + }; + static SDL_CONTEXT: std::cell::RefCell = { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); @@ -147,108 +155,89 @@ impl SdlWindow { } /// Handle events - /// Return an iterator of all captured SimulatorEvent + /// Return an iterator of all captured SimulatorEvent for this window. + /// If an event is not targeted to this window, keep it in the global + /// event pump. pub fn events( &mut self, output_settings: &OutputSettings, ) -> impl Iterator + '_ { - let output_settings = output_settings.clone(); - let events: Vec = - SDL_CONTEXT.with(|ctx| ctx.borrow_mut().event_pump.poll_iter().collect()); let window_id = self.canvas.window().id(); - events - .into_iter() - .filter(move |e| e.get_window_id() == Some(window_id) || e.get_window_id().is_none()) - .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, - window_id, - .. - } => { - if window_id != window_id { - return None; - } - let point = output_settings.output_to_display(Point::new(x, y)); - Some(SimulatorEvent::MouseButtonUp { point, mouse_btn }) - } - Event::MouseButtonDown { - x, - y, - mouse_btn, - window_id, - .. - } => { - if window_id != window_id { - return None; - } - let point = output_settings.output_to_display(Point::new(x, y)); - Some(SimulatorEvent::MouseButtonDown { point, mouse_btn }) - } - Event::MouseWheel { - x, - y, - direction, - window_id, - .. - } => { - if window_id != window_id { - return None; - } - Some(SimulatorEvent::MouseWheel { - scroll_delta: Point::new(x, y), - direction, - }) - } - Event::MouseMotion { - x, y, window_id, .. - } => { - if window_id != window_id { - return None; - } - let point = output_settings.output_to_display(Point::new(x, y)); - Some(SimulatorEvent::MouseMove { point }) - } - _ => None, + + // Pump the global pump, adding new events to it, and filters only the + // events that are for this window (or global). + let events = SDL_CONTEXT.with(|ctx| { + let mut bindings = ctx.borrow_mut(); + let new_events = bindings.event_pump.poll_iter(); + + GLOBAL_EVENT_PUMP.with(|pump| { + let (events, remaining): (_, Vec) = pump + .borrow_mut() + .drain(..) + .into_iter() + .chain(new_events.into_iter()) + .partition(|e| { + e.get_window_id() == Some(window_id) || e.get_window_id().is_none() + }); + + // Limit the size of the global event pump to avoid memory leaks. + *pump.borrow_mut() = + remaining[..remaining.len().min(MAXIMUM_GLOBAL_EVENT_PUMP_SIZE)].to_vec(); + events }) + }); + + let output_settings = output_settings.clone(); + events.into_iter().filter_map(move |event| match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => Some(SimulatorEvent::Quit), + Event::KeyDown { + keycode, + keymod, + repeat, + .. + } => keycode.map(|keycode| SimulatorEvent::KeyDown { + keycode, + keymod, + repeat, + }), + Event::KeyUp { + keycode, + keymod, + repeat, + .. + } => keycode.map(|keycode| SimulatorEvent::KeyUp { + keycode, + keymod, + repeat, + }), + 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, + }) } } From be21fa5fb9c206506eaf3fe9cade222120e8db12 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 6 Jun 2024 12:57:38 -0700 Subject: [PATCH 5/5] Renamed example, add handling of window events and close --- ...t-handling-multiple.rs => multiple-windows.rs} | 15 ++++++++++++++- src/window/sdl_window.rs | 5 +++++ 2 files changed, 19 insertions(+), 1 deletion(-) rename examples/{input-handling-multiple.rs => multiple-windows.rs} (87%) diff --git a/examples/input-handling-multiple.rs b/examples/multiple-windows.rs similarity index 87% rename from examples/input-handling-multiple.rs rename to examples/multiple-windows.rs index 59768d3..d4947aa 100644 --- a/examples/input-handling-multiple.rs +++ b/examples/multiple-windows.rs @@ -16,6 +16,7 @@ use embedded_graphics::{ use embedded_graphics_simulator::{ sdl2::Keycode, OutputSettings, SimulatorDisplay, SimulatorEvent, Window, }; +use sdl2::event::WindowEvent; const BACKGROUND_COLOR: Rgb888 = Rgb888::BLACK; const FOREGROUND_COLOR: Rgb888 = Rgb888::RED; @@ -42,6 +43,7 @@ fn move_circle( enum LoopResult { Continue, AddNewWindow, + RemoveWindow, Quit, } @@ -63,6 +65,7 @@ fn handle_loop( Keycode::Up => Point::new(0, -KEYBOARD_DELTA), Keycode::Down => Point::new(0, KEYBOARD_DELTA), Keycode::N => return Ok(LoopResult::AddNewWindow), + Keycode::Escape => return Ok(LoopResult::RemoveWindow), _ => Point::zero(), }; let new_position = *position + delta; @@ -73,6 +76,7 @@ fn handle_loop( move_circle(display, *position, point)?; *position = point; } + SimulatorEvent::WindowEvent(WindowEvent::Close) => return Ok(LoopResult::RemoveWindow), _ => {} } } @@ -96,7 +100,12 @@ fn main() -> Result<(), core::convert::Infallible> { } 'running: loop { - for (display, window, position) in windows.iter_mut() { + // When no windows are opened, exit the application. + if windows.is_empty() { + break; + } + + for (i, (display, window, position)) in windows.iter_mut().enumerate() { match handle_loop(window, display, position)? { LoopResult::Continue => {} LoopResult::AddNewWindow => { @@ -114,6 +123,10 @@ fn main() -> Result<(), core::convert::Infallible> { windows.push((display, window, position)); break; } + LoopResult::RemoveWindow => { + windows.swap_remove(i); + break; + } LoopResult::Quit => { break 'running; } diff --git a/src/window/sdl_window.rs b/src/window/sdl_window.rs index b6f7e9a..fbc32ba 100644 --- a/src/window/sdl_window.rs +++ b/src/window/sdl_window.rs @@ -89,6 +89,8 @@ pub enum SimulatorEvent { }, /// An exit event Quit, + /// A window event, containing the SDL2 window event + WindowEvent(sdl2::event::WindowEvent), } pub struct SdlWindow { @@ -188,6 +190,8 @@ impl SdlWindow { }); let output_settings = output_settings.clone(); + // TODO: Handling of ESC key should be left to the application. They might + // want to handle it differently. events.into_iter().filter_map(move |event| match event { Event::Quit { .. } | Event::KeyDown { @@ -236,6 +240,7 @@ impl SdlWindow { let point = output_settings.output_to_display(Point::new(x, y)); Some(SimulatorEvent::MouseMove { point }) } + Event::Window { win_event, .. } => Some(SimulatorEvent::WindowEvent(win_event)), _ => None, }) }