Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

## [Unreleased] - ReleaseDate

### Changed

- [#48] Allowed for multiple window to co-exist in the same application.

## [0.6.0] - 2023-11-26

### Changed
Expand Down
125 changes: 125 additions & 0 deletions examples/input-handling-multiple.rs
Original file line number Diff line number Diff line change
@@ -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<Rgb888>,
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<Rgb888>,
position: &mut Point,
) -> Result<LoopResult, core::convert::Infallible> {
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(())
}
188 changes: 112 additions & 76 deletions src/window/sdl_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ use sdl2::{

use crate::{OutputImage, OutputSettings, SimulatorDisplay};

struct SdlContext {
video_subsystem: sdl2::VideoSubsystem,
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<Vec<Event>> = {
std::cell::RefCell::new(Vec::with_capacity(MAXIMUM_GLOBAL_EVENT_PUMP_SIZE))
};

static SDL_CONTEXT: std::cell::RefCell<SdlContext> = {
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 {
Expand Down Expand Up @@ -67,7 +93,6 @@ pub enum SimulatorEvent {

pub struct SdlWindow {
canvas: Canvas<sdl2::video::Window>,
event_pump: EventPump,
window_texture: SdlWindowTexture,
size: Size,
}
Expand All @@ -81,19 +106,18 @@ impl SdlWindow {
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 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(),
Expand All @@ -107,7 +131,6 @@ impl SdlWindow {

Self {
canvas,
event_pump,
window_texture,
size,
}
Expand All @@ -132,76 +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<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,
let window_id = self.canvas.window().id();

// 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<Event>) = 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,
})
}
}

Expand Down