1use super::Widget;
19use alloc::string::String;
20use core::marker::PhantomData;
21use embedded_graphics::{
22 mono_font::MonoFont, pixelcolor::PixelColor, prelude::*, primitives::Rectangle, text::Alignment,
23};
24use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
25use zest_theme::{ButtonCatalog, ButtonClass, Status, Theme};
26
27pub struct ImageButton<'a, C: PixelColor, M: Clone> {
30 rect: Rectangle,
31 pixels: &'a [C],
32 image_size: Size,
33 label: Option<String>,
34 on_press: Option<M>,
35 pressed: bool,
36 class: ButtonClass,
37 font_override: Option<&'a MonoFont<'a>>,
38 width: Length,
39 height: Length,
40 _color: PhantomData<C>,
41}
42
43impl<'a, C: PixelColor, M: Clone> ImageButton<'a, C, M> {
44 pub fn new(pixels: &'a [C], image_size: Size) -> Self {
49 Self {
50 rect: Rectangle::zero(),
51 pixels,
52 image_size,
53 label: None,
54 on_press: None,
55 pressed: false,
56 class: ButtonClass::Standard,
57 font_override: None,
58 width: Length::Shrink,
59 height: Length::Shrink,
60 _color: PhantomData,
61 }
62 }
63
64 #[must_use]
66 pub fn width(mut self, width: impl Into<Length>) -> Self {
67 self.width = width.into();
68 self
69 }
70
71 #[must_use]
73 pub fn height(mut self, height: impl Into<Length>) -> Self {
74 self.height = height.into();
75 self
76 }
77
78 #[must_use]
80 pub fn on_press(mut self, msg: M) -> Self {
81 self.on_press = Some(msg);
82 self
83 }
84
85 #[must_use]
88 pub fn on_press_maybe(mut self, msg: Option<M>) -> Self {
89 self.on_press = msg;
90 self
91 }
92
93 #[must_use]
95 pub fn label(mut self, label: impl Into<String>) -> Self {
96 self.label = Some(label.into());
97 self
98 }
99
100 #[must_use]
103 pub fn class(mut self, class: ButtonClass) -> Self {
104 self.class = class;
105 self
106 }
107
108 #[must_use]
110 pub fn font(mut self, font: &'a MonoFont<'a>) -> Self {
111 self.font_override = Some(font);
112 self
113 }
114
115 pub fn is_enabled(&self) -> bool {
118 self.on_press.is_some()
119 }
120
121 fn status(&self) -> Status {
122 if !self.is_enabled() {
123 Status::Disabled
124 } else if self.pressed {
125 Status::Pressed
126 } else {
127 Status::Active
128 }
129 }
130
131 fn resolved_font<'t>(&'t self, theme: &'t Theme<'a, C>) -> &'t MonoFont<'a> {
132 self.font_override.unwrap_or(theme.default_font())
133 }
134
135 fn hit_test(&self, point: Point) -> bool {
136 let top_left = self.rect.top_left;
137 let bot_right =
138 top_left + Point::new(self.rect.size.width as i32, self.rect.size.height as i32);
139 point.x >= top_left.x
140 && point.x < bot_right.x
141 && point.y >= top_left.y
142 && point.y < bot_right.y
143 }
144}
145
146impl<'a, C: PixelColor, M: Clone> Widget<C, M> for ImageButton<'a, C, M> {
147 fn measure(&mut self, constraints: Constraints) -> Size {
148 let label_h: u32 = if self.label.is_some() { 20 } else { 0 };
151 let intrinsic = Size::new(
152 self.image_size.width + 16,
153 self.image_size.height + label_h + 16,
154 );
155 let w = self.width.resolve(intrinsic.width, constraints.max.width);
156 let h = self
157 .height
158 .resolve(intrinsic.height, constraints.max.height);
159 constraints.clamp(Size::new(w, h))
160 }
161
162 fn preferred_size(&self) -> (Length, Length) {
163 (self.width, self.height)
164 }
165
166 fn arrange(&mut self, rect: Rectangle) {
167 self.rect = rect;
168 }
169
170 fn rect(&self) -> Rectangle {
171 self.rect
172 }
173
174 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
175 if !self.is_enabled() {
176 return None;
177 }
178 if !self.hit_test(point) {
179 return None;
180 }
181 match phase {
182 TouchPhase::Down => {
183 self.pressed = true;
184 None
185 }
186 TouchPhase::Up => {
187 if self.pressed {
188 self.on_press.clone()
189 } else {
190 None
191 }
192 }
193 TouchPhase::Moved => None,
194 }
195 }
196
197 fn mark_pressed(&mut self, point: Point) {
198 if self.is_enabled() && self.hit_test(point) {
199 self.pressed = true;
200 }
201 }
202
203 fn draw<'t>(
204 &self,
205 renderer: &mut dyn Renderer<C>,
206 theme: &Theme<'t, C>,
207 ) -> Result<(), RenderError> {
208 let appearance = theme.button(self.class, self.status());
209 let font = self.resolved_font(theme);
210
211 if let Some(bg) = appearance.background {
212 renderer.fill_rect(self.rect, bg)?;
213 }
214 if let Some(border) = appearance.border {
215 renderer.stroke_rect(self.rect, border)?;
216 }
217
218 let label_h = if self.label.is_some() {
220 font.character_size.height as i32 + 4
221 } else {
222 0
223 };
224 let content_h = self.image_size.height as i32 + label_h;
225 let avail_h = self.rect.size.height as i32;
226 let top = self.rect.top_left.y + (avail_h - content_h).max(0) / 2;
227
228 let dx = (self.rect.size.width as i32 - self.image_size.width as i32).max(0) / 2;
230 let origin = Point::new(self.rect.top_left.x + dx, top);
231 renderer.draw_image(origin, self.image_size, self.pixels)?;
232
233 if let Some(label) = &self.label {
234 let center_x = self.rect.top_left.x + self.rect.size.width as i32 / 2;
235 let baseline_y =
236 top + self.image_size.height as i32 + font.character_size.height as i32;
237 renderer.draw_text(
238 label,
239 Point::new(center_x, baseline_y),
240 font,
241 appearance.text,
242 Alignment::Center,
243 )?;
244 }
245
246 Ok(())
247 }
248}