rlvgl_ui/
button_matrix.rs1use alloc::{boxed::Box, string::String};
8use rlvgl_core::{
9 event::Event,
10 font::{FontMetrics, WidgetFont},
11 renderer::Renderer,
12 widget::{Color, Rect, Widget},
13};
14use rlvgl_widgets::button_matrix::ButtonMatrix as BaseButtonMatrix;
15
16use crate::theme::{ColorScheme, ComponentSize, Theme, Variant};
17
18pub use rlvgl_widgets::button_matrix::{BUTTON_NONE, ButtonId, ButtonMatrixControl};
19
20type ActivateHandler = dyn FnMut(ButtonId, &str);
22
23pub struct ButtonMatrix {
25 inner: BaseButtonMatrix,
26 on_activate: Option<Box<ActivateHandler>>,
27}
28
29impl ButtonMatrix {
30 pub fn new(bounds: Rect) -> Self {
32 Self {
33 inner: BaseButtonMatrix::new(bounds),
34 on_activate: None,
35 }
36 }
37
38 pub fn with_map(mut self, map: &[&str]) -> Self {
40 self.inner.set_map(map);
41 self
42 }
43
44 pub fn set_map(&mut self, map: &[&str]) {
46 self.inner.set_map(map);
47 }
48
49 pub fn button_count(&self) -> usize {
51 self.inner.button_count()
52 }
53
54 pub fn button_text(&self, id: ButtonId) -> Option<&str> {
56 self.inner.button_text(id)
57 }
58
59 pub fn on_activate<F: FnMut(ButtonId, &str) + 'static>(mut self, handler: F) -> Self {
61 self.on_activate = Some(Box::new(handler));
62 self
63 }
64
65 pub fn set_selected_button(&mut self, id: ButtonId) {
67 self.inner.set_selected_button(id);
68 }
69
70 pub fn selected_button(&self) -> ButtonId {
72 self.inner.selected_button()
73 }
74
75 pub fn navigate_next(&mut self) {
77 self.inner.navigate_next();
78 }
79
80 pub fn navigate_prev(&mut self) {
82 self.inner.navigate_prev();
83 }
84
85 pub fn activate_selected(&mut self) {
87 self.inner.activate_selected();
88 self.emit_activate(self.inner.selected_button());
89 }
90
91 pub fn set_button_control(&mut self, id: ButtonId, control: ButtonMatrixControl) {
93 self.inner.set_button_control(id, control);
94 }
95
96 pub fn clear_button_control(&mut self, id: ButtonId, control: ButtonMatrixControl) {
98 self.inner.clear_button_control(id, control);
99 }
100
101 pub fn set_all_controls(&mut self, control: ButtonMatrixControl) {
103 self.inner.set_all_controls(control);
104 }
105
106 pub fn clear_all_controls(&mut self, control: ButtonMatrixControl) {
108 self.inner.clear_all_controls(control);
109 }
110
111 pub fn set_one_checked(&mut self, enabled: bool) {
113 self.inner.set_one_checked(enabled);
114 }
115
116 pub fn one_checked(&self) -> bool {
118 self.inner.one_checked()
119 }
120
121 pub fn set_button_checked(&mut self, id: ButtonId, checked: bool) {
123 self.inner.set_button_checked(id, checked);
124 }
125
126 pub fn button_checked(&self, id: ButtonId) -> bool {
128 self.inner.button_checked(id)
129 }
130
131 pub fn set_button_width(&mut self, id: ButtonId, width: u8) {
133 self.inner.set_button_width(id, width);
134 }
135
136 pub fn text_color(mut self, color: Color) -> Self {
138 self.inner.text_color = color;
139 self
140 }
141
142 pub fn button_color(mut self, color: Color) -> Self {
144 self.inner.button_color = color;
145 self
146 }
147
148 pub fn pressed_color(mut self, color: Color) -> Self {
150 self.inner.pressed_color = color;
151 self
152 }
153
154 pub fn checked_color(mut self, color: Color) -> Self {
156 self.inner.checked_color = color;
157 self
158 }
159
160 pub fn set_font(&mut self, font: &'static dyn FontMetrics) {
162 self.inner.set_font(font);
163 }
164
165 pub fn themed(
167 mut self,
168 theme: &Theme,
169 scheme: ColorScheme,
170 variant: Variant,
171 size: ComponentSize,
172 ) -> Self {
173 let resolved = theme.component_style(scheme, variant, size);
174 let colors = theme.scheme(scheme);
175 self.inner.style = resolved.style;
176 self.inner.text_color = resolved.text_color;
177 self.inner.button_color = resolved.style.bg_color;
178 self.inner.checked_color = resolved.accent_color;
179 self.inner.pressed_color = colors.muted;
180 self.inner.disabled_color = colors.border.with_alpha(128);
181 self
182 }
183
184 pub fn style(&self) -> &rlvgl_core::style::Style {
186 &self.inner.style
187 }
188
189 pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
191 &mut self.inner.style
192 }
193
194 fn emit_activate(&mut self, id: ButtonId) {
195 if id == BUTTON_NONE {
196 return;
197 }
198 let Some(text) = self.inner.button_text(id).map(String::from) else {
199 return;
200 };
201 if let Some(cb) = self.on_activate.as_mut() {
202 cb(id, &text);
203 }
204 }
205}
206
207impl Widget for ButtonMatrix {
208 fn bounds(&self) -> Rect {
209 self.inner.bounds()
210 }
211
212 fn widget_font_mut(&mut self) -> Option<&mut WidgetFont> {
213 self.inner.widget_font_mut()
214 }
215
216 fn set_bounds(&mut self, bounds: Rect) {
217 self.inner.set_bounds(bounds);
218 }
219
220 fn draw(&self, renderer: &mut dyn Renderer) {
221 self.inner.draw(renderer);
222 }
223
224 fn handle_event(&mut self, event: &Event) -> bool {
225 let is_release = matches!(event, Event::PressRelease { .. });
226 let handled = self.inner.handle_event(event);
227 if handled && is_release {
228 self.emit_activate(self.inner.selected_button());
229 }
230 handled
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use alloc::rc::Rc;
238 use core::cell::Cell;
239
240 #[test]
241 fn button_matrix_activation_callback_reports_id() {
242 let activated = Rc::new(Cell::new(BUTTON_NONE));
243 let flag = activated.clone();
244 let mut matrix = ButtonMatrix::new(rect(0, 0, 100, 40))
245 .with_map(&["A", "B"])
246 .on_activate(move |id, _| flag.set(id));
247
248 assert!(matrix.handle_event(&Event::PressDown { x: 5, y: 5 }));
249 assert!(matrix.handle_event(&Event::PressRelease { x: 5, y: 5 }));
250
251 assert_eq!(activated.get(), ButtonId(0));
252 }
253
254 #[test]
255 fn button_matrix_themed_sets_state_colors() {
256 let theme = Theme::material_light();
257 let matrix = ButtonMatrix::new(rect(0, 0, 100, 40)).themed(
258 &theme,
259 ColorScheme::Warning,
260 Variant::Outline,
261 ComponentSize::Lg,
262 );
263
264 assert_eq!(matrix.style().border_width, 2);
265 assert_eq!(
266 matrix.inner.checked_color,
267 theme.scheme(ColorScheme::Warning).solid
268 );
269 }
270
271 fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
272 Rect {
273 x,
274 y,
275 width,
276 height,
277 }
278 }
279}