From 12f229099a2377e5e1b61fdcfc0725fc9fc1bdfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 3 Mar 2023 19:36:04 +0100 Subject: [PATCH 1/8] Add a simple canvas widget --- backend-embedded-graphics/Cargo.toml | 2 + backend-embedded-graphics/src/lib.rs | 2 + .../src/widgets/canvas.rs | 214 ++++++++++++++++++ backend-embedded-graphics/src/widgets/mod.rs | 1 + examples/canvas.rs | 162 +++++++++++++ 5 files changed, 381 insertions(+) create mode 100644 backend-embedded-graphics/src/widgets/canvas.rs create mode 100644 examples/canvas.rs diff --git a/backend-embedded-graphics/Cargo.toml b/backend-embedded-graphics/Cargo.toml index 46d61da..f13da61 100644 --- a/backend-embedded-graphics/Cargo.toml +++ b/backend-embedded-graphics/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Dániel Buga "] edition = "2018" [dependencies] +embedded-canvas = "0.2.0" embedded-graphics = "0.7.0" embedded-text = { version = "0.5.0", features = ["plugin"] } embedded-gui = { path = ".." } @@ -14,4 +15,5 @@ object-chain = "0.1" [features] ansi = ["embedded-text/ansi"] +std = ["embedded-canvas/alloc"] default = ["ansi"] diff --git a/backend-embedded-graphics/src/lib.rs b/backend-embedded-graphics/src/lib.rs index 1ed0633..6a01f0b 100644 --- a/backend-embedded-graphics/src/lib.rs +++ b/backend-embedded-graphics/src/lib.rs @@ -49,6 +49,8 @@ use embedded_gui::{ Canvas, }; +pub use embedded_canvas; + trait ToPoint { fn to_point(self) -> Point; } diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs new file mode 100644 index 0000000..a6fdd98 --- /dev/null +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -0,0 +1,214 @@ +use embedded_canvas::{ CCanvasAt}; +use embedded_graphics::{ + prelude::{DrawTarget, PixelColor, Point, Dimensions, Size}, + Drawable, +}; +use embedded_gui::{ + geometry::{measurement::MeasureSpec, BoundingBox, MeasuredSize, Position}, + prelude::WrapperBindable, + state::WidgetState, + widgets::Widget, + WidgetRenderer, +}; + +use crate::{themes::Theme, EgCanvas}; + +pub trait CanvasProperties { + type Color; + type Canvas: Drawable; + + fn canvas(&mut self) -> &mut Self::Canvas; + fn measure(&self) -> MeasuredSize; + fn move_canvas(&mut self, pos: Position); +} + +pub struct CCanvasStyle +where + C: PixelColor, +{ + pub clear_color: C, + pub canvas: CCanvasAt, +} + +impl CanvasProperties for CCanvasStyle +where + C: PixelColor, +{ + type Color = C; + type Canvas = CCanvasAt; + + fn canvas(&mut self) -> &mut Self::Canvas { + &mut self.canvas + } + + fn measure(&self) -> MeasuredSize { + MeasuredSize { + width: W as u32, + height: H as u32, + } + } + + fn move_canvas(&mut self, pos: Position) { + self.canvas.top_left = Point { x: pos.x, y: pos.y }; + } +} + +impl Default for CCanvasStyle +where + C: Theme, +{ + fn default() -> Self { + Self { + clear_color: C::BACKGROUND_COLOR, + canvas: CCanvasAt::new(Point::zero()), + } + } +} + +//#[cfg(feature = "std")] +use embedded_canvas::CanvasAt; + +//#[cfg(feature = "std")] +pub struct CanvasStyle +where +C: PixelColor, +{ + pub clear_color: C, + pub canvas: CanvasAt, +} + + +//#[cfg(feature = "std")] +impl CanvasStyle +where + C: Theme, +{ + pub fn new(size: Size) -> Self { + Self { + clear_color: C::BACKGROUND_COLOR, + canvas: CanvasAt::new(Point::zero(), size), + } + } +} + +//#[cfg(feature = "std")] +impl CanvasProperties for CanvasStyle +where + C: PixelColor, +{ + type Color = C; + type Canvas = CanvasAt; + + fn canvas(&mut self) -> &mut Self::Canvas { + &mut self.canvas + } + + fn measure(&self) -> MeasuredSize { + MeasuredSize { + width: self.canvas.bounding_box().size.width, + height: self.canvas.bounding_box().size.height, + } + } + + fn move_canvas(&mut self, pos: Position) { + self.canvas.top_left = Point { x: pos.x, y: pos.y }; + } +} + +//#[cfg(feature = "std")] +impl Default for CanvasStyle +where + C: Theme, +{ + fn default() -> Self { + Self { + clear_color: C::BACKGROUND_COLOR, + canvas: CanvasAt::new(Point::zero(), Size::zero()), + } + } +} + +pub struct Canvas

{ + pub bounds: BoundingBox, + pub parent_index: usize, + pub canvas_properties: P, +} + +impl

Canvas

{ + pub fn new() -> Self + where + P: Default, + { + Self { + parent_index: 0, + bounds: BoundingBox::default(), + canvas_properties: P::default(), + } + } + + pub fn with_properties(properties: P) -> Self + where + P: Default, + { + Self { + parent_index: 0, + bounds: BoundingBox::default(), + canvas_properties: properties, + } + } + + pub fn canvas(&mut self) -> &mut P::Canvas + where + P: CanvasProperties, + { + self.canvas_properties.canvas() + } +} + +impl

WrapperBindable for Canvas

where P: CanvasProperties {} + +impl

Widget for Canvas

+where + P: CanvasProperties, +{ + fn bounding_box(&self) -> BoundingBox { + self.bounds + } + + fn bounding_box_mut(&mut self) -> &mut BoundingBox { + &mut self.bounds + } + + fn measure(&mut self, measure_spec: MeasureSpec) { + let canvas_size = self.canvas_properties.measure(); + + let width = measure_spec.width.apply_to_measured(canvas_size.width); + let height = measure_spec.height.apply_to_measured(canvas_size.height); + + self.bounds.size = MeasuredSize { width, height }; + } + + fn parent_index(&self) -> usize { + self.parent_index + } + + fn set_parent(&mut self, index: usize) { + self.parent_index = index; + } + + fn on_state_changed(&mut self, _: WidgetState) {} +} + +impl WidgetRenderer> for Canvas

+where + C: PixelColor, + DT: DrawTarget, + P: CanvasProperties, +{ + fn draw(&mut self, canvas: &mut EgCanvas

) -> Result<(), DT::Error> { + self.canvas_properties.move_canvas(self.bounds.position); + self.canvas().draw(&mut canvas.target)?; + + Ok(()) + } +} diff --git a/backend-embedded-graphics/src/widgets/mod.rs b/backend-embedded-graphics/src/widgets/mod.rs index b964d23..d0d7a34 100644 --- a/backend-embedded-graphics/src/widgets/mod.rs +++ b/backend-embedded-graphics/src/widgets/mod.rs @@ -1,5 +1,6 @@ pub mod background; pub mod border; +pub mod canvas; pub mod graphical; pub mod label; pub mod scroll; diff --git a/examples/canvas.rs b/examples/canvas.rs new file mode 100644 index 0000000..0fcf9e9 --- /dev/null +++ b/examples/canvas.rs @@ -0,0 +1,162 @@ +use std::{thread, time::Duration}; + +use backend_embedded_graphics::{ + themes::{default::DefaultTheme, Theme}, + widgets::canvas::{Canvas, CanvasStyle}, + EgCanvas, +}; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::Rgb888, + prelude::{RgbColor, Size as EgSize}, + primitives::{Circle, Primitive, PrimitiveStyle, Rectangle}, + Drawable, +}; +use embedded_graphics_simulator::{ + sdl2::MouseButton, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window as SimWindow, +}; +use embedded_gui::{ + data::BoundData, + geometry::Position, + input::event::{InputEvent, PointerEvent}, + prelude::*, + widgets::{border::Border, fill::FillParent, layouts::linear::Column}, +}; + +fn convert_input(event: SimulatorEvent) -> Result { + unsafe { + // This is fine for a demo + static mut MOUSE_DOWN: bool = false; + match event { + SimulatorEvent::MouseButtonUp { + mouse_btn: MouseButton::Left, + point, + } => { + MOUSE_DOWN = false; + Ok(InputEvent::PointerEvent( + Position { + x: point.x, + y: point.y, + }, + PointerEvent::Up, + )) + } + SimulatorEvent::MouseButtonDown { + mouse_btn: MouseButton::Left, + point, + } => { + MOUSE_DOWN = true; + Ok(InputEvent::PointerEvent( + Position { + x: point.x, + y: point.y, + }, + PointerEvent::Down, + )) + } + SimulatorEvent::MouseMove { point } => Ok(InputEvent::PointerEvent( + Position { + x: point.x, + y: point.y, + }, + if MOUSE_DOWN { + PointerEvent::Drag + } else { + PointerEvent::Hover + }, + )), + SimulatorEvent::Quit => Err(true), + _ => Err(false), + } + } +} + +struct AnimationState { + enabled: bool, + time: u32, +} + +fn main() { + let display = SimulatorDisplay::new(EgSize::new(256, 128)); + + let state = BoundData::new( + AnimationState { + enabled: false, + time: 0, + }, + |_| {}, + ); + + let mut gui = Window::new( + EgCanvas::new(display), + Column::new() + .add( + DefaultTheme::primary_button("Animate") + .bind(&state) + .on_clicked(|state| state.enabled = !state.enabled), + ) + .add(FillParent::both(Border::new( + Canvas::with_properties(CanvasStyle::::new(EgSize::new(256, 128))) + .bind(&state) + .on_data_changed(|widget, state| { + let clear_color = widget.canvas_properties.clear_color; + let canvas = widget.canvas(); + + canvas.clear(clear_color).unwrap(); + + let t = state.time % 100; + + let increment = if t < 50 { t } else { 50 - (t - 50) }; + + let rectangle = Rectangle::with_center( + canvas.center(), + EgSize { + width: 50 + increment, + height: 50 + increment, + }, + ) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::BLUE, 1)); + rectangle.draw(canvas).unwrap(); + + let circle = Circle::with_center(canvas.center(), 40 + increment) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 1)); + circle.draw(canvas).unwrap(); + }), + ))), + ); + + let output_settings = OutputSettingsBuilder::new().scale(2).build(); + let mut window = SimWindow::new("GUI demonstration", &output_settings); + + loop { + gui.canvas.target.clear(Rgb888::BACKGROUND_COLOR).unwrap(); + + gui.update(); + gui.measure(); + gui.arrange(); + gui.draw().unwrap(); + + state.update(|state| { + if state.enabled { + state.time = state.time.wrapping_add(1); + } + }); + + // Update the window. + window.update(&gui.canvas.target); + + // Handle key and mouse events. + for event in window.events() { + match convert_input(event) { + Ok(input) => { + gui.input_event(input); + } + Err(true) => return, + _ => {} + } + } + + // Wait for a little while. + thread::sleep(Duration::from_millis(10)); + } +} From 2c93791e4894805553236f58d119e0464828241c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 17 Mar 2023 20:01:47 +0100 Subject: [PATCH 2/8] Allow handling events --- .../src/widgets/canvas.rs | 130 ++++++++++++++++-- examples/canvas.rs | 113 ++++++++++++++- 2 files changed, 229 insertions(+), 14 deletions(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index a6fdd98..dba4e46 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -1,12 +1,19 @@ -use embedded_canvas::{ CCanvasAt}; +use embedded_canvas::CCanvasAt; use embedded_graphics::{ - prelude::{DrawTarget, PixelColor, Point, Dimensions, Size}, + prelude::{Dimensions, DrawTarget, PixelColor, Point, Size}, Drawable, }; use embedded_gui::{ geometry::{measurement::MeasureSpec, BoundingBox, MeasuredSize, Position}, + input::{ + controller::InputContext, + event::{InputEvent, PointerEvent}, + }, prelude::WrapperBindable, - state::WidgetState, + state::{ + selection::{Selected, Unselected}, + WidgetState, + }, widgets::Widget, WidgetRenderer, }; @@ -71,13 +78,12 @@ use embedded_canvas::CanvasAt; //#[cfg(feature = "std")] pub struct CanvasStyle where -C: PixelColor, + C: PixelColor, { pub clear_color: C, pub canvas: CanvasAt, } - //#[cfg(feature = "std")] impl CanvasStyle where @@ -128,14 +134,21 @@ where } } -pub struct Canvas

{ +pub struct Canvas bool> { pub bounds: BoundingBox, pub parent_index: usize, pub canvas_properties: P, + pub state: WidgetState, + handler: Option, +} + +impl Canvas<(), fn(InputContext, InputEvent) -> bool> { + pub const STATE_SELECTED: Selected = Selected; + pub const STATE_UNSELECTED: Unselected = Unselected; } -impl

Canvas

{ - pub fn new() -> Self +impl

Canvas bool> { + pub fn new() -> Canvas bool> where P: Default, { @@ -143,10 +156,12 @@ impl

Canvas

{ parent_index: 0, bounds: BoundingBox::default(), canvas_properties: P::default(), + state: WidgetState::default(), + handler: None, } } - pub fn with_properties(properties: P) -> Self + pub fn with_properties(properties: P) -> Canvas bool> where P: Default, { @@ -154,22 +169,48 @@ impl

Canvas

{ parent_index: 0, bounds: BoundingBox::default(), canvas_properties: properties, + state: WidgetState::default(), + handler: None, } } +} +impl Canvas +where + H: FnMut(InputContext, InputEvent) -> bool, +{ pub fn canvas(&mut self) -> &mut P::Canvas where P: CanvasProperties, { self.canvas_properties.canvas() } + + pub fn with_input_handler

(self, handler: H2) -> Canvas + where + H2: FnMut(InputContext, InputEvent) -> bool, + { + Canvas { + parent_index: self.parent_index, + bounds: self.bounds, + canvas_properties: self.canvas_properties, + state: self.state, + handler: Some(handler), + } + } } -impl

WrapperBindable for Canvas

where P: CanvasProperties {} +impl WrapperBindable for Canvas +where + P: CanvasProperties, + H: FnMut(InputContext, InputEvent) -> bool, +{ +} -impl

Widget for Canvas

+impl Widget for Canvas where P: CanvasProperties, + H: FnMut(InputContext, InputEvent) -> bool, { fn bounding_box(&self) -> BoundingBox { self.bounds @@ -197,13 +238,78 @@ where } fn on_state_changed(&mut self, _: WidgetState) {} + + fn test_input(&mut self, event: InputEvent) -> Option { + let bounds = self.bounding_box(); + + let state = &mut self.state; + self.handler.as_mut().and_then(|_| match event { + InputEvent::Cancel => { + state.set_state(Canvas::STATE_UNSELECTED); + None + } + + InputEvent::PointerEvent(position, PointerEvent::Down) => { + if bounds.contains(position) { + Some(0) + } else { + // Allow a potentially clicked widget to handle the event. + state.set_state(Canvas::STATE_UNSELECTED); + None + } + } + + InputEvent::PointerEvent(position, PointerEvent::Drag | PointerEvent::Hover) => { + if bounds.contains(position) { + Some(0) + } else { + None + } + } + + InputEvent::KeyEvent(_) => { + if state.has_state(Canvas::STATE_SELECTED) { + Some(0) + } else { + None + } + } + + _ => Some(0), + }) + } + + fn handle_input(&mut self, ctxt: InputContext, event: InputEvent) -> bool { + let state = &mut self.state; + self.handler + .as_mut() + .map(|handler| { + match event { + InputEvent::Cancel => { + state.set_state(Canvas::STATE_UNSELECTED); + } + InputEvent::PointerEvent(_, PointerEvent::Down) => { + state.set_state(Canvas::STATE_SELECTED); + } + _ => {} + } + + handler(ctxt, event) + }) + .unwrap_or(false) + } + + fn is_selectable(&self) -> bool { + self.handler.is_some() + } } -impl WidgetRenderer> for Canvas

+impl WidgetRenderer> for Canvas where C: PixelColor, DT: DrawTarget, P: CanvasProperties, + H: FnMut(InputContext, InputEvent) -> bool, { fn draw(&mut self, canvas: &mut EgCanvas

) -> Result<(), DT::Error> { self.canvas_properties.move_canvas(self.bounds.position); diff --git a/examples/canvas.rs b/examples/canvas.rs index 0fcf9e9..41b13af 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -13,16 +13,97 @@ use embedded_graphics::{ Drawable, }; use embedded_graphics_simulator::{ - sdl2::MouseButton, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window as SimWindow, + sdl2::{Keycode, Mod, MouseButton}, + OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window as SimWindow, }; use embedded_gui::{ data::BoundData, geometry::Position, - input::event::{InputEvent, PointerEvent}, + input::event::{InputEvent, Key, KeyEvent, Modifier, PointerEvent}, prelude::*, widgets::{border::Border, fill::FillParent, layouts::linear::Column}, }; +trait Convert { + type Output; + + fn convert(self) -> Self::Output; +} + +impl Convert for Keycode { + type Output = Option; + + fn convert(self) -> Self::Output { + match self { + Keycode::Backspace => Some(Key::Backspace), + Keycode::Tab => Some(Key::Tab), + Keycode::Return => Some(Key::Enter), + Keycode::Space => Some(Key::Space), + Keycode::KpComma | Keycode::Comma => Some(Key::Comma), + Keycode::KpMinus | Keycode::Minus => Some(Key::Minus), + Keycode::KpPeriod | Keycode::Period => Some(Key::Period), + Keycode::Kp1 | Keycode::Num0 => Some(Key::N0), + Keycode::Kp2 | Keycode::Num1 => Some(Key::N1), + Keycode::Kp3 | Keycode::Num2 => Some(Key::N2), + Keycode::Kp4 | Keycode::Num3 => Some(Key::N3), + Keycode::Kp5 | Keycode::Num4 => Some(Key::N4), + Keycode::Kp6 | Keycode::Num5 => Some(Key::N5), + Keycode::Kp7 | Keycode::Num6 => Some(Key::N6), + Keycode::Kp8 | Keycode::Num7 => Some(Key::N7), + Keycode::Kp9 | Keycode::Num8 => Some(Key::N8), + Keycode::Kp0 | Keycode::Num9 => Some(Key::N9), + Keycode::A => Some(Key::A), + Keycode::B => Some(Key::B), + Keycode::C => Some(Key::C), + Keycode::D => Some(Key::D), + Keycode::E => Some(Key::E), + Keycode::F => Some(Key::F), + Keycode::G => Some(Key::G), + Keycode::H => Some(Key::H), + Keycode::I => Some(Key::I), + Keycode::J => Some(Key::J), + Keycode::K => Some(Key::K), + Keycode::L => Some(Key::L), + Keycode::M => Some(Key::M), + Keycode::N => Some(Key::N), + Keycode::O => Some(Key::O), + Keycode::P => Some(Key::P), + Keycode::Q => Some(Key::Q), + Keycode::R => Some(Key::R), + Keycode::S => Some(Key::S), + Keycode::T => Some(Key::T), + Keycode::U => Some(Key::U), + Keycode::V => Some(Key::V), + Keycode::W => Some(Key::W), + Keycode::X => Some(Key::X), + Keycode::Y => Some(Key::Y), + Keycode::Z => Some(Key::Z), + Keycode::Delete => Some(Key::Del), + Keycode::Right => Some(Key::ArrowRight), + Keycode::Left => Some(Key::ArrowLeft), + Keycode::Down => Some(Key::ArrowDown), + Keycode::Up => Some(Key::ArrowUp), + _ => None, + } + } +} + +impl Convert for Mod { + type Output = Modifier; + + fn convert(self) -> Self::Output { + if self.contains(Mod::RALTMOD) { + Modifier::Alt + } else if self.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD) { + Modifier::Shift + } else if self.contains(Mod::CAPSMOD) { + Modifier::Shift + } else { + Modifier::None + } + } +} + fn convert_input(event: SimulatorEvent) -> Result { unsafe { // This is fine for a demo @@ -65,6 +146,20 @@ fn convert_input(event: SimulatorEvent) -> Result { PointerEvent::Hover }, )), + SimulatorEvent::KeyDown { + keycode, keymod, .. + } => Ok(InputEvent::KeyEvent(KeyEvent::KeyDown( + keycode.convert().ok_or(false)?, + keymod.convert(), + 0, + ))), + + SimulatorEvent::KeyUp { + keycode, keymod, .. + } => Ok(InputEvent::KeyEvent(KeyEvent::KeyUp( + keycode.convert().ok_or(false)?, + keymod.convert(), + ))), SimulatorEvent::Quit => Err(true), _ => Err(false), } @@ -97,6 +192,20 @@ fn main() { ) .add(FillParent::both(Border::new( Canvas::with_properties(CanvasStyle::::new(EgSize::new(256, 128))) + .with_input_handler(|_ctxt, input| { + state.update(|data| match input { + InputEvent::PointerEvent(_, event) => match event { + PointerEvent::Up => data.enabled = true, + PointerEvent::Down => data.enabled = false, + PointerEvent::Hover | PointerEvent::Drag => {} + }, + InputEvent::KeyEvent(KeyEvent::KeyDown(Key::Space, _, _)) => { + data.enabled = !data.enabled; + } + _ => (), + }); + true + }) .bind(&state) .on_data_changed(|widget, state| { let clear_color = widget.canvas_properties.clear_color; From ae33a22fb03d9f569689d336c05076b44c9b5028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 17 Mar 2023 21:13:15 +0100 Subject: [PATCH 3/8] Draw canvas to the right area --- .../src/widgets/canvas.rs | 34 +++++++++---------- examples/canvas.rs | 15 ++++---- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index dba4e46..f8b9bda 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -1,10 +1,10 @@ use embedded_canvas::CCanvasAt; use embedded_graphics::{ - prelude::{Dimensions, DrawTarget, PixelColor, Point, Size}, + prelude::{Dimensions, DrawTarget, DrawTargetExt, PixelColor, Point, Size}, Drawable, }; use embedded_gui::{ - geometry::{measurement::MeasureSpec, BoundingBox, MeasuredSize, Position}, + geometry::{measurement::MeasureSpec, BoundingBox, MeasuredSize}, input::{ controller::InputContext, event::{InputEvent, PointerEvent}, @@ -18,15 +18,14 @@ use embedded_gui::{ WidgetRenderer, }; -use crate::{themes::Theme, EgCanvas}; +use crate::{themes::Theme, EgCanvas, ToRectangle}; pub trait CanvasProperties { type Color; - type Canvas: Drawable; + type Canvas: DrawTargetExt + Drawable; fn canvas(&mut self) -> &mut Self::Canvas; fn measure(&self) -> MeasuredSize; - fn move_canvas(&mut self, pos: Position); } pub struct CCanvasStyle @@ -54,10 +53,6 @@ where height: H as u32, } } - - fn move_canvas(&mut self, pos: Position) { - self.canvas.top_left = Point { x: pos.x, y: pos.y }; - } } impl Default for CCanvasStyle @@ -115,10 +110,6 @@ where height: self.canvas.bounding_box().size.height, } } - - fn move_canvas(&mut self, pos: Position) { - self.canvas.top_left = Point { x: pos.x, y: pos.y }; - } } //#[cfg(feature = "std")] @@ -179,11 +170,18 @@ impl Canvas where H: FnMut(InputContext, InputEvent) -> bool, { - pub fn canvas(&mut self) -> &mut P::Canvas + pub fn canvas( + &mut self, + ) -> impl DrawTarget< + Color = ::Color, + Error = ::Error, + > + Dimensions + + '_ where P: CanvasProperties, { - self.canvas_properties.canvas() + let bounds = self.bounding_box().to_rectangle(); + self.canvas_properties.canvas().cropped(&bounds) } pub fn with_input_handler

(self, handler: H2) -> Canvas @@ -312,8 +310,10 @@ where H: FnMut(InputContext, InputEvent) -> bool, { fn draw(&mut self, canvas: &mut EgCanvas
) -> Result<(), DT::Error> { - self.canvas_properties.move_canvas(self.bounds.position); - self.canvas().draw(&mut canvas.target)?; + let bounds = self.bounding_box().to_rectangle(); + self.canvas_properties + .canvas() + .draw(&mut canvas.target.clipped(&bounds))?; Ok(()) } diff --git a/examples/canvas.rs b/examples/canvas.rs index 41b13af..0b508cd 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -8,7 +8,7 @@ use backend_embedded_graphics::{ use embedded_graphics::{ draw_target::DrawTarget, pixelcolor::Rgb888, - prelude::{RgbColor, Size as EgSize}, + prelude::{Dimensions, RgbColor, Size as EgSize}, primitives::{Circle, Primitive, PrimitiveStyle, Rectangle}, Drawable, }; @@ -209,7 +209,7 @@ fn main() { .bind(&state) .on_data_changed(|widget, state| { let clear_color = widget.canvas_properties.clear_color; - let canvas = widget.canvas(); + let mut canvas = widget.canvas(); canvas.clear(clear_color).unwrap(); @@ -218,18 +218,19 @@ fn main() { let increment = if t < 50 { t } else { 50 - (t - 50) }; let rectangle = Rectangle::with_center( - canvas.center(), + canvas.bounding_box().center(), EgSize { width: 50 + increment, height: 50 + increment, }, ) .into_styled(PrimitiveStyle::with_stroke(Rgb888::BLUE, 1)); - rectangle.draw(canvas).unwrap(); + rectangle.draw(&mut canvas).unwrap(); - let circle = Circle::with_center(canvas.center(), 40 + increment) - .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 1)); - circle.draw(canvas).unwrap(); + let circle = + Circle::with_center(canvas.bounding_box().center(), 40 + increment) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 1)); + circle.draw(&mut canvas).unwrap(); }), ))), ); From 3ee4505a424a658eefa6932342417817e4539d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 17 Mar 2023 21:31:37 +0100 Subject: [PATCH 4/8] Avoid opaque type --- backend-embedded-graphics/src/widgets/canvas.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index f8b9bda..3ecd2ef 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -1,5 +1,6 @@ use embedded_canvas::CCanvasAt; use embedded_graphics::{ + draw_target::Cropped, prelude::{Dimensions, DrawTarget, DrawTargetExt, PixelColor, Point, Size}, Drawable, }; @@ -170,13 +171,7 @@ impl Canvas where H: FnMut(InputContext, InputEvent) -> bool, { - pub fn canvas( - &mut self, - ) -> impl DrawTarget< - Color = ::Color, - Error = ::Error, - > + Dimensions - + '_ + pub fn canvas(&mut self) -> Cropped<'_, P::Canvas> where P: CanvasProperties, { From 787dd5f13527a7749a24ae9a253c887465b7612b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 17 Mar 2023 22:03:41 +0100 Subject: [PATCH 5/8] Place example in a frame layout --- examples/canvas.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/canvas.rs b/examples/canvas.rs index 0b508cd..221e3a1 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -21,7 +21,7 @@ use embedded_gui::{ geometry::Position, input::event::{InputEvent, Key, KeyEvent, Modifier, PointerEvent}, prelude::*, - widgets::{border::Border, fill::FillParent, layouts::linear::Column}, + widgets::{border::Border, fill::FillParent, layouts::frame::Frame}, }; trait Convert { @@ -184,13 +184,8 @@ fn main() { let mut gui = Window::new( EgCanvas::new(display), - Column::new() - .add( - DefaultTheme::primary_button("Animate") - .bind(&state) - .on_clicked(|state| state.enabled = !state.enabled), - ) - .add(FillParent::both(Border::new( + Frame::new() + .add_layer(FillParent::both(Border::new( Canvas::with_properties(CanvasStyle::::new(EgSize::new(256, 128))) .with_input_handler(|_ctxt, input| { state.update(|data| match input { @@ -232,7 +227,12 @@ fn main() { .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 1)); circle.draw(&mut canvas).unwrap(); }), - ))), + ))) + .add_layer( + DefaultTheme::primary_button("Animate") + .bind(&state) + .on_clicked(|state| state.enabled = !state.enabled), + ), ); let output_settings = OutputSettingsBuilder::new().scale(2).build(); From 642d1b40f5b57961fab4242b79fe0b2c1c5ca89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 17 Mar 2023 22:16:22 +0100 Subject: [PATCH 6/8] Don't handle hover --- backend-embedded-graphics/src/widgets/canvas.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index 3ecd2ef..5b62dbf 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -252,7 +252,10 @@ where } } - InputEvent::PointerEvent(position, PointerEvent::Drag | PointerEvent::Hover) => { + // We want controls drawn above the Canvas to get input events. + InputEvent::PointerEvent(_, PointerEvent::Hover) => None, + + InputEvent::PointerEvent(position, PointerEvent::Drag) => { if bounds.contains(position) { Some(0) } else { From e51f2a877de7119dbcd7d17144e58d3f43479abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sun, 19 Mar 2023 11:18:11 +0100 Subject: [PATCH 7/8] Allow accessing clear color --- backend-embedded-graphics/src/widgets/canvas.rs | 11 ++++++++++- examples/canvas.rs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index 5b62dbf..6482e5b 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -23,9 +23,10 @@ use crate::{themes::Theme, EgCanvas, ToRectangle}; pub trait CanvasProperties { type Color; - type Canvas: DrawTargetExt + Drawable; + type Canvas: DrawTarget + DrawTargetExt + Drawable; fn canvas(&mut self) -> &mut Self::Canvas; + fn clear_color(&mut self) -> Self::Color; fn measure(&self) -> MeasuredSize; } @@ -48,6 +49,10 @@ where &mut self.canvas } + fn clear_color(&mut self) -> Self::Color { + self.clear_color + } + fn measure(&self) -> MeasuredSize { MeasuredSize { width: W as u32, @@ -105,6 +110,10 @@ where &mut self.canvas } + fn clear_color(&mut self) -> Self::Color { + self.clear_color + } + fn measure(&self) -> MeasuredSize { MeasuredSize { width: self.canvas.bounding_box().size.width, diff --git a/examples/canvas.rs b/examples/canvas.rs index 221e3a1..d3c6263 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -2,7 +2,7 @@ use std::{thread, time::Duration}; use backend_embedded_graphics::{ themes::{default::DefaultTheme, Theme}, - widgets::canvas::{Canvas, CanvasStyle}, + widgets::canvas::{Canvas, CanvasProperties, CanvasStyle}, EgCanvas, }; use embedded_graphics::{ @@ -203,7 +203,7 @@ fn main() { }) .bind(&state) .on_data_changed(|widget, state| { - let clear_color = widget.canvas_properties.clear_color; + let clear_color = widget.canvas_properties.clear_color(); let mut canvas = widget.canvas(); canvas.clear(clear_color).unwrap(); From d128d1b753a01b630c00c06a1878c33cdbad3e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sun, 19 Mar 2023 11:29:20 +0100 Subject: [PATCH 8/8] Only draw once per frame --- .../src/widgets/canvas.rs | 77 ++++++++++++++----- examples/canvas.rs | 20 ++--- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/backend-embedded-graphics/src/widgets/canvas.rs b/backend-embedded-graphics/src/widgets/canvas.rs index 6482e5b..9828d24 100644 --- a/backend-embedded-graphics/src/widgets/canvas.rs +++ b/backend-embedded-graphics/src/widgets/canvas.rs @@ -135,60 +135,69 @@ where } } -pub struct Canvas bool> { +pub struct Canvas { pub bounds: BoundingBox, pub parent_index: usize, pub canvas_properties: P, pub state: WidgetState, handler: Option, + on_draw: D, + draw: bool, } -impl Canvas<(), fn(InputContext, InputEvent) -> bool> { +impl Canvas<(), (), ()> { pub const STATE_SELECTED: Selected = Selected; pub const STATE_UNSELECTED: Unselected = Unselected; } -impl

Canvas bool> { - pub fn new() -> Canvas bool> +impl

Canvas +where + P: CanvasProperties, +{ + pub fn new() -> Canvas bool, fn(&mut Cropped<'_, P::Canvas>)> where P: Default, { - Self { + Canvas { parent_index: 0, bounds: BoundingBox::default(), canvas_properties: P::default(), state: WidgetState::default(), handler: None, + on_draw: |_| {}, + draw: true, } } - pub fn with_properties(properties: P) -> Canvas bool> + pub fn with_properties( + properties: P, + ) -> Canvas bool, fn(&mut Cropped<'_, P::Canvas>)> where P: Default, { - Self { + Canvas { parent_index: 0, bounds: BoundingBox::default(), canvas_properties: properties, state: WidgetState::default(), handler: None, + on_draw: |_| {}, + draw: true, } } } -impl Canvas +impl Canvas where + P: CanvasProperties, H: FnMut(InputContext, InputEvent) -> bool, + D: FnMut(&mut Cropped<'_, P::Canvas>), { - pub fn canvas(&mut self) -> Cropped<'_, P::Canvas> - where - P: CanvasProperties, - { - let bounds = self.bounding_box().to_rectangle(); - self.canvas_properties.canvas().cropped(&bounds) + pub fn invalidate(&mut self) { + self.draw = true; } - pub fn with_input_handler

(self, handler: H2) -> Canvas + pub fn with_input_handler

(self, handler: H2) -> Canvas where H2: FnMut(InputContext, InputEvent) -> bool, { @@ -198,21 +207,41 @@ where canvas_properties: self.canvas_properties, state: self.state, handler: Some(handler), + on_draw: self.on_draw, + draw: self.draw, + } + } + + pub fn with_on_draw(self, on_draw: D2) -> Canvas + where + P: CanvasProperties, + D2: FnMut(&mut Cropped<'_, P::Canvas>), + { + Canvas { + parent_index: self.parent_index, + bounds: self.bounds, + canvas_properties: self.canvas_properties, + state: self.state, + handler: self.handler, + on_draw, + draw: self.draw, } } } -impl WrapperBindable for Canvas +impl WrapperBindable for Canvas where P: CanvasProperties, H: FnMut(InputContext, InputEvent) -> bool, + D: FnMut(&mut Cropped<'_, P::Canvas>), { } -impl Widget for Canvas +impl Widget for Canvas where P: CanvasProperties, H: FnMut(InputContext, InputEvent) -> bool, + D: FnMut(&mut Cropped<'_, P::Canvas>), { fn bounding_box(&self) -> BoundingBox { self.bounds @@ -309,14 +338,26 @@ where } } -impl WidgetRenderer> for Canvas +impl WidgetRenderer> for Canvas where C: PixelColor, DT: DrawTarget, P: CanvasProperties, H: FnMut(InputContext, InputEvent) -> bool, + D: FnMut(&mut Cropped<'_, P::Canvas>), { fn draw(&mut self, canvas: &mut EgCanvas
) -> Result<(), DT::Error> { + if self.draw { + self.draw = false; + let bounds = self.bounding_box().to_rectangle(); + let clear_color = self.canvas_properties.clear_color(); + let mut canvas = self.canvas_properties.canvas().cropped(&bounds); + + _ = canvas.clear(clear_color); + + (self.on_draw)(&mut canvas); + } + let bounds = self.bounding_box().to_rectangle(); self.canvas_properties .canvas() diff --git a/examples/canvas.rs b/examples/canvas.rs index d3c6263..d691575 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -2,7 +2,7 @@ use std::{thread, time::Duration}; use backend_embedded_graphics::{ themes::{default::DefaultTheme, Theme}, - widgets::canvas::{Canvas, CanvasProperties, CanvasStyle}, + widgets::canvas::{Canvas, CanvasStyle}, EgCanvas, }; use embedded_graphics::{ @@ -201,14 +201,8 @@ fn main() { }); true }) - .bind(&state) - .on_data_changed(|widget, state| { - let clear_color = widget.canvas_properties.clear_color(); - let mut canvas = widget.canvas(); - - canvas.clear(clear_color).unwrap(); - - let t = state.time % 100; + .with_on_draw(|canvas| { + let t = state.with_data(|state| state.time % 100); let increment = if t < 50 { t } else { 50 - (t - 50) }; @@ -220,13 +214,15 @@ fn main() { }, ) .into_styled(PrimitiveStyle::with_stroke(Rgb888::BLUE, 1)); - rectangle.draw(&mut canvas).unwrap(); + rectangle.draw(canvas).unwrap(); let circle = Circle::with_center(canvas.bounding_box().center(), 40 + increment) .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 1)); - circle.draw(&mut canvas).unwrap(); - }), + circle.draw(canvas).unwrap(); + }) + .bind(&state) + .on_data_changed(|widget, _| widget.invalidate()), ))) .add_layer( DefaultTheme::primary_button("Animate")