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