Skip to main content

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}