rlvgl_core/application.rs
1//! Application trait for self-contained rlvgl applications.
2//!
3//! This module defines the [`Application`] trait that apps implement to be
4//! loaded by the simulator or embedded runtime. Applications produce a widget
5//! tree and respond to lifecycle events.
6
7use alloc::rc::Rc;
8use core::cell::RefCell;
9
10use crate::WidgetNode;
11use crate::event::Event;
12use crate::object::ObjectNode;
13
14/// Metadata describing an application.
15pub struct AppInfo {
16 /// Human-readable application name.
17 pub name: &'static str,
18 /// Semantic version string.
19 pub version: &'static str,
20 /// Preferred display width in pixels.
21 pub preferred_width: u32,
22 /// Preferred display height in pixels.
23 pub preferred_height: u32,
24}
25
26/// Trait implemented by loadable rlvgl applications.
27///
28/// The runtime calls [`build`](Application::build) once to obtain the root
29/// widget tree, then dispatches events and ticks each frame. Applications that
30/// need to add or remove widgets after events should do so in
31/// [`after_event`](Application::after_event).
32pub trait Application {
33 /// Return metadata about this application.
34 fn info(&self) -> AppInfo;
35
36 /// Construct the initial widget tree for the given display dimensions.
37 ///
38 /// The returned [`WidgetNode`] becomes the root of the UI. The runtime
39 /// wraps it in `Rc<RefCell<>>` for shared access.
40 fn build(&mut self, width: u32, height: u32) -> WidgetNode;
41
42 /// Called after each event has been dispatched through the widget tree.
43 ///
44 /// Use this to flush deferred widget additions/removals (replacing the
45 /// manual `flush_pending` pattern).
46 fn after_event(&mut self, root: &Rc<RefCell<WidgetNode>>, event: &Event);
47
48 /// Called once per frame for animations or deferred work.
49 ///
50 /// The default implementation does nothing.
51 fn tick(&mut self, _root: &Rc<RefCell<WidgetNode>>) {}
52
53 /// Called before the application is unloaded.
54 ///
55 /// Use this for cleanup. The default implementation does nothing.
56 fn destroy(&mut self) {}
57}
58
59/// Extension helpers for legacy [`Application`] implementations.
60///
61/// This is the compatibility bridge from the public-field [`WidgetNode`]
62/// carrier to the LPAR object substrate. New runtime phases should target
63/// [`ObjectNode`] while existing applications can still build their legacy
64/// root and adopt it at the runtime boundary.
65pub trait ApplicationObjectExt: Application {
66 /// Build this application and adopt the returned [`WidgetNode`] root into
67 /// an [`ObjectNode`] tree.
68 fn build_object_root(&mut self, width: u32, height: u32) -> ObjectNode {
69 ObjectNode::adopt(self.build(width, height))
70 }
71}
72
73impl<T: Application + ?Sized> ApplicationObjectExt for T {}
74
75/// Trait implemented by applications that natively build an [`ObjectNode`] tree.
76///
77/// This is the forward runtime carrier for LPAR phases. The legacy
78/// [`Application`] trait remains source-compatible; runtimes that need object
79/// metadata, invalidation, bubbling, focus, or scroll semantics should prefer
80/// this trait when available.
81pub trait ObjectApplication {
82 /// Return metadata about this application.
83 fn info(&self) -> AppInfo;
84
85 /// Construct the initial object tree for the given display dimensions.
86 fn build_object(&mut self, width: u32, height: u32) -> ObjectNode;
87
88 /// Called after each event has been dispatched through the object tree.
89 fn after_object_event(&mut self, _root: &Rc<RefCell<ObjectNode>>, _event: &Event) {}
90
91 /// Called once per frame for animations or deferred work.
92 fn tick_object(&mut self, _root: &Rc<RefCell<ObjectNode>>) {}
93
94 /// Called before the application is unloaded.
95 fn destroy(&mut self) {}
96}
97
98/// Symbol name used to locate the `create_app` entry point in a cdylib.
99pub const CREATE_APP_SYMBOL: &[u8] = b"rlvgl_create_app";
100
101/// Symbol name used to locate the `destroy_app` entry point in a cdylib.
102pub const DESTROY_APP_SYMBOL: &[u8] = b"rlvgl_destroy_app";
103
104#[cfg(test)]
105mod tests {
106 use alloc::rc::Rc;
107 use core::cell::RefCell;
108
109 use super::*;
110 use crate::event::Event;
111 use crate::renderer::Renderer;
112 use crate::widget::{Rect, Widget};
113
114 struct TestWidget;
115
116 impl Widget for TestWidget {
117 fn bounds(&self) -> Rect {
118 Rect {
119 x: 0,
120 y: 0,
121 width: 10,
122 height: 10,
123 }
124 }
125
126 fn draw(&self, _renderer: &mut dyn Renderer) {}
127
128 fn handle_event(&mut self, _event: &Event) -> bool {
129 false
130 }
131 }
132
133 struct TestApp;
134
135 impl Application for TestApp {
136 fn info(&self) -> AppInfo {
137 AppInfo {
138 name: "test",
139 version: "0.0.0",
140 preferred_width: 10,
141 preferred_height: 10,
142 }
143 }
144
145 fn build(&mut self, _width: u32, _height: u32) -> WidgetNode {
146 WidgetNode::new(Rc::new(RefCell::new(TestWidget))).with_tag("root")
147 }
148
149 fn after_event(&mut self, _root: &Rc<RefCell<WidgetNode>>, _event: &Event) {}
150 }
151
152 #[test]
153 fn legacy_application_can_build_object_root() {
154 let mut app = TestApp;
155 let root = app.build_object_root(10, 10);
156
157 assert_eq!(root.tag(), Some("root"));
158 assert_eq!(root.widget().borrow().bounds().width, 10);
159 }
160}