firmware for my Touchscreen E-Paper Input Module for Framework Laptop 16
1use embedded_graphics::prelude::*;
2use embedded_graphics::mono_font::MonoTextStyle;
3use embedded_graphics::pixelcolor::BinaryColor;
4use embedded_graphics::primitives::{CornerRadii, PrimitiveStyle, Rectangle, RoundedRectangle};
5use embedded_graphics::text::{Alignment, Baseline, Text, TextStyle, TextStyleBuilder};
6use embedded_graphics::text::renderer::TextRenderer;
7use fugit::ExtU32;
8use eepy_sys::input_common::{Event, TouchEventType};
9use eepy_sys::misc::{now, Instant};
10use crate::draw_target::EpdDrawTarget;
11use crate::element::{Gui, DEFAULT_PRIMITIVE_STYLE, DEFAULT_TEXT_STYLE};
12
13const CENTRE_STYLE: TextStyle = TextStyleBuilder::new()
14 .alignment(Alignment::Center)
15 .baseline(Baseline::Middle)
16 .build();
17
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
20pub struct ButtonOutput {
21 pub clicked: bool,
22 pub long_clicked: bool,
23 pub needs_refresh: bool,
24}
25
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27#[derive(Debug)]
28pub struct Button<'a> {
29 pub rect: RoundedRectangle,
30 pub label: &'a str,
31 pub rect_style: PrimitiveStyle<BinaryColor>,
32 pub char_style: MonoTextStyle<'a, BinaryColor>,
33 pub touch_feedback: bool,
34 pub touch_feedback_immediate_release: bool,
35
36 click_begin_time: Option<Instant>,
37 inverted: bool,
38 should_uninvert: bool,
39}
40
41impl<'a> Button<'a> {
42 pub fn new(rect: RoundedRectangle, label: &'a str, rect_style: PrimitiveStyle<BinaryColor>, char_style: MonoTextStyle<'a, BinaryColor>, touch_feedback: bool, touch_feedback_immediate_release: bool) -> Self {
43 Self {
44 rect,
45 label,
46 rect_style,
47 char_style,
48 touch_feedback,
49 touch_feedback_immediate_release,
50 click_begin_time: None,
51 inverted: false,
52 should_uninvert: false,
53 }
54 }
55
56 pub fn auto_sized(top_left: Point, corner_radii: CornerRadii, label: &'a str, rect_style: PrimitiveStyle<BinaryColor>, char_style: MonoTextStyle<'a, BinaryColor>, touch_feedback: bool, touch_feedback_immediate_release: bool) -> Self {
57 let size = Size::new((char_style.font.character_size.width + char_style.font.character_spacing) * (label.len() as u32 + 1), char_style.line_height());
58 Self {
59 rect: RoundedRectangle::new(Rectangle::new(top_left, size), corner_radii),
60 label,
61 rect_style,
62 char_style,
63 touch_feedback,
64 touch_feedback_immediate_release,
65 click_begin_time: None,
66 inverted: false,
67 should_uninvert: false,
68 }
69 }
70
71 pub fn with_default_style(rect: Rectangle, label: &'a str, touch_feedback_immediate_release: bool) -> Self {
72 Self {
73 rect: RoundedRectangle::new(rect, CornerRadii::new(Size::new(3, 3))),
74 label,
75 rect_style: DEFAULT_PRIMITIVE_STYLE,
76 char_style: DEFAULT_TEXT_STYLE,
77 touch_feedback: true,
78 touch_feedback_immediate_release,
79 click_begin_time: None,
80 inverted: false,
81 should_uninvert: false,
82 }
83 }
84
85 pub fn with_default_style_auto_sized(top_left: Point, label: &'a str, touch_feedback_immediate_release: bool) -> Self {
86 Self::auto_sized(
87 top_left,
88 CornerRadii::new(Size::new(3, 3)),
89 label,
90 DEFAULT_PRIMITIVE_STYLE,
91 DEFAULT_TEXT_STYLE,
92 true,
93 touch_feedback_immediate_release,
94 )
95 }
96
97 fn invert(&mut self) {
98 self.rect_style.fill_color = self.rect_style.fill_color.map(|c| c.invert());
99 self.char_style.text_color = self.char_style.text_color.map(|c| c.invert());
100 self.inverted = !self.inverted;
101 }
102}
103
104impl<'a> Gui for Button<'a> {
105 type Output = ButtonOutput;
106
107 fn draw_init(&self, target: &mut EpdDrawTarget) {
108 self.rect
109 .into_styled(self.rect_style)
110 .draw(target)
111 .unwrap();
112
113 Text::with_text_style(
114 self.label,
115 self.rect.bounding_box().center(),
116 self.char_style,
117 CENTRE_STYLE
118 )
119 .draw(target)
120 .unwrap();
121 }
122
123 fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output {
124 let mut ret = ButtonOutput::default();
125
126 if let Event::Touch(ev) = ev {
127 if self.rect.contains(ev.eg_point()) {
128 match (self.click_begin_time, ev.ev_type) {
129 (None, TouchEventType::Down) => {
130 self.click_begin_time = Some(now());
131 if self.touch_feedback {
132 self.invert();
133 self.draw_init(target);
134 ret.needs_refresh = true;
135 }
136 },
137 (Some(t), TouchEventType::Up) => {
138 self.click_begin_time = None;
139 if self.inverted {
140 if self.touch_feedback_immediate_release {
141 self.invert();
142 self.draw_init(target);
143 ret.needs_refresh = true;
144 } else {
145 self.should_uninvert = true;
146 }
147 }
148
149 ret.clicked = true;
150 if now().checked_duration_since(t).unwrap() > 500.millis::<1, 1_000_000>() {
151 ret.long_clicked = true;
152 }
153 },
154 _ => {},
155 }
156 } else {
157 self.click_begin_time = None;
158 if self.inverted {
159 if self.touch_feedback_immediate_release {
160 self.invert();
161 self.draw_init(target);
162 ret.needs_refresh = true;
163 } else {
164 self.should_uninvert = true;
165 }
166 }
167 }
168 }
169
170 if ev == Event::RefreshFinished && self.should_uninvert {
171 self.should_uninvert = false;
172 self.invert();
173 self.draw_init(target);
174 ret.needs_refresh = true;
175 }
176
177 ret
178 }
179
180 fn bounding_box(&self) -> Rectangle {
181 self.rect.into_styled(self.rect_style).bounding_box()
182 }
183}