Skip to main content

rlvgl_core/
lib.rs

1//! Core runtime types and utilities for the `rlvgl` UI toolkit.
2//!
3//! This crate exposes the building blocks used by higher-level widgets and
4//! platform backends. It is intended to be usable in `no_std` environments and
5//! therefore avoids allocations where possible.
6//!
7//! Widgets are organized into a tree of `WidgetNode` values which receive
8//! `Event`s and draw themselves via a `Renderer` implementation.
9//!
10//! **Note:** `Event` and `Renderer` are externally supplied types, not defined
11//! in this crate.
12#![cfg_attr(not(test), no_std)]
13#![deny(missing_docs)]
14#![cfg_attr(all(docsrs, nightly), feature(doc_cfg))]
15
16// When running tests, pull in the standard library so the test
17// harness can link successfully.
18#[cfg(any(
19    test,
20    feature = "gif",
21    feature = "lottie",
22    feature = "pinyin",
23    feature = "fatfs",
24    feature = "nes",
25    feature = "apng",
26    all(feature = "fontdue", not(target_os = "none")),
27    all(feature = "jpeg", not(target_os = "none")),
28    all(feature = "png", not(target_os = "none")),
29    all(feature = "qrcode", not(target_os = "none"))
30))]
31extern crate std;
32
33extern crate alloc;
34
35/// Tick-driven tween/animation system (deterministic, no wall clock).
36pub mod anim;
37pub mod animation;
38pub mod application;
39#[cfg(feature = "fs")]
40pub mod asset;
41pub mod bitmap_font;
42/// Graphics-language layer: structured drawing commands as data.
43pub mod cmd;
44/// Drawing helpers for rounded rectangles and borders.
45pub mod draw;
46/// Shared edit-state machine (buffer, caret, mutation gates) promoted from
47/// `rlvgl-ui` so that `rlvgl-widgets` can depend on it without a crate cycle
48/// (LPAR-14 §5.C).
49pub mod edit;
50pub mod event;
51/// Focus traversal and group policy for the LPAR-04 event/focus runtime.
52pub mod focus;
53/// Backend-neutral font metrics, shaping, and greedy wrapping.
54pub mod font;
55#[cfg(feature = "fs")]
56pub mod fs;
57/// 1-bit bitmap icons (folder, file) rendered at font height.
58pub mod icon_bitmap;
59/// Image descriptors, blit options, and cache handles.
60pub mod image;
61pub mod interface;
62/// Shared invalidation planner and present-plan types (LPAR-03).
63pub mod invalidation;
64/// LPAR-10 layout substrate: `Dimension`, flex/grid engines, `LayoutState`, and layout pass.
65pub mod layout;
66/// LPAR-08 alpha mask primitives and coverage combinators.
67pub mod mask;
68/// LVGL-parity object metadata and tree helpers.
69pub mod object;
70/// Node-resident object animations; see [`object_anim::ObjectAnims`].
71pub mod object_anim;
72/// LPAR-15 value-binding `Subject<T>` — orthogonal to the LPAR-04 event system.
73pub mod observer;
74/// Variable-width packed font renderer (grayscale anti-aliased).
75pub mod packed_font;
76pub mod plugins;
77/// LPAR-15 typed property accessor: `PropertyValue` enum and the `Queryable` trait.
78pub mod property;
79/// Anti-aliased rasterization kernels (OBB and helpers) usable by both
80/// software and hardware-accelerated `Renderer` implementations.
81pub mod raster;
82pub mod renderer;
83/// LPAR-05 scroll runtime: scroll state, controller, and snap logic.
84pub mod scroll;
85pub mod style;
86/// LPAR-07 style cascade substrate: `Part`, `Selector`, `StylePatch`, `StyleState`, and resolution.
87pub mod style_cascade;
88pub mod theme;
89/// Tick-driven timer registry; see [`timer::Timers`].
90pub mod timer;
91pub mod widget;
92
93#[cfg(feature = "canvas")]
94#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
95pub use plugins::canvas;
96
97#[cfg(feature = "fatfs")]
98#[cfg_attr(docsrs, doc(cfg(feature = "fatfs")))]
99pub use plugins::fatfs;
100
101#[cfg(all(feature = "fontdue", not(target_os = "none")))]
102#[cfg_attr(docsrs, doc(cfg(feature = "fontdue")))]
103pub use plugins::fontdue;
104
105#[cfg(feature = "gif")]
106#[cfg_attr(docsrs, doc(cfg(feature = "gif")))]
107pub use plugins::gif;
108
109#[cfg(feature = "apng")]
110#[cfg_attr(docsrs, doc(cfg(feature = "apng")))]
111pub use plugins::apng;
112
113#[cfg(all(feature = "jpeg", not(target_os = "none")))]
114#[cfg_attr(docsrs, doc(cfg(feature = "jpeg")))]
115#[cfg_attr(docsrs, doc(cfg(feature = "jpeg")))]
116pub use plugins::jpeg;
117#[cfg(feature = "lottie")]
118#[cfg_attr(docsrs, doc(cfg(feature = "lottie")))]
119pub use plugins::lottie;
120
121#[cfg(feature = "nes")]
122#[cfg_attr(docsrs, doc(cfg(feature = "nes")))]
123pub use plugins::nes;
124
125#[cfg(feature = "pinyin")]
126#[cfg_attr(docsrs, doc(cfg(feature = "pinyin")))]
127pub use plugins::pinyin;
128
129#[cfg(all(feature = "png", not(target_os = "none")))]
130#[cfg_attr(docsrs, doc(cfg(feature = "png")))]
131pub use plugins::png;
132
133#[cfg(all(feature = "qrcode", not(target_os = "none")))]
134#[cfg_attr(docsrs, doc(cfg(feature = "qrcode")))]
135pub use plugins::qrcode;
136
137// Pull doc tests from the workspace README
138#[cfg(doctest)]
139doc_comment::doctest!("../../README.md");
140
141use alloc::rc::Rc;
142use alloc::vec::Vec;
143use core::cell::RefCell;
144
145/// Node in the widget hierarchy.
146///
147/// A `WidgetNode` owns a concrete widget instance and zero or more child nodes.
148/// Events are dispatched depth‑first and drawing occurs in the same order.
149/// This mirrors the behaviour of common retained‑mode UI frameworks.
150pub struct WidgetNode {
151    /// The widget instance held by this node.
152    pub widget: Rc<RefCell<dyn widget::Widget>>,
153    /// Child nodes that make up this widget's hierarchy.
154    pub children: Vec<WidgetNode>,
155    /// Optional test-automation tag for addressing this node by name.
156    ///
157    /// Used by `rlvgl-playit` to locate widgets in the tree without
158    /// relying on coordinates. Zero-cost when `None`.
159    pub tag: Option<&'static str>,
160}
161
162impl WidgetNode {
163    /// Create a new node with no children and no tag.
164    pub fn new(widget: Rc<RefCell<dyn widget::Widget>>) -> Self {
165        Self {
166            widget,
167            children: Vec::new(),
168            tag: None,
169        }
170    }
171
172    /// Attach a test-automation tag to this node.
173    pub fn with_tag(mut self, tag: &'static str) -> Self {
174        self.tag = Some(tag);
175        self
176    }
177
178    /// Propagate an event to this node and its children.
179    ///
180    /// Returns `true` if any widget handled the event.
181    pub fn dispatch_event(&mut self, event: &event::Event) -> bool {
182        if self.widget.borrow_mut().handle_event(event) {
183            return true;
184        }
185        for child in &mut self.children {
186            if child.dispatch_event(event) {
187                return true;
188            }
189        }
190        false
191    }
192
193    /// Recursively draw this node and all child nodes using the given renderer.
194    pub fn draw(&self, renderer: &mut dyn renderer::Renderer) {
195        self.widget.borrow().draw(renderer);
196        for child in &self.children {
197            child.draw(renderer);
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::event::Event;
206    use crate::renderer::Renderer;
207    use crate::widget::{Color, Rect, Widget};
208
209    struct TestWidget {
210        name: &'static str,
211        events: alloc::vec::Vec<&'static str>,
212        handle: bool,
213    }
214
215    impl TestWidget {
216        fn new(name: &'static str) -> (Rc<RefCell<Self>>, Rc<RefCell<Self>>) {
217            let w = Rc::new(RefCell::new(Self {
218                name,
219                events: alloc::vec::Vec::new(),
220                handle: false,
221            }));
222            (w.clone(), w)
223        }
224    }
225
226    impl Widget for TestWidget {
227        fn bounds(&self) -> Rect {
228            Rect {
229                x: 0,
230                y: 0,
231                width: 0,
232                height: 0,
233            }
234        }
235        fn draw(&self, renderer: &mut dyn Renderer) {
236            renderer.draw_text((0, 0), self.name, Color(0, 0, 0, 0));
237        }
238        fn handle_event(&mut self, _event: &Event) -> bool {
239            self.events.push(self.name);
240            self.handle
241        }
242    }
243
244    struct TestRenderer(pub alloc::vec::Vec<alloc::string::String>);
245    impl Renderer for TestRenderer {
246        fn fill_rect(&mut self, _rect: Rect, _color: Color) {}
247        fn draw_text(&mut self, _position: (i32, i32), text: &str, _color: Color) {
248            self.0.push(text.to_string());
249        }
250    }
251
252    #[test]
253    fn dispatch_event_bubbles_through_children() {
254        let (root_a, _) = TestWidget::new("A");
255        let (child_b, _) = TestWidget::new("B");
256        let (child_c, _) = TestWidget::new("C");
257
258        let mut tree = WidgetNode {
259            widget: root_a,
260            children: alloc::vec![
261                WidgetNode {
262                    widget: child_b.clone(),
263                    children: alloc::vec![],
264                    tag: None,
265                },
266                WidgetNode {
267                    widget: child_c.clone(),
268                    children: alloc::vec![],
269                    tag: None,
270                },
271            ],
272            tag: None,
273        };
274
275        let consumed = tree.dispatch_event(&Event::Tick);
276        assert!(!consumed, "no widget indicates it handled the event");
277
278        let b = child_b.borrow();
279        let c = child_c.borrow();
280        assert_eq!(b.events, alloc::vec!["B"], "child B saw one event");
281        assert_eq!(c.events, alloc::vec!["C"], "child C saw one event");
282    }
283
284    #[test]
285    fn draw_preorder_parent_before_children() {
286        let (root_a, root_ref) = TestWidget::new("A");
287        let (child_b, _) = TestWidget::new("B");
288        let (child_c, _) = TestWidget::new("C");
289
290        let tree = WidgetNode {
291            widget: root_a,
292            children: alloc::vec![
293                WidgetNode {
294                    widget: child_b,
295                    children: alloc::vec![],
296                    tag: None,
297                },
298                WidgetNode {
299                    widget: child_c,
300                    children: alloc::vec![],
301                    tag: None,
302                },
303            ],
304            tag: None,
305        };
306
307        let mut renderer = TestRenderer(alloc::vec::Vec::new());
308        tree.draw(&mut renderer);
309        assert_eq!(
310            renderer.0,
311            alloc::vec![
312                alloc::string::String::from("A"),
313                alloc::string::String::from("B"),
314                alloc::string::String::from("C"),
315            ],
316            "preorder draw order"
317        );
318
319        // Ensure no accidental mutation of the root widget occurred during draw.
320        assert!(root_ref.borrow().events.is_empty());
321    }
322}