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
15/// Extension trait adding a fluent `on_click` method to widgets.
16pub trait OnClick {
17    /// Attach a click handler executed when the widget is released.
18    fn on_click<F: FnMut(&mut Self) + 'static>(self, handler: F) -> Self;
19}
20
21impl OnClick for BaseButton {
22    fn on_click<F: FnMut(&mut Self) + 'static>(mut self, handler: F) -> Self {
23        self.set_on_click(handler);
24        self
25    }
26}
27
28/// Slider widget with change callback support.
29pub struct Slider {
30    inner: BaseSlider,
31    on_change: Option<Box<dyn FnMut(i32)>>,
32}
33
34impl Slider {
35    /// Create a new slider with the provided range.
36    pub fn new(bounds: Rect, min: i32, max: i32) -> Self {
37        let inner = BaseSlider::new(bounds, min, max);
38        Self {
39            inner,
40            on_change: None,
41        }
42    }
43
44    /// Register a callback invoked whenever the slider value changes.
45    pub fn on_change<F: FnMut(i32) + 'static>(mut self, handler: F) -> Self {
46        self.on_change = Some(Box::new(handler));
47        self
48    }
49
50    /// Current slider value.
51    pub fn value(&self) -> i32 {
52        self.inner.value()
53    }
54
55    /// Set the slider value, clamped to the valid range.
56    pub fn set_value(&mut self, val: i32) {
57        self.inner.set_value(val);
58    }
59
60    /// Immutable access to the slider style.
61    pub fn style(&self) -> &rlvgl_core::style::Style {
62        &self.inner.style
63    }
64
65    /// Mutable access to the slider style.
66    pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
67        &mut self.inner.style
68    }
69}
70
71impl Widget for Slider {
72    fn bounds(&self) -> Rect {
73        self.inner.bounds()
74    }
75
76    fn draw(&self, renderer: &mut dyn Renderer) {
77        self.inner.draw(renderer);
78    }
79
80    fn handle_event(&mut self, event: &Event) -> bool {
81        let before = self.inner.value();
82        let handled = self.inner.handle_event(event);
83        let after = self.inner.value();
84        if !handled || after == before {
85            return handled;
86        }
87        if let Some(cb) = self.on_change.as_mut() {
88            cb(after);
89        }
90        handled
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use alloc::rc::Rc;
98    use core::cell::Cell;
99    use rlvgl_core::{event::Event, widget::Rect};
100    use rlvgl_widgets::button::Button;
101
102    #[test]
103    fn button_on_click_executes() {
104        let bounds = Rect {
105            x: 0,
106            y: 0,
107            width: 20,
108            height: 20,
109        };
110        let clicked = Rc::new(Cell::new(false));
111        let c = clicked.clone();
112        let mut btn = Button::new("ok", bounds).on_click(move |_| c.set(true));
113        let event = Event::PressRelease { x: 5, y: 5 };
114        btn.handle_event(&event);
115        assert!(clicked.get());
116    }
117
118    #[test]
119    fn slider_on_change_executes() {
120        let bounds = Rect {
121            x: 0,
122            y: 0,
123            width: 100,
124            height: 10,
125        };
126        let value = Rc::new(Cell::new(0));
127        let v = value.clone();
128        let mut slider = Slider::new(bounds, 0, 10).on_change(move |x| v.set(x));
129        let event = Event::PressRelease { x: 50, y: 5 };
130        slider.handle_event(&event);
131        assert_ne!(value.get(), 0);
132    }
133}