Skip to content

Commit 756b40a

Browse files
committed
Add support for multiple displays per window
1 parent c198fae commit 756b40a

7 files changed

Lines changed: 329 additions & 61 deletions

File tree

examples/multiple-displays.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! # Example: Multiple displays
2+
//!
3+
//! TODO: add description
4+
5+
extern crate embedded_graphics;
6+
extern crate embedded_graphics_simulator;
7+
8+
use embedded_graphics::{
9+
mono_font::{ascii::FONT_10X20, MonoTextStyle},
10+
pixelcolor::{BinaryColor, Rgb565},
11+
prelude::*,
12+
primitives::{Circle, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
13+
text::{Alignment, Baseline, Text, TextStyleBuilder},
14+
};
15+
use embedded_graphics_simulator::{
16+
sdl2::MouseButton, BinaryColorTheme, MultiWindow, OutputSettings, OutputSettingsBuilder,
17+
SimulatorDisplay, SimulatorEvent,
18+
};
19+
20+
fn main() -> Result<(), core::convert::Infallible> {
21+
let character_style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
22+
let centered = TextStyleBuilder::new()
23+
.alignment(Alignment::Center)
24+
.baseline(Baseline::Middle)
25+
.build();
26+
27+
let mut oled_displays = Vec::new();
28+
for i in 0..3 {
29+
let mut oled: SimulatorDisplay<BinaryColor> = SimulatorDisplay::new(Size::new(128, 64));
30+
31+
Text::with_text_style(
32+
&format!("Display {i}"),
33+
oled.bounding_box().center(),
34+
character_style,
35+
centered,
36+
)
37+
.draw(&mut oled)
38+
.unwrap();
39+
40+
oled_displays.push(oled);
41+
}
42+
43+
let mut tft: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(320, 240));
44+
tft.clear(Rgb565::new(5, 10, 5)).unwrap();
45+
46+
Text::with_text_style(
47+
&format!("Draw here"),
48+
tft.bounding_box().center(),
49+
MonoTextStyle::new(&FONT_10X20, Rgb565::CSS_LIGHT_SLATE_GRAY),
50+
centered,
51+
)
52+
.draw(&mut tft)
53+
.unwrap();
54+
55+
let mut window = MultiWindow::new("Multiple displays example", Size::new(1200, 500));
56+
57+
let oled_settings = OutputSettingsBuilder::new()
58+
.theme(BinaryColorTheme::OledBlue)
59+
.scale(2)
60+
.pixel_spacing(1)
61+
.build();
62+
for (i, oled) in oled_displays.iter().enumerate() {
63+
window.add_display(
64+
&oled,
65+
Point::new((128 * 3 + 10) * (i as i32), 0),
66+
&oled_settings,
67+
);
68+
}
69+
70+
let tft_settings = OutputSettings::default();
71+
window.add_display(&tft, Point::new(50, 230), &tft_settings);
72+
73+
let border_style = PrimitiveStyleBuilder::new()
74+
.stroke_width(5)
75+
.stroke_alignment(StrokeAlignment::Inside)
76+
.build();
77+
78+
let mut mouse_down = false;
79+
80+
'running: loop {
81+
for oled in &oled_displays {
82+
window.update_display(oled);
83+
}
84+
window.update_display(&tft);
85+
window.update();
86+
87+
for event in window.events() {
88+
match event {
89+
SimulatorEvent::MouseMove { point } => {
90+
for oled in &mut oled_displays {
91+
let is_inside = window.translate_mouse_position(oled, point).is_some();
92+
93+
let style = PrimitiveStyleBuilder::from(&border_style)
94+
.stroke_color(BinaryColor::from(is_inside))
95+
.build();
96+
97+
oled.bounding_box().into_styled(style).draw(oled).unwrap();
98+
}
99+
100+
if mouse_down {
101+
if let Some(point) = window.translate_mouse_position(&tft, point) {
102+
Circle::with_center(point, 10)
103+
.into_styled(PrimitiveStyle::with_fill(Rgb565::CSS_DODGER_BLUE))
104+
.draw(&mut tft)
105+
.unwrap();
106+
}
107+
}
108+
}
109+
SimulatorEvent::MouseButtonDown {
110+
mouse_btn: MouseButton::Left,
111+
..
112+
} => {
113+
mouse_down = true;
114+
}
115+
SimulatorEvent::MouseButtonUp {
116+
mouse_btn: MouseButton::Left,
117+
..
118+
} => {
119+
mouse_down = false;
120+
}
121+
SimulatorEvent::Quit => break 'running,
122+
_ => {}
123+
}
124+
}
125+
}
126+
127+
Ok(())
128+
}

src/display.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
use std::{convert::TryFrom, fs::File, io::BufReader, path::Path};
1+
use std::{
2+
convert::TryFrom,
3+
fs::File,
4+
io::BufReader,
5+
path::Path,
6+
sync::atomic::{AtomicUsize, Ordering},
7+
};
28

39
use embedded_graphics::{
410
pixelcolor::{raw::ToBytes, BinaryColor, Gray8, Rgb888},
@@ -7,14 +13,23 @@ use embedded_graphics::{
713

814
use crate::{output_image::OutputImage, output_settings::OutputSettings};
915

16+
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
17+
1018
/// Simulator display.
11-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19+
#[derive(Debug, Clone, Eq, PartialOrd, Ord, Hash)]
1220
pub struct SimulatorDisplay<C> {
1321
size: Size,
1422
pub(crate) pixels: Box<[C]>,
23+
pub(crate) id: usize,
1524
}
1625

1726
impl<C: PixelColor> SimulatorDisplay<C> {
27+
fn new_common(size: Size, pixels: Box<[C]>) -> Self {
28+
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
29+
30+
Self { size, pixels, id }
31+
}
32+
1833
/// Creates a new display filled with a color.
1934
///
2035
/// This constructor can be used if `C` doesn't implement `From<BinaryColor>` or another
@@ -23,7 +38,7 @@ impl<C: PixelColor> SimulatorDisplay<C> {
2338
let pixel_count = size.width as usize * size.height as usize;
2439
let pixels = vec![default_color; pixel_count].into_boxed_slice();
2540

26-
SimulatorDisplay { size, pixels }
41+
SimulatorDisplay::new_common(size, pixels)
2742
}
2843

2944
/// Returns the color of the pixel at a point.
@@ -75,10 +90,7 @@ impl<C: PixelColor> SimulatorDisplay<C> {
7590
.into_boxed_slice();
7691

7792
if pixels.iter().any(|p| *p == BinaryColor::On) {
78-
Some(SimulatorDisplay {
79-
pixels,
80-
size: self.size,
81-
})
93+
Some(SimulatorDisplay::new_common(self.size, pixels))
8294
} else {
8395
None
8496
}
@@ -122,8 +134,9 @@ where
122134
/// // example: output_image.save_png("out.png")?;
123135
/// ```
124136
pub fn to_rgb_output_image(&self, output_settings: &OutputSettings) -> OutputImage<Rgb888> {
125-
let mut output = OutputImage::new(self, output_settings);
126-
output.update(self);
137+
let size = output_settings.framebuffer_size(self);
138+
let mut output = OutputImage::new(size);
139+
output.update(self, Point::zero(), output_settings);
127140

128141
output
129142
}
@@ -152,8 +165,9 @@ where
152165
&self,
153166
output_settings: &OutputSettings,
154167
) -> OutputImage<Gray8> {
155-
let mut output = OutputImage::new(self, output_settings);
156-
output.update(self);
168+
let size = output_settings.framebuffer_size(self);
169+
let mut output = OutputImage::new(size);
170+
output.update(self, Point::zero(), output_settings);
157171

158172
output
159173
}
@@ -226,10 +240,10 @@ where
226240
.map(|p| Rgb888::new(p[0], p[1], p[2]).into())
227241
.collect();
228242

229-
Ok(Self {
230-
size: Size::new(image.width(), image.height()),
243+
Ok(Self::new_common(
244+
Size::new(image.width(), image.height()),
231245
pixels,
232-
})
246+
))
233247
}
234248
}
235249

@@ -257,6 +271,12 @@ impl<C> OriginDimensions for SimulatorDisplay<C> {
257271
}
258272
}
259273

274+
impl<C: PartialEq> PartialEq for SimulatorDisplay<C> {
275+
fn eq(&self, other: &Self) -> bool {
276+
self.size == other.size && self.pixels == other.pixels
277+
}
278+
}
279+
260280
#[cfg(test)]
261281
mod tests {
262282
use super::*;
@@ -321,6 +341,7 @@ mod tests {
321341
.map(|c| BinaryColor::from(*c != 0))
322342
.collect::<Vec<_>>()
323343
.into_boxed_slice(),
344+
id: 0,
324345
};
325346

326347
let expected = [
@@ -345,6 +366,7 @@ mod tests {
345366
.map(|c| Gray2::new(*c))
346367
.collect::<Vec<_>>()
347368
.into_boxed_slice(),
369+
id: 0,
348370
};
349371

350372
let expected = [
@@ -370,6 +392,7 @@ mod tests {
370392
.map(|c| Gray4::new(*c))
371393
.collect::<Vec<_>>()
372394
.into_boxed_slice(),
395+
id: 0,
373396
};
374397

375398
let expected = [
@@ -398,6 +421,7 @@ mod tests {
398421
.map(Gray8::new)
399422
.collect::<Vec<_>>()
400423
.into_boxed_slice(),
424+
id: 0,
401425
};
402426

403427
assert_eq!(&display.to_be_bytes(), &expected);
@@ -412,6 +436,7 @@ mod tests {
412436
let display = SimulatorDisplay {
413437
size: Size::new(2, 1),
414438
pixels: expected.clone().into_boxed_slice(),
439+
id: 0,
415440
};
416441

417442
assert_eq!(&display.to_be_bytes(), &[0x80, 0x00, 0x00, 0x01]);
@@ -425,6 +450,7 @@ mod tests {
425450
let display = SimulatorDisplay {
426451
size: Size::new(2, 1),
427452
pixels: expected.clone().into_boxed_slice(),
453+
id: 0,
428454
};
429455

430456
assert_eq!(

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ mod output_settings;
158158
mod theme;
159159
mod window;
160160

161-
#[cfg(feature = "with-sdl")]
162-
pub use window::SimulatorEvent;
163-
164161
/// Re-exported types from sdl2 crate.
165162
///
166163
/// The types in this module are used in the [`SimulatorEvent`] enum and are re-exported from the
@@ -180,3 +177,6 @@ pub use crate::{
180177
theme::BinaryColorTheme,
181178
window::Window,
182179
};
180+
181+
#[cfg(feature = "with-sdl")]
182+
pub use window::{MultiWindow, SimulatorEvent};

0 commit comments

Comments
 (0)