Skip to main content

rlvgl_core/
widget.rs

1//! Basic widget traits and geometry types.
2
3use crate::event::Event;
4use crate::renderer::Renderer;
5
6/// Rectangle bounds of a widget.
7///
8/// Coordinates are relative to the parent widget. Width and height are signed
9/// integers to simplify layout calculations.
10///
11/// Used by [`Widget`] implementations to describe layout and passed to
12/// [`Renderer::fill_rect`] when drawing.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Rect {
15    /// X coordinate relative to the parent widget.
16    pub x: i32,
17    /// Y coordinate relative to the parent widget.
18    pub y: i32,
19    /// Width in pixels.
20    pub width: i32,
21    /// Height in pixels.
22    pub height: i32,
23}
24
25impl Rect {
26    /// Axis-aligned bounding box of `self` and `other`.
27    ///
28    /// Mirrors the union semantics of `CommandList::dirty_union` and the
29    /// [`anim`](crate::anim) registry's dirty-rect bookkeeping.
30    pub fn union(self, other: Rect) -> Rect {
31        let x0 = self.x.min(other.x);
32        let y0 = self.y.min(other.y);
33        let x1 = (self.x + self.width).max(other.x + other.width);
34        let y1 = (self.y + self.height).max(other.y + other.height);
35        Rect {
36            x: x0,
37            y: y0,
38            width: x1 - x0,
39            height: y1 - y0,
40        }
41    }
42
43    /// Axis-aligned intersection of `self` and `other`, or `None` when the
44    /// rects are disjoint or the overlap is degenerate (zero/negative area).
45    ///
46    /// Companion to [`union`](Self::union); used by
47    /// [`ClipRenderer`](crate::renderer::ClipRenderer) to fold nested clip
48    /// regions and crop forwarded draws.
49    pub fn intersect(self, other: Rect) -> Option<Rect> {
50        let x0 = self.x.max(other.x);
51        let y0 = self.y.max(other.y);
52        let x1 = (self.x + self.width).min(other.x + other.width);
53        let y1 = (self.y + self.height).min(other.y + other.height);
54        if x1 <= x0 || y1 <= y0 {
55            return None;
56        }
57        Some(Rect {
58            x: x0,
59            y: y0,
60            width: x1 - x0,
61            height: y1 - y0,
62        })
63    }
64
65    /// Integer linear interpolation from `self` toward `to` by `num / den`.
66    ///
67    /// Each field is interpolated as `a + (b - a) * num / den` using `i64`
68    /// intermediates (truncation toward zero). Deterministic — no floats.
69    pub fn lerp(self, to: Rect, num: i32, den: i32) -> Rect {
70        let l = |a: i32, b: i32| lerp_i32(a, b, num, den);
71        Rect {
72            x: l(self.x, to.x),
73            y: l(self.y, to.y),
74            width: l(self.width, to.width),
75            height: l(self.height, to.height),
76        }
77    }
78}
79
80/// Shared integer lerp: `a + (b - a) * num / den` with `i64` intermediates.
81///
82/// `den == 0` returns `b` (treated as "fully arrived").
83pub(crate) fn lerp_i32(a: i32, b: i32, num: i32, den: i32) -> i32 {
84    if den == 0 {
85        return b;
86    }
87    (a as i64 + (b as i64 - a as i64) * num as i64 / den as i64) as i32
88}
89
90/// RGBA color used by the renderer.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct Color(
93    /// Red component in the range `0..=255`.
94    pub u8,
95    /// Green component in the range `0..=255`.
96    pub u8,
97    /// Blue component in the range `0..=255`.
98    pub u8,
99    /// Alpha component in the range `0..=255`.
100    ///
101    /// A value of `255` is fully opaque and `0` is fully transparent.
102    pub u8,
103);
104
105impl Color {
106    /// Convert this color to a packed ARGB8888 integer.
107    ///
108    /// Used by display backends in the
109    /// [`rlvgl-platform`](https://docs.rs/rlvgl-platform) crate.
110    pub fn to_argb8888(self) -> u32 {
111        ((self.3 as u32) << 24) | ((self.0 as u32) << 16) | ((self.1 as u32) << 8) | (self.2 as u32)
112    }
113
114    /// Integer linear interpolation from `self` toward `to` by `num / den`.
115    ///
116    /// Each channel is interpolated as `a + (b - a) * num / den` with
117    /// integer math (truncation toward zero). Deterministic — no floats.
118    /// Used by the [`anim`](crate::anim) pulse adapter.
119    pub fn lerp(self, to: Color, num: i32, den: i32) -> Color {
120        let l = |a: u8, b: u8| lerp_i32(a as i32, b as i32, num, den).clamp(0, 255) as u8;
121        Color(
122            l(self.0, to.0),
123            l(self.1, to.1),
124            l(self.2, to.2),
125            l(self.3, to.3),
126        )
127    }
128
129    /// Return a copy with the alpha channel multiplied by `opacity`.
130    ///
131    /// Both the existing alpha and `opacity` are in `0..=255`.
132    pub fn with_alpha(self, opacity: u8) -> Color {
133        Color(
134            self.0,
135            self.1,
136            self.2,
137            ((self.3 as u16 * opacity as u16) / 255) as u8,
138        )
139    }
140}
141
142/// Base trait implemented by all widgets.
143///
144/// A widget is expected to provide its bounds, draw itself using a
145/// [`Renderer`], and optionally handle input [`Event`]s.
146pub trait Widget {
147    /// Return the area this widget occupies relative to its parent.
148    fn bounds(&self) -> Rect;
149    /// Render the widget using the provided [`Renderer`].
150    fn draw(&self, renderer: &mut dyn Renderer);
151    /// Handle an event and return `true` if it was consumed.
152    ///
153    /// The default implementation for most widgets will simply ignore the
154    /// event and return `false`.
155    fn handle_event(&mut self, event: &Event) -> bool;
156
157    /// Return a region (in draw/landscape coordinates) that should be
158    /// restored from the pristine background copy, or `None`.
159    ///
160    /// Called once per frame before drawing. Overlay widgets should return
161    /// their bounds when they have just become hidden and need their
162    /// screen region cleared. The compositor handles the actual restoration.
163    fn clear_region(&mut self) -> Option<Rect> {
164        None
165    }
166
167    /// Called by the LPAR-10 layout pass to notify this widget of its
168    /// layout-computed bounds.
169    ///
170    /// The **default implementation is a no-op**: existing widgets are
171    /// repositioned at draw time by the object layer's translation mechanism
172    /// without any change to the widget's own `bounds()` return value.
173    ///
174    /// **Resize-aware widgets** that want the layout pass to also control
175    /// their *size* (not just position) SHOULD override this method to adopt
176    /// the computed rect: store `bounds` and return it from `Widget::bounds()`.
177    /// When this is done, `widget.bounds() == effective_bounds` and the object
178    /// layer's draw translation auto-zeroes.
179    ///
180    /// This is additive (a default no-op body) and MUST NOT break any existing
181    /// `Widget` implementer (LPAR-02 §5 additive-first rule; LPAR-10 §5.A).
182    fn set_bounds(&mut self, _bounds: Rect) {}
183
184    /// Expose this widget's font slot for cascade-driven font resolution
185    /// (FONT-05 §5.B).
186    ///
187    /// The **default returns `None`**: a widget with no text font is opaque to
188    /// the font registry. Text widgets that hold a
189    /// [`WidgetFont`](crate::font::WidgetFont) override this to return
190    /// `Some(&mut self.font)`, so
191    /// [`apply_font_registry`](crate::font::apply_font_registry) can write a
192    /// registry-resolved handle into the slot without per-widget dispatch.
193    ///
194    /// Defaulted, so it adds no obligation to existing `Widget` implementers
195    /// (FONT-00 §5.D as amended 2026-06-15) and changes no behavior for widgets
196    /// that do not override it.
197    fn widget_font_mut(&mut self) -> Option<&mut crate::font::WidgetFont> {
198        None
199    }
200}