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}