From 35d7a43dcd209fdddccfbbd02f4db8131b6c8e0d Mon Sep 17 00:00:00 2001 From: arthomnix Date: Tue, 14 Jan 2025 11:48:50 +0000 Subject: [PATCH] fw16-epd-gui: add slider element --- fw16-epd-gui/src/element/button.rs | 20 ++--- fw16-epd-gui/src/element/mod.rs | 17 ++--- fw16-epd-gui/src/element/slider.rs | 114 +++++++++++++++++++++++++++++ fw16-epd-main/src/gui.rs | 47 ++++++++++-- 4 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 fw16-epd-gui/src/element/slider.rs diff --git a/fw16-epd-gui/src/element/button.rs b/fw16-epd-gui/src/element/button.rs index 98017d3..1650df4 100644 --- a/fw16-epd-gui/src/element/button.rs +++ b/fw16-epd-gui/src/element/button.rs @@ -22,12 +22,12 @@ pub struct ButtonOutput { #[derive(Debug, defmt::Format)] pub struct Button<'a> { - rect: RoundedRectangle, - label: &'a str, - rect_style: PrimitiveStyle, - char_style: MonoTextStyle<'a, BinaryColor>, - touch_feedback: bool, - touch_feedback_immediate_release: bool, + pub rect: RoundedRectangle, + pub label: &'a str, + pub rect_style: PrimitiveStyle, + pub char_style: MonoTextStyle<'a, BinaryColor>, + pub touch_feedback: bool, + pub touch_feedback_immediate_release: bool, began_click: bool, inverted: bool, @@ -90,10 +90,6 @@ impl<'a> Button<'a> { ) } - pub fn rect(&self) -> Rectangle { - self.rect.bounding_box() - } - fn invert(&mut self) { self.rect_style.fill_color = self.rect_style.fill_color.map(|c| c.invert()); self.char_style.text_color = self.char_style.text_color.map(|c| c.invert()); @@ -172,4 +168,8 @@ impl<'a> Gui for Button<'a> { ret } + + fn bounding_box(&self) -> Rectangle { + self.rect.into_styled(self.rect_style).bounding_box() + } } \ No newline at end of file diff --git a/fw16-epd-gui/src/element/mod.rs b/fw16-epd-gui/src/element/mod.rs index 6d955db..aefe936 100644 --- a/fw16-epd-gui/src/element/mod.rs +++ b/fw16-epd-gui/src/element/mod.rs @@ -1,11 +1,13 @@ pub mod button; +pub mod slider; use embedded_graphics::mono_font::ascii::FONT_10X20; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{PrimitiveStyle, PrimitiveStyleBuilder}; +use embedded_graphics::primitives::{PrimitiveStyle, PrimitiveStyleBuilder, Rectangle}; use fw16_epd_program_interface::{Event, TouchEvent}; +use tp370pgh01::{DIM_X, DIM_Y}; use crate::draw_target::EpdDrawTarget; pub const DEFAULT_PRIMITIVE_STYLE: PrimitiveStyle = PrimitiveStyleBuilder::new() @@ -21,16 +23,9 @@ pub trait Gui { fn draw_init(&self, target: &mut EpdDrawTarget); fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output; -} -impl> Gui for T { - type Output = (); - - fn draw_init(&self, target: &mut EpdDrawTarget) { - self.draw(target).unwrap(); - } - - fn tick(&mut self, _target: &mut EpdDrawTarget, _ev: Event) { - // no-op + // By default, assume element fills the display + fn bounding_box(&self) -> Rectangle { + Rectangle::new(Point::zero(), Size::new(DIM_X as u32, DIM_Y as u32)) } } \ No newline at end of file diff --git a/fw16-epd-gui/src/element/slider.rs b/fw16-epd-gui/src/element/slider.rs new file mode 100644 index 0000000..61feca8 --- /dev/null +++ b/fw16-epd-gui/src/element/slider.rs @@ -0,0 +1,114 @@ +use embedded_graphics::prelude::*; +use embedded_graphics::geometry::Point; +use embedded_graphics::pixelcolor::BinaryColor; +use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle, Rectangle}; +use fw16_epd_program_interface::{Event, TouchEventType}; +use crate::draw_target::EpdDrawTarget; +use crate::element::{Gui, DEFAULT_PRIMITIVE_STYLE}; + +pub struct Slider { + pub start_point: Point, + pub length: i32, + pub min: i32, + pub max: i32, + pub value: i32, + + pub line_style: PrimitiveStyle, + pub marker_style: PrimitiveStyle, + pub marker_radius: i32, + + sliding: bool, +} + +impl Slider { + pub fn new(start_point: Point, length: i32, min: i32, max: i32, starting_value: i32, line_style: PrimitiveStyle, marker_style: PrimitiveStyle, marker_radius: i32) -> Self { + Self { + start_point, + length, + min, + max, + value: starting_value, + + line_style, + marker_style, + marker_radius, + + sliding: false, + } + } + + pub fn with_default_style(start_point: Point, length: i32, min: i32, max: i32, starting_value: i32) -> Self { + Self { + start_point, + length, + min, + max, + value: starting_value, + + line_style: DEFAULT_PRIMITIVE_STYLE, + marker_style: DEFAULT_PRIMITIVE_STYLE, + marker_radius: 9, + + sliding: false, + } + } +} + +impl Gui for Slider { + type Output = bool; + + fn draw_init(&self, target: &mut EpdDrawTarget) { + target.fill_solid(&self.bounding_box(), BinaryColor::Off).unwrap(); + + Line::new(self.start_point, self.start_point + Point::new(self.length, 0)) + .into_styled(self.line_style) + .draw(target) + .unwrap(); + + let x = self.start_point.x + (self.length * (self.value - self.min)) / (self.max - self.min); + let marker_point = Point::new(x, self.start_point.y); + Circle::with_center(marker_point, self.marker_radius as u32 * 2) + .into_styled(self.marker_style) + .draw(target) + .unwrap(); + } + + fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> bool { + if let Event::Touch(ev) = ev { + let p = ev.eg_point(); + + if self.bounding_box().contains(p) && ev.ev_type == TouchEventType::Down { + self.sliding = true; + } else if ev.ev_type == TouchEventType::Up { + self.sliding = false; + } + + if self.sliding { + if p.x >= self.start_point.x && p.x <= (self.start_point.x + self.length) { + let xmin = self.start_point.x; + let xmax = xmin + self.length; + + self.value = self.min + ((self.max - self.min) * (ev.eg_point().x - xmin)) / (xmax - xmin); + self.draw_init(target); + } else if p.x < self.start_point.x { + self.value = self.min; + self.draw_init(target); + } else { + self.value = self.max; + self.draw_init(target); + } + + return true; + } + } + + false + } + + fn bounding_box(&self) -> Rectangle { + let real_radius = self.marker_radius + self.marker_style.stroke_width as i32; + let top_left = self.start_point + Point::new(-real_radius, -real_radius); + Rectangle::new(top_left, Size::new((self.length + 2 * real_radius) as u32, 2 * real_radius as u32)) + } + +} \ No newline at end of file diff --git a/fw16-epd-main/src/gui.rs b/fw16-epd-main/src/gui.rs index 5b1ad3b..abcd10f 100644 --- a/fw16-epd-main/src/gui.rs +++ b/fw16-epd-main/src/gui.rs @@ -2,10 +2,11 @@ use defmt::debug; use embedded_graphics::geometry::AnchorPoint; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{Line, Rectangle}; +use embedded_graphics::primitives::{Line, PrimitiveStyle, Rectangle}; use fw16_epd_gui::draw_target::EpdDrawTarget; use fw16_epd_gui::element::button::Button; use fw16_epd_gui::element::{Gui, DEFAULT_PRIMITIVE_STYLE}; +use fw16_epd_gui::element::slider::Slider; use fw16_epd_program_interface::{RefreshBlockMode, SafeOption, Event, TouchEventType}; use crate::{next_event, set_touch_enabled}; @@ -84,21 +85,36 @@ impl Gui for MainPage { struct ScratchpadPage { exit_button: Button<'static>, clear_button: Button<'static>, + toggle_button: Button<'static>, + slider: Slider, + eraser: bool, prev_pos: Option, } impl ScratchpadPage { fn new() -> Self { let exit_button = Button::with_default_style_auto_sized(Point::new(10, 416 - 10 - 20), "Exit", true); + let next_pos = exit_button - .rect() + .bounding_box() .translate(Point::new(10, 0)) .anchor_point(AnchorPoint::TopRight); let clear_button = Button::with_default_style_auto_sized(next_pos, "Clear", true); + let next_pos = clear_button + .bounding_box() + .translate(Point::new(10, 0)) + .anchor_point(AnchorPoint::TopRight); + let toggle_button = Button::with_default_style_auto_sized(next_pos, "Eraser", false); + + let slider = Slider::with_default_style(Point::new(20, 20), 100, 1, 10, 2); + Self { exit_button, clear_button, + toggle_button, + slider, + eraser: false, prev_pos: None, } } @@ -110,10 +126,13 @@ impl Gui for ScratchpadPage { fn draw_init(&self, draw_target: &mut EpdDrawTarget) { self.exit_button.draw_init(draw_target); self.clear_button.draw_init(draw_target); + self.toggle_button.draw_init(draw_target); + self.slider.draw_init(draw_target); } fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { let mut refresh: Option = None; + let mut handle_drawing: bool = true; let e = self.exit_button.tick(draw_target, ev); if e.clicked { @@ -134,16 +153,32 @@ impl Gui for ScratchpadPage { refresh = Some(RefreshBlockMode::BlockAcknowledge); } + let t = self.toggle_button.tick(draw_target, ev); + if t.clicked { + self.eraser = !self.eraser; + self.toggle_button.label = if self.eraser { "Pen" } else { "Eraser" }; + refresh = Some(RefreshBlockMode::NonBlocking); + } + if t.needs_refresh { + refresh = Some(RefreshBlockMode::BlockAcknowledge); + } + + if self.slider.tick(draw_target, ev) { + refresh = Some(RefreshBlockMode::NonBlocking); + debug!("stroke width = {}", self.slider.value); + handle_drawing = false; + } + if let Event::Touch(ev) = ev { - if matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { + if handle_drawing && matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { if let Some(prev) = self.prev_pos { + let style = PrimitiveStyle::with_stroke(BinaryColor::from(!self.eraser), self.slider.value as u32); Line::new(prev, ev.eg_point()) - .into_styled(DEFAULT_PRIMITIVE_STYLE) + .into_styled(style) .draw(draw_target) .unwrap(); - self.exit_button.draw_init(draw_target); - self.clear_button.draw_init(draw_target); + self.draw_init(draw_target); if refresh.is_none() { refresh = Some(RefreshBlockMode::NonBlocking); -- 2.51.2