Skip to main content

rlvgl_ui/
event.rs

1// SPDX-License-Identifier: MIT
2//! Event hook helpers for rlvgl-ui widgets.
3//!
4//! Bridges [`Event`] types from `rlvgl-core` with components from
5//! [`rlvgl_widgets`] using builder-style `on_click` and `on_change` APIs.
6
7use alloc::boxed::Box;
8use rlvgl_core::{
9    event::Event,
10    renderer::Renderer,
11    widget::{Rect, Widget},
12};
13use rlvgl_widgets::{button::Button as BaseButton, slider::Slider as BaseSlider};
14
15use crate::theme::{ColorScheme, ComponentSize, Theme, Variant};
16
17/// Extension trait adding a fluent `on_click` method to widgets.
18pub trait OnClick {
19    /// Attach a click handler executed when the widget is released.
20    fn on_click<F: FnMut(&mut Self) + 'static>(self, handler: F) -> Self;
21}
22
23impl OnClick for BaseButton {
24    fn on_click<F: FnMut(&mut Self) + 'static>(mut self, handler: F) -> Self {
25        self.set_on_click(handler);
26        self
27    }
28}
29
30/// Slider widget with change callback support.
31pub struct Slider {
32    inner: BaseSlider,
33    on_change: Option<Box<dyn FnMut(i32)>>,
34}
35
36impl Slider {
37    /// Create a new slider with the provided range.
38    pub fn new(bounds: Rect, min: i32, max: i32) -> Self {
39        let inner = BaseSlider::new(bounds, min, max);
40        Self {
41            inner,
42            on_change: None,
43        }
44    }
45
46    /// Register a callback invoked whenever the slider value changes.
47    pub fn on_change<F: FnMut(i32) + 'static>(mut self, handler: F) -> Self {
48        self.on_change = Some(Box::new(handler));
49        self
50    }
51
52    /// Set the current slider value and return the widget.
53    pub fn with_value(mut self, val: i32) -> Self {
54        self.inner.set_value(val);
55        self
56    }
57
58    /// Set the knob color and return the widget.
59    pub fn knob_color(mut self, color: rlvgl_core::widget::Color) -> Self {
60        self.inner.knob_color = color;
61        self
62    }
63
64    /// Return the knob color.
65    pub fn knob_color_value(&self) -> rlvgl_core::widget::Color {
66        self.inner.knob_color
67    }
68
69    /// Set the knob color.
70    pub fn set_knob_color(&mut self, color: rlvgl_core::widget::Color) {
71        self.inner.knob_color = color;
72    }
73
74    /// Apply a themed style and knob color.
75    pub fn themed(
76        mut self,
77        theme: &Theme,
78        scheme: ColorScheme,
79        variant: Variant,
80        size: ComponentSize,
81    ) -> Self {
82        let resolved = theme.component_style(scheme, variant, size);
83        self.inner.style = resolved.style;
84        self.inner.knob_color = resolved.accent_color;
85        self
86    }
87
88    /// Current slider value.
89    pub fn value(&self) -> i32 {
90        self.inner.value()
91    }
92
93    /// Set the slider value, clamped to the valid range.
94    pub fn set_value(&mut self, val: i32) {
95        self.inner.set_value(val);
96    }
97
98    /// Immutable access to the slider style.
99    pub fn style(&self) -> &rlvgl_core::style::Style {
100        &self.inner.style
101    }
102
103    /// Mutable access to the slider style.
104    pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
105        &mut self.inner.style
106    }
107}
108
109impl Widget for Slider {
110    fn bounds(&self) -> Rect {
111        self.inner.bounds()
112    }
113
114    fn draw(&self, renderer: &mut dyn Renderer) {
115        self.inner.draw(renderer);
116    }
117
118    fn handle_event(&mut self, event: &Event) -> bool {
119        let before = self.inner.value();
120        let handled = self.inner.handle_event(event);
121        let after = self.inner.value();
122        if !handled || after == before {
123            return handled;
124        }
125        if let Some(cb) = self.on_change.as_mut() {
126            cb(after);
127        }
128        handled
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use alloc::rc::Rc;
136    use core::cell::Cell;
137    use rlvgl_core::{event::Event, widget::Rect};
138    use rlvgl_widgets::button::Button;
139
140    #[test]
141    fn button_on_click_executes() {
142        let bounds = Rect {
143            x: 0,
144            y: 0,
145            width: 20,
146            height: 20,
147        };
148        let clicked = Rc::new(Cell::new(false));
149        let c = clicked.clone();
150        let mut btn = Button::new("ok", bounds).on_click(move |_| c.set(true));
151        let event = Event::PressRelease { x: 5, y: 5 };
152        btn.handle_event(&event);
153        assert!(clicked.get());
154    }
155
156    #[test]
157    fn slider_on_change_executes() {
158        let bounds = Rect {
159            x: 0,
160            y: 0,
161            width: 100,
162            height: 10,
163        };
164        let value = Rc::new(Cell::new(0));
165        let v = value.clone();
166        let mut slider = Slider::new(bounds, 0, 10).on_change(move |x| v.set(x));
167        let event = Event::PressRelease { x: 50, y: 5 };
168        slider.handle_event(&event);
169        assert_ne!(value.get(), 0);
170    }
171
172    #[test]
173    fn slider_themed_sets_style_and_knob() {
174        let theme = Theme::material_light();
175        let slider = Slider::new(
176            Rect {
177                x: 0,
178                y: 0,
179                width: 100,
180                height: 10,
181            },
182            0,
183            10,
184        )
185        .themed(
186            &theme,
187            ColorScheme::Primary,
188            Variant::Outline,
189            ComponentSize::Lg,
190        );
191
192        assert_eq!(
193            slider.knob_color_value(),
194            theme.scheme(ColorScheme::Primary).solid
195        );
196        assert_eq!(slider.style().border_width, 2);
197    }
198}