Skip to main content

rlvgl_widgets/
checkbox.rs

1//! Binary checkbox widget.
2use alloc::string::String;
3use rlvgl_core::draw::{draw_widget_bg, fill_rounded_rect};
4use rlvgl_core::event::Event;
5use rlvgl_core::font::{FontMetrics, WidgetFont, shape_text_ltr};
6use rlvgl_core::renderer::{ClipRenderer, Renderer};
7use rlvgl_core::style::Style;
8use rlvgl_core::widget::{Color, Rect, Widget};
9
10/// Standard checkbox widget with label text.
11pub struct Checkbox {
12    bounds: Rect,
13    text: String,
14    /// Visual styling for the checkbox box and label.
15    pub style: Style,
16    /// Color used when rendering the label text.
17    pub text_color: Color,
18    /// Color of the check mark when selected.
19    pub check_color: Color,
20    checked: bool,
21    /// Font assignment for this widget (FONT-00 §5); resolves to `FONT_6X10`
22    /// when unset.
23    font: WidgetFont,
24}
25
26impl Checkbox {
27    /// Create a new checkbox.
28    pub fn new(text: impl Into<String>, bounds: Rect) -> Self {
29        Self {
30            bounds,
31            text: text.into(),
32            style: Style::default(),
33            text_color: Color(0, 0, 0, 255),
34            check_color: Color(0, 0, 0, 255),
35            checked: false,
36            font: WidgetFont::new(),
37        }
38    }
39
40    /// Return whether the checkbox is currently checked.
41    pub fn is_checked(&self) -> bool {
42        self.checked
43    }
44
45    /// Set the checked state programmatically.
46    pub fn set_checked(&mut self, value: bool) {
47        self.checked = value;
48    }
49
50    /// Assign the font used to render this widget (FONT-00 §5); resolves to
51    /// `FONT_6X10` when unset.
52    pub fn set_font(&mut self, font: &'static dyn FontMetrics) {
53        self.font.set(font);
54    }
55}
56
57impl Widget for Checkbox {
58    fn bounds(&self) -> Rect {
59        self.bounds
60    }
61
62    fn widget_font_mut(&mut self) -> Option<&mut WidgetFont> {
63        Some(&mut self.font)
64    }
65
66    fn draw(&self, renderer: &mut dyn Renderer) {
67        let a = self.style.alpha;
68        let r = self.style.radius;
69        // Draw background
70        draw_widget_bg(renderer, self.bounds, &self.style);
71
72        // Draw check box square at the left side
73        let square_size = 10;
74        let box_rect = Rect {
75            x: self.bounds.x,
76            y: self.bounds.y,
77            width: square_size,
78            height: square_size,
79        };
80        fill_rounded_rect(renderer, box_rect, self.style.border_color.with_alpha(a), r);
81
82        if self.checked {
83            let inner = Rect {
84                x: box_rect.x + 2,
85                y: box_rect.y + 2,
86                width: box_rect.width - 4,
87                height: box_rect.height - 4,
88            };
89            fill_rounded_rect(renderer, inner, self.check_color.with_alpha(a), r);
90        }
91
92        // Draw label text to the right of the box with baseline at the bottom
93        let text_origin = (
94            self.bounds.x + square_size + 4,
95            self.bounds.y + self.bounds.height,
96        );
97        let clip = Rect {
98            x: text_origin.0,
99            y: self.bounds.y,
100            width: self.bounds.width - (square_size + 4),
101            height: self.bounds.height,
102        };
103        let font = self.font.resolve();
104        let shaped = shape_text_ltr(font, &self.text, text_origin, 0);
105        let mut clipped = ClipRenderer::new(renderer, clip);
106        clipped.draw_text_shaped(&shaped, (0, 0), self.text_color.with_alpha(a));
107    }
108
109    /// Toggle the checked state when clicked.
110    fn handle_event(&mut self, event: &Event) -> bool {
111        if let Event::PressRelease { x, y } = event {
112            let inside = *x >= self.bounds.x
113                && *x < self.bounds.x + self.bounds.width
114                && *y >= self.bounds.y
115                && *y < self.bounds.y + self.bounds.height;
116            if inside {
117                self.checked = !self.checked;
118                return true;
119            }
120        }
121        false
122    }
123}