use alloc::{boxed::Box, string::String};
use rlvgl_core::{
event::Event,
font::{FontMetrics, WidgetFont},
renderer::Renderer,
widget::{Color, Rect, Widget},
};
use rlvgl_widgets::button_matrix::ButtonMatrix as BaseButtonMatrix;
use crate::theme::{ColorScheme, ComponentSize, Theme, Variant};
pub use rlvgl_widgets::button_matrix::{BUTTON_NONE, ButtonId, ButtonMatrixControl};
type ActivateHandler = dyn FnMut(ButtonId, &str);
pub struct ButtonMatrix {
inner: BaseButtonMatrix,
on_activate: Option<Box<ActivateHandler>>,
}
impl ButtonMatrix {
pub fn new(bounds: Rect) -> Self {
Self {
inner: BaseButtonMatrix::new(bounds),
on_activate: None,
}
}
pub fn with_map(mut self, map: &[&str]) -> Self {
self.inner.set_map(map);
self
}
pub fn set_map(&mut self, map: &[&str]) {
self.inner.set_map(map);
}
pub fn button_count(&self) -> usize {
self.inner.button_count()
}
pub fn button_text(&self, id: ButtonId) -> Option<&str> {
self.inner.button_text(id)
}
pub fn on_activate<F: FnMut(ButtonId, &str) + 'static>(mut self, handler: F) -> Self {
self.on_activate = Some(Box::new(handler));
self
}
pub fn set_selected_button(&mut self, id: ButtonId) {
self.inner.set_selected_button(id);
}
pub fn selected_button(&self) -> ButtonId {
self.inner.selected_button()
}
pub fn navigate_next(&mut self) {
self.inner.navigate_next();
}
pub fn navigate_prev(&mut self) {
self.inner.navigate_prev();
}
pub fn activate_selected(&mut self) {
self.inner.activate_selected();
self.emit_activate(self.inner.selected_button());
}
pub fn set_button_control(&mut self, id: ButtonId, control: ButtonMatrixControl) {
self.inner.set_button_control(id, control);
}
pub fn clear_button_control(&mut self, id: ButtonId, control: ButtonMatrixControl) {
self.inner.clear_button_control(id, control);
}
pub fn set_all_controls(&mut self, control: ButtonMatrixControl) {
self.inner.set_all_controls(control);
}
pub fn clear_all_controls(&mut self, control: ButtonMatrixControl) {
self.inner.clear_all_controls(control);
}
pub fn set_one_checked(&mut self, enabled: bool) {
self.inner.set_one_checked(enabled);
}
pub fn one_checked(&self) -> bool {
self.inner.one_checked()
}
pub fn set_button_checked(&mut self, id: ButtonId, checked: bool) {
self.inner.set_button_checked(id, checked);
}
pub fn button_checked(&self, id: ButtonId) -> bool {
self.inner.button_checked(id)
}
pub fn set_button_width(&mut self, id: ButtonId, width: u8) {
self.inner.set_button_width(id, width);
}
pub fn text_color(mut self, color: Color) -> Self {
self.inner.text_color = color;
self
}
pub fn button_color(mut self, color: Color) -> Self {
self.inner.button_color = color;
self
}
pub fn pressed_color(mut self, color: Color) -> Self {
self.inner.pressed_color = color;
self
}
pub fn checked_color(mut self, color: Color) -> Self {
self.inner.checked_color = color;
self
}
pub fn set_font(&mut self, font: &'static dyn FontMetrics) {
self.inner.set_font(font);
}
pub fn themed(
mut self,
theme: &Theme,
scheme: ColorScheme,
variant: Variant,
size: ComponentSize,
) -> Self {
let resolved = theme.component_style(scheme, variant, size);
let colors = theme.scheme(scheme);
self.inner.style = resolved.style;
self.inner.text_color = resolved.text_color;
self.inner.button_color = resolved.style.bg_color;
self.inner.checked_color = resolved.accent_color;
self.inner.pressed_color = colors.muted;
self.inner.disabled_color = colors.border.with_alpha(128);
self
}
pub fn style(&self) -> &rlvgl_core::style::Style {
&self.inner.style
}
pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
&mut self.inner.style
}
fn emit_activate(&mut self, id: ButtonId) {
if id == BUTTON_NONE {
return;
}
let Some(text) = self.inner.button_text(id).map(String::from) else {
return;
};
if let Some(cb) = self.on_activate.as_mut() {
cb(id, &text);
}
}
}
impl Widget for ButtonMatrix {
fn bounds(&self) -> Rect {
self.inner.bounds()
}
fn widget_font_mut(&mut self) -> Option<&mut WidgetFont> {
self.inner.widget_font_mut()
}
fn set_bounds(&mut self, bounds: Rect) {
self.inner.set_bounds(bounds);
}
fn draw(&self, renderer: &mut dyn Renderer) {
self.inner.draw(renderer);
}
fn handle_event(&mut self, event: &Event) -> bool {
let is_release = matches!(event, Event::PressRelease { .. });
let handled = self.inner.handle_event(event);
if handled && is_release {
self.emit_activate(self.inner.selected_button());
}
handled
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::rc::Rc;
use core::cell::Cell;
#[test]
fn button_matrix_activation_callback_reports_id() {
let activated = Rc::new(Cell::new(BUTTON_NONE));
let flag = activated.clone();
let mut matrix = ButtonMatrix::new(rect(0, 0, 100, 40))
.with_map(&["A", "B"])
.on_activate(move |id, _| flag.set(id));
assert!(matrix.handle_event(&Event::PressDown { x: 5, y: 5 }));
assert!(matrix.handle_event(&Event::PressRelease { x: 5, y: 5 }));
assert_eq!(activated.get(), ButtonId(0));
}
#[test]
fn button_matrix_themed_sets_state_colors() {
let theme = Theme::material_light();
let matrix = ButtonMatrix::new(rect(0, 0, 100, 40)).themed(
&theme,
ColorScheme::Warning,
Variant::Outline,
ComponentSize::Lg,
);
assert_eq!(matrix.style().border_width, 2);
assert_eq!(
matrix.inner.checked_color,
theme.scheme(ColorScheme::Warning).solid
);
}
fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
Rect {
x,
y,
width,
height,
}
}
}