Skip to main content

rlvgl_app_demo/
lib.rs

1//! Demo application for rlvgl showcasing core widgets and plugin features.
2//!
3//! Implements the [`Application`] trait so it can be loaded statically or
4//! dynamically by the simulator and hardware runtimes.
5
6#![cfg_attr(not(test), no_std)]
7
8extern crate alloc;
9
10#[cfg(any(
11    test,
12    feature = "gif",
13    all(feature = "jpeg", not(target_os = "none")),
14    all(feature = "png", not(target_os = "none")),
15    all(feature = "qrcode", not(target_os = "none"))
16))]
17extern crate std;
18
19use alloc::boxed::Box;
20use alloc::{rc::Rc, vec::Vec};
21use core::cell::RefCell;
22
23#[cfg(feature = "gif")]
24use rlvgl_core::gif;
25#[cfg(all(feature = "jpeg", not(target_os = "none")))]
26use rlvgl_core::jpeg;
27#[cfg(all(feature = "png", not(target_os = "none")))]
28use rlvgl_core::png;
29#[cfg(all(feature = "qrcode", not(target_os = "none")))]
30use rlvgl_core::qrcode;
31
32#[cfg(any(
33    all(feature = "png", not(target_os = "none")),
34    all(feature = "jpeg", not(target_os = "none")),
35    feature = "gif"
36))]
37use rlvgl_core::widget::Color;
38use rlvgl_core::{
39    WidgetNode,
40    application::{AppInfo, Application},
41    event::Event,
42    widget::{Rect, Widget},
43};
44#[cfg(any(
45    all(feature = "png", not(target_os = "none")),
46    all(feature = "jpeg", not(target_os = "none")),
47    feature = "gif"
48))]
49use rlvgl_widgets::image::Image;
50use rlvgl_widgets::{button::Button, container::Container, label::Label};
51
52use rlvgl_i18n::t;
53
54type WidgetHandle = Rc<RefCell<dyn Widget>>;
55type WidgetSlot = Rc<RefCell<Option<WidgetHandle>>>;
56
57// 1x1 pixel PNG and JPEG images used to exercise the decoders without relying on
58// external binary assets.
59#[cfg(all(feature = "png", not(target_os = "none")))]
60const PNG_LOGO: &[u8] = &[
61    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
62    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
63    0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xf8, 0xcf, 0xc0, 0x00,
64    0x00, 0x03, 0x01, 0x01, 0x00, 0xc9, 0xfe, 0x92, 0xef, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
65    0x44, 0xae, 0x42, 0x60, 0x82,
66];
67
68#[cfg(all(feature = "jpeg", not(target_os = "none")))]
69const JPEG_LOGO: &[u8] = &[
70    0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
71    0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08,
72    0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14, 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12,
73    0x13, 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20,
74    0x22, 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27,
75    0x39, 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x09, 0x09,
76    0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d, 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32, 0x32, 0x32,
77    0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
78    0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
79    0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
80    0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01,
81    0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
82    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
83    0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04,
84    0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05,
85    0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
86    0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a,
87    0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
88    0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
89    0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
90    0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
91    0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
92    0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
93    0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
94    0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01,
95    0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
96    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5,
97    0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02,
98    0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61,
99    0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52,
100    0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a,
101    0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
102    0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
103    0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86,
104    0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
105    0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
106    0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
107    0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
108    0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f,
109    0x00, 0xe2, 0xe8, 0xa2, 0x8a, 0xf9, 0x93, 0xf7, 0x13, 0xff, 0xd9,
110];
111
112#[cfg(feature = "gif")]
113const GIF_LOGO: &[u8] = &[
114    0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0xf0, 0x00, 0x00, 0xff, 0x00, 0x00,
115    0xff, 0xff, 0xff, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
116    0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b,
117];
118
119/// Demo application state.
120pub struct DemoApp {
121    pending: Rc<RefCell<Vec<WidgetNode>>>,
122    to_remove: Rc<RefCell<Vec<WidgetHandle>>>,
123    counter: Rc<RefCell<u32>>,
124}
125
126impl DemoApp {
127    /// Create a new demo application instance.
128    pub fn new() -> Self {
129        Self {
130            pending: Rc::new(RefCell::new(Vec::new())),
131            to_remove: Rc::new(RefCell::new(Vec::new())),
132            counter: Rc::new(RefCell::new(0)),
133        }
134    }
135}
136
137impl Default for DemoApp {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl Application for DemoApp {
144    fn info(&self) -> AppInfo {
145        AppInfo {
146            name: "rlvgl-demo",
147            version: "0.1.0",
148            preferred_width: 320,
149            preferred_height: 240,
150        }
151    }
152
153    fn build(&mut self, width: u32, height: u32) -> WidgetNode {
154        let w = width as i32;
155        let h = height as i32;
156        let root_w = width;
157        let root_h = height;
158
159        let button = Rc::new(RefCell::new(Button::new(
160            t!("demo.clicks_zero"),
161            Rect {
162                x: 10,
163                y: 40,
164                width: 80,
165                height: 20,
166            },
167        )));
168
169        {
170            let counter = self.counter.clone();
171            button.borrow_mut().set_on_click(move |btn: &mut Button| {
172                let mut count = counter.borrow_mut();
173                *count += 1;
174                btn.set_text(t!("demo.clicks", count = *count));
175            });
176        }
177
178        let label = Label::new(
179            t!("demo.title", version = "0.1.9"),
180            Rect {
181                x: 10,
182                y: 10,
183                width: 200,
184                height: 20,
185            },
186        );
187
188        let plugins = Rc::new(RefCell::new(Button::new(
189            t!("demo.plugins"),
190            Rect {
191                x: 100,
192                y: 40,
193                width: 80,
194                height: 20,
195            },
196        )));
197        let menu_widget: WidgetSlot = Rc::new(RefCell::new(None));
198        #[cfg(all(feature = "qrcode", not(target_os = "none")))]
199        let qr_demo: WidgetSlot = Rc::new(RefCell::new(None));
200        #[cfg(all(feature = "png", not(target_os = "none")))]
201        let png_demo: WidgetSlot = Rc::new(RefCell::new(None));
202        #[cfg(feature = "gif")]
203        let gif_demo: WidgetSlot = Rc::new(RefCell::new(None));
204        #[cfg(all(feature = "jpeg", not(target_os = "none")))]
205        let jpeg_demo: WidgetSlot = Rc::new(RefCell::new(None));
206        {
207            let pending_add = self.pending.clone();
208            let pending_rm = self.to_remove.clone();
209            let menu_ref = menu_widget.clone();
210            #[cfg(all(feature = "qrcode", not(target_os = "none")))]
211            let qr_ref = qr_demo.clone();
212            #[cfg(all(feature = "png", not(target_os = "none")))]
213            let png_ref = png_demo.clone();
214            #[cfg(feature = "gif")]
215            let gif_ref = gif_demo.clone();
216            #[cfg(all(feature = "jpeg", not(target_os = "none")))]
217            let jpeg_ref = jpeg_demo.clone();
218            let _root_w = root_w;
219            let _root_h = root_h;
220            plugins.borrow_mut().set_on_click(move |_btn: &mut Button| {
221                if let Some(menu_w) = menu_ref.borrow_mut().take() {
222                    pending_rm.borrow_mut().push(menu_w);
223                } else {
224                    let menu_w: WidgetHandle = Rc::new(RefCell::new(Container::new(Rect {
225                        x: 10,
226                        y: 70,
227                        width: 100,
228                        height: 170,
229                    })));
230                    #[allow(unused_mut)]
231                    let mut children = Vec::new();
232
233                    #[cfg(all(feature = "qrcode", not(target_os = "none")))]
234                    {
235                        let qr_button = Rc::new(RefCell::new(Button::new(
236                            t!("demo.qr_code"),
237                            Rect {
238                                x: 20,
239                                y: 80,
240                                width: 80,
241                                height: 20,
242                            },
243                        )));
244                        {
245                            let pending_add = pending_add.clone();
246                            let pending_rm = pending_rm.clone();
247                            let qr_demo = qr_ref.clone();
248                            let w = root_w;
249                            let h = root_h;
250                            qr_button.borrow_mut().set_on_click(move |_b: &mut Button| {
251                                if let Some(qr_w) = qr_demo.borrow_mut().take() {
252                                    pending_rm.borrow_mut().push(qr_w);
253                                } else {
254                                    let demo = build_plugin_demo(w, h);
255                                    let handle = demo.widget.clone();
256                                    qr_demo.borrow_mut().replace(handle.clone());
257                                    pending_add.borrow_mut().push(demo);
258                                }
259                            });
260                        }
261                        children.push(WidgetNode {
262                            widget: qr_button,
263                            children: Vec::new(),
264                        });
265                    }
266
267                    #[cfg(all(feature = "png", not(target_os = "none")))]
268                    {
269                        let png_button = Rc::new(RefCell::new(Button::new(
270                            t!("demo.png"),
271                            Rect {
272                                x: 20,
273                                y: 110,
274                                width: 80,
275                                height: 20,
276                            },
277                        )));
278                        {
279                            let pending_add = pending_add.clone();
280                            let pending_rm = pending_rm.clone();
281                            let png_demo = png_ref.clone();
282                            let w = root_w;
283                            let h = root_h;
284                            png_button
285                                .borrow_mut()
286                                .set_on_click(move |_b: &mut Button| {
287                                    if let Some(png_w) = png_demo.borrow_mut().take() {
288                                        pending_rm.borrow_mut().push(png_w);
289                                    } else {
290                                        let demo = build_png_demo(w, h);
291                                        let handle = demo.widget.clone();
292                                        png_demo.borrow_mut().replace(handle.clone());
293                                        pending_add.borrow_mut().push(demo);
294                                    }
295                                });
296                        }
297                        children.push(WidgetNode {
298                            widget: png_button,
299                            children: Vec::new(),
300                        });
301                    }
302
303                    #[cfg(feature = "gif")]
304                    {
305                        let gif_button = Rc::new(RefCell::new(Button::new(
306                            t!("demo.gif"),
307                            Rect {
308                                x: 20,
309                                y: 140,
310                                width: 80,
311                                height: 20,
312                            },
313                        )));
314                        {
315                            let pending_add = pending_add.clone();
316                            let pending_rm = pending_rm.clone();
317                            let gif_demo = gif_ref.clone();
318                            let w = root_w;
319                            let h = root_h;
320                            gif_button
321                                .borrow_mut()
322                                .set_on_click(move |_b: &mut Button| {
323                                    if let Some(gif_w) = gif_demo.borrow_mut().take() {
324                                        pending_rm.borrow_mut().push(gif_w);
325                                    } else {
326                                        let demo = build_gif_demo(w, h);
327                                        let handle = demo.widget.clone();
328                                        gif_demo.borrow_mut().replace(handle.clone());
329                                        pending_add.borrow_mut().push(demo);
330                                    }
331                                });
332                        }
333                        children.push(WidgetNode {
334                            widget: gif_button,
335                            children: Vec::new(),
336                        });
337                    }
338
339                    #[cfg(all(feature = "jpeg", not(target_os = "none")))]
340                    {
341                        let jpeg_button = Rc::new(RefCell::new(Button::new(
342                            t!("demo.jpeg"),
343                            Rect {
344                                x: 20,
345                                y: 170,
346                                width: 80,
347                                height: 20,
348                            },
349                        )));
350                        {
351                            let pending_add = pending_add.clone();
352                            let pending_rm = pending_rm.clone();
353                            let jpeg_demo = jpeg_ref.clone();
354                            let w = root_w;
355                            let h = root_h;
356                            jpeg_button
357                                .borrow_mut()
358                                .set_on_click(move |_b: &mut Button| {
359                                    if let Some(jpeg_w) = jpeg_demo.borrow_mut().take() {
360                                        pending_rm.borrow_mut().push(jpeg_w);
361                                    } else {
362                                        let demo = build_jpeg_demo(w, h);
363                                        let handle = demo.widget.clone();
364                                        jpeg_demo.borrow_mut().replace(handle.clone());
365                                        pending_add.borrow_mut().push(demo);
366                                    }
367                                });
368                        }
369                        children.push(WidgetNode {
370                            widget: jpeg_button,
371                            children: Vec::new(),
372                        });
373                    }
374
375                    let menu = WidgetNode {
376                        widget: menu_w.clone(),
377                        children,
378                    };
379                    menu_ref.borrow_mut().replace(menu_w);
380                    pending_add.borrow_mut().push(menu);
381                }
382            });
383        }
384
385        WidgetNode {
386            widget: Rc::new(RefCell::new(Container::new(Rect {
387                x: 0,
388                y: 0,
389                width: w,
390                height: h,
391            }))),
392            children: alloc::vec![
393                WidgetNode {
394                    widget: Rc::new(RefCell::new(label)),
395                    children: Vec::new(),
396                },
397                WidgetNode {
398                    widget: button.clone(),
399                    children: Vec::new(),
400                },
401                WidgetNode {
402                    widget: plugins.clone(),
403                    children: Vec::new(),
404                },
405            ],
406        }
407    }
408
409    fn after_event(&mut self, root: &Rc<RefCell<WidgetNode>>, _event: &Event) {
410        flush_pending(root, &self.pending, &self.to_remove);
411    }
412
413    fn tick(&mut self, _root: &Rc<RefCell<WidgetNode>>) {}
414
415    fn destroy(&mut self) {}
416}
417
418/// Create a new demo application as a boxed trait object.
419pub fn create_app() -> Box<dyn Application> {
420    Box::new(DemoApp::new())
421}
422
423// ---------------------------------------------------------------------------
424// FFI exports for cdylib loading
425// ---------------------------------------------------------------------------
426
427/// FFI entry point: create a new `DemoApp` and return an owned raw pointer.
428///
429/// # Safety contract
430///
431/// The caller and callee must be compiled with the same `rustc` version and
432/// target. The fat pointer (`*mut dyn Application`) is stable within a single
433/// compiler+target pair.
434#[unsafe(no_mangle)]
435#[allow(improper_ctypes_definitions)]
436pub extern "C" fn rlvgl_create_app() -> *mut dyn Application {
437    Box::into_raw(create_app())
438}
439
440/// FFI entry point: destroy a previously created application.
441///
442/// # Safety
443///
444/// `ptr` must have been returned by [`rlvgl_create_app`] and must not be used
445/// after this call.
446#[unsafe(no_mangle)]
447#[allow(improper_ctypes_definitions)]
448pub unsafe extern "C" fn rlvgl_destroy_app(ptr: *mut dyn Application) {
449    if !ptr.is_null() {
450        unsafe {
451            drop(Box::from_raw(ptr));
452        }
453    }
454}
455
456// ---------------------------------------------------------------------------
457// Plugin demo builders (feature-gated)
458// ---------------------------------------------------------------------------
459
460#[cfg(all(feature = "qrcode", not(target_os = "none")))]
461/// Build a widget demonstrating plugin features such as QR code generation.
462pub fn build_plugin_demo(root_w: u32, root_h: u32) -> WidgetNode {
463    let (pixels_vec, width, _) = qrcode::generate(b"https://github.com/SoftOboros/rlvgl").unwrap();
464    let target = root_h * 2 / 3;
465    let scale = target as f32 / width as f32;
466    let new_w = target;
467    let new_h = target;
468    let mut scaled = Vec::with_capacity((new_w * new_h) as usize);
469    for y in 0..new_h {
470        for x in 0..new_w {
471            let src_x = (x as f32 / scale).floor() as usize;
472            let src_y = (y as f32 / scale).floor() as usize;
473            let idx = src_y * width as usize + src_x;
474            let color = pixels_vec
475                .get(idx)
476                .copied()
477                .unwrap_or(Color(255, 255, 255, 255));
478            scaled.push(color);
479        }
480    }
481    let pixels: &'static [Color] = Box::leak(scaled.into_boxed_slice());
482    let x_pos = (root_w - new_w) as i32;
483    let y_pos = (root_h - new_h) as i32;
484    WidgetNode {
485        widget: Rc::new(RefCell::new(Image::new(
486            Rect {
487                x: x_pos,
488                y: y_pos,
489                width: new_w as i32,
490                height: new_h as i32,
491            },
492            new_w as i32,
493            new_h as i32,
494            pixels,
495        ))),
496        children: Vec::new(),
497    }
498}
499
500#[cfg(all(feature = "png", not(target_os = "none")))]
501/// Build a widget displaying an embedded PNG at the given scale.
502pub fn build_png_demo_scaled(scale: f32, root_w: u32, root_h: u32) -> WidgetNode {
503    let (pixels_vec, width, height) =
504        png::decode(PNG_LOGO).expect("failed to decode built-in PNG asset");
505
506    let mut scale = if scale.is_finite() && scale > 0.0 {
507        scale
508    } else {
509        1.0
510    };
511    scale = scale
512        .min(root_w as f32 / width as f32)
513        .min(root_h as f32 / height as f32);
514
515    let new_w = (width as f32 * scale).max(1.0).round() as u32;
516    let new_h = (height as f32 * scale).max(1.0).round() as u32;
517
518    let mut scaled = Vec::with_capacity((new_w * new_h) as usize);
519    for y in 0..new_h {
520        for x in 0..new_w {
521            let src_x = (x as f32 / scale).floor() as usize;
522            let src_y = (y as f32 / scale).floor() as usize;
523            let idx = src_y * width as usize + src_x;
524            let color = pixels_vec.get(idx).copied().unwrap_or(Color(0, 0, 0, 255));
525            scaled.push(color);
526        }
527    }
528    let pixels: &'static [Color] = Box::leak(scaled.into_boxed_slice());
529
530    let x_pos = (root_w - new_w) as i32;
531    let y_pos = (root_h - new_h) as i32;
532    WidgetNode {
533        widget: Rc::new(RefCell::new(Image::new(
534            Rect {
535                x: x_pos,
536                y: y_pos,
537                width: new_w as i32,
538                height: new_h as i32,
539            },
540            new_w as i32,
541            new_h as i32,
542            pixels,
543        ))),
544        children: Vec::new(),
545    }
546}
547
548#[cfg(all(feature = "png", not(target_os = "none")))]
549/// Build a PNG demo using the default scale of `0.5`.
550pub fn build_png_demo(root_w: u32, root_h: u32) -> WidgetNode {
551    build_png_demo_scaled(0.5, root_w, root_h)
552}
553
554#[cfg(feature = "gif")]
555/// Build a widget displaying an embedded GIF at the given scale.
556pub fn build_gif_demo_scaled(scale: f32, root_w: u32, root_h: u32) -> WidgetNode {
557    let (frames, width, height) =
558        gif::decode(GIF_LOGO).expect("failed to decode built-in GIF asset");
559    let frame = &frames[0];
560    let width = width as u32;
561    let height = height as u32;
562    let mut scale = if scale.is_finite() && scale > 0.0 {
563        scale
564    } else {
565        1.0
566    };
567    scale = scale
568        .min(root_w as f32 / width as f32)
569        .min(root_h as f32 / height as f32);
570
571    let new_w = (width as f32 * scale).max(1.0).round() as u32;
572    let new_h = (height as f32 * scale).max(1.0).round() as u32;
573
574    let mut scaled = Vec::with_capacity((new_w * new_h) as usize);
575    for y in 0..new_h {
576        for x in 0..new_w {
577            let src_x = (x as f32 / scale).floor() as usize;
578            let src_y = (y as f32 / scale).floor() as usize;
579            let idx = src_y * width as usize + src_x;
580            let color = frame
581                .pixels
582                .get(idx)
583                .copied()
584                .unwrap_or(Color(0, 0, 0, 255));
585            scaled.push(color);
586        }
587    }
588    let pixels: &'static [Color] = Box::leak(scaled.into_boxed_slice());
589
590    let x_pos = (root_w - new_w) as i32;
591    let y_pos = (root_h - new_h) as i32;
592    WidgetNode {
593        widget: Rc::new(RefCell::new(Image::new(
594            Rect {
595                x: x_pos,
596                y: y_pos,
597                width: new_w as i32,
598                height: new_h as i32,
599            },
600            new_w as i32,
601            new_h as i32,
602            pixels,
603        ))),
604        children: Vec::new(),
605    }
606}
607
608#[cfg(feature = "gif")]
609/// Build a GIF demo using the default scale of `0.5`.
610pub fn build_gif_demo(root_w: u32, root_h: u32) -> WidgetNode {
611    build_gif_demo_scaled(0.5, root_w, root_h)
612}
613
614#[cfg(all(feature = "jpeg", not(target_os = "none")))]
615/// Build a widget displaying an embedded JPEG at the given scale.
616pub fn build_jpeg_demo_scaled(scale: f32, root_w: u32, root_h: u32) -> WidgetNode {
617    let (pixels_vec, width, height) =
618        jpeg::decode(JPEG_LOGO).expect("failed to decode built-in JPEG asset");
619
620    let width = width as u32;
621    let height = height as u32;
622    let mut scale = if scale.is_finite() && scale > 0.0 {
623        scale
624    } else {
625        1.0
626    };
627    scale = scale
628        .min(root_w as f32 / width as f32)
629        .min(root_h as f32 / height as f32);
630
631    let new_w = (width as f32 * scale).max(1.0).round() as u32;
632    let new_h = (height as f32 * scale).max(1.0).round() as u32;
633
634    let mut scaled = Vec::with_capacity((new_w * new_h) as usize);
635    for y in 0..new_h {
636        for x in 0..new_w {
637            let src_x = (x as f32 / scale).floor() as usize;
638            let src_y = (y as f32 / scale).floor() as usize;
639            let idx = src_y * width as usize + src_x;
640            let color = pixels_vec.get(idx).copied().unwrap_or(Color(0, 0, 0, 255));
641            scaled.push(color);
642        }
643    }
644    let pixels: &'static [Color] = Box::leak(scaled.into_boxed_slice());
645
646    let x_pos = (root_w - new_w) as i32;
647    let y_pos = (root_h - new_h) as i32;
648    WidgetNode {
649        widget: Rc::new(RefCell::new(Image::new(
650            Rect {
651                x: x_pos,
652                y: y_pos,
653                width: new_w as i32,
654                height: new_h as i32,
655            },
656            new_w as i32,
657            new_h as i32,
658            pixels,
659        ))),
660        children: Vec::new(),
661    }
662}
663
664#[cfg(all(feature = "jpeg", not(target_os = "none")))]
665/// Build a JPEG demo using the default scale of `0.5`.
666pub fn build_jpeg_demo(root_w: u32, root_h: u32) -> WidgetNode {
667    build_jpeg_demo_scaled(0.5, root_w, root_h)
668}
669
670/// Flush any widgets queued during event callbacks into the root tree.
671///
672/// This is also available as part of the [`Application::after_event`] flow,
673/// but exposed publicly for use by targets that manage their own widget tree.
674pub fn flush_pending(
675    root: &Rc<RefCell<WidgetNode>>,
676    pending: &Rc<RefCell<Vec<WidgetNode>>>,
677    to_remove: &Rc<RefCell<Vec<WidgetHandle>>>,
678) {
679    let mut root_ref = root.borrow_mut();
680    for handle in to_remove.borrow_mut().drain(..) {
681        root_ref
682            .children
683            .retain(|n| !Rc::ptr_eq(&n.widget, &handle));
684    }
685    let mut pending_nodes = pending.borrow_mut();
686    root_ref.children.extend(pending_nodes.drain(..));
687}