Skip to main content

ove/
lvgl.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Safe LVGL v9 wrappers for the oveRTOS Rust SDK.
8//!
9//! Provides idiomatic Rust bindings matching the C++ `ove::lvgl` wrapper:
10//!
11//! - **Minimal overhead** — every widget is `Copy` + pointer-sized;
12//!   per-op cost is benchmarked at <https://varcain.github.io/oveRTOS/benchmarks/>
13//! - **Fluent API** — method chaining via `self -> Self` on `Copy` types
14//! - **Trait composition** — `Layout`, `Styleable`, `EventTarget` blanket-impl
15//!   on anything implementing `Widget`, replacing C++ CRTP mixins
16//! - **RAII** — `LvglGuard` for lock/unlock, `Style` with `Drop`
17//! - **`no_std` compatible** — no allocator needed
18
19use crate::bindings;
20use crate::error::{Error, Result};
21
22// SAFETY (module-wide contract for the `unsafe { bindings::lv_*(...) }` and
23// `bindings::ove_lvgl_*(...)` calls below): every LVGL object handle is a
24// pointer into LVGL's own registry, valid until the widget is deleted, and
25// all calls must run with the LVGL lock held (see `LvglGuard` / `lock()`),
26// matching the C/C++ contract. The event-handler trampolines and the widget
27// `Send`/`Sync` impls carry their own dedicated SAFETY banners (search
28// "Event handler soundness" and "Send + Sync"). Blocks that deviate —
29// `transmute` of the event user-data pointer, `from_raw_parts` over a canvas
30// buffer — carry their own `// SAFETY:` note.
31
32// =========================================================================
33//  Constants
34// =========================================================================
35
36/// LVGL alignment constant: default (inherit from parent).
37pub const ALIGN_DEFAULT: u8 = 0;
38/// LVGL alignment constant: align to the top-left of the parent.
39pub const ALIGN_TOP_LEFT: u8 = 1;
40/// LVGL alignment constant: align to the top-center of the parent.
41pub const ALIGN_TOP_MID: u8 = 2;
42/// LVGL alignment constant: align to the top-right of the parent.
43pub const ALIGN_TOP_RIGHT: u8 = 3;
44/// LVGL alignment constant: align to the bottom-left of the parent.
45pub const ALIGN_BOTTOM_LEFT: u8 = 4;
46/// LVGL alignment constant: align to the bottom-center of the parent.
47pub const ALIGN_BOTTOM_MID: u8 = 5;
48/// LVGL alignment constant: align to the bottom-right of the parent.
49pub const ALIGN_BOTTOM_RIGHT: u8 = 6;
50/// LVGL alignment constant: align to the middle of the left edge.
51pub const ALIGN_LEFT_MID: u8 = 7;
52/// LVGL alignment constant: align to the middle of the right edge.
53pub const ALIGN_RIGHT_MID: u8 = 8;
54/// LVGL alignment constant: center the widget relative to its parent.
55pub const ALIGN_CENTER: u8 = 9;
56
57/// LVGL style selector for the main (background) part of a widget.
58pub const PART_MAIN: u32 = 0x00_0000;
59/// LVGL style selector for the indicator part (e.g. bar fill, checkbox mark).
60pub const PART_INDICATOR: u32 = 0x02_0000;
61/// LVGL style selector for the knob of interactive widgets (slider, arc).
62pub const PART_KNOB: u32 = 0x03_0000;
63/// LVGL style selector for the items part (e.g. table cells, list items).
64pub const PART_ITEMS: u32 = 0x05_0000;
65
66/// LVGL v9 `LV_SIZE_CONTENT` — sets widget to size-to-content mode.
67/// Computed from: `LV_COORD_SET_SPEC(LV_COORD_MAX)` where
68/// `LV_COORD_TYPE_SHIFT = 29`, giving `((1<<29)-1) | (1<<29)`.
69pub const SIZE_CONTENT: i32 = 0x3FFF_FFFF;
70
71/// LVGL state bit for checked/toggled widgets (`LV_STATE_CHECKED`).
72pub const STATE_CHECKED: u32 = 0x0001;
73
74/// Grid descriptor sentinel that terminates column/row arrays
75/// (`LV_GRID_TEMPLATE_LAST`). Equal to `LV_COORD_MAX = (1 << 29) - 1`.
76pub const GRID_TEMPLATE_LAST: i32 = 0x1FFF_FFFF;
77/// Grid track size: size to content (`LV_GRID_CONTENT`).
78/// Equal to `LV_COORD_MAX - 101`.
79pub const GRID_CONTENT: i32 = 0x1FFF_FF9A;
80
81/// Grid track size: fractional unit. `grid_fr(1)` means "one FR unit".
82/// Equal to `LV_COORD_MAX - 100 + x`.
83pub const fn grid_fr(x: i32) -> i32 {
84    0x1FFF_FF9B + x
85}
86
87/// Grid cell alignment: flush with the cell's start edge (`LV_GRID_ALIGN_START`).
88pub const GRID_ALIGN_START: u32 = 0;
89/// Grid cell alignment: centred within the cell (`LV_GRID_ALIGN_CENTER`).
90pub const GRID_ALIGN_CENTER: u32 = 1;
91/// Grid cell alignment: flush with the cell's end edge (`LV_GRID_ALIGN_END`).
92pub const GRID_ALIGN_END: u32 = 2;
93/// Grid cell alignment: stretch to fill the cell (`LV_GRID_ALIGN_STRETCH`).
94pub const GRID_ALIGN_STRETCH: u32 = 3;
95
96/// Flex flow direction.
97#[repr(u32)]
98#[derive(Clone, Copy)]
99pub enum FlexFlow {
100    Row = 0x00,
101    Column = 0x01,
102    RowWrap = 0x04,
103    RowReverse = 0x02,
104    RowWrapReverse = 0x06,
105    ColumnWrap = 0x05,
106    ColumnReverse = 0x03,
107    ColumnWrapReverse = 0x07,
108}
109
110/// Flex track / item alignment (matches `lv_flex_align_t`).
111#[repr(u32)]
112#[derive(Clone, Copy)]
113pub enum FlexAlign {
114    Start = 0,
115    End = 1,
116    Center = 2,
117    SpaceEvenly = 3,
118    SpaceAround = 4,
119    SpaceBetween = 5,
120}
121
122/// Container layout kind (matches `lv_obj_set_layout` argument).
123#[repr(u32)]
124#[derive(Clone, Copy)]
125pub enum LayoutKind {
126    None = 0,
127    Flex = 1,
128    Grid = 2,
129}
130
131/// LVGL palette family (matches `LV_PALETTE_*`).
132#[repr(u32)]
133#[derive(Clone, Copy)]
134pub enum Palette {
135    Red = 0,
136    Pink = 1,
137    Purple = 2,
138    DeepPurple = 3,
139    Indigo = 4,
140    Blue = 5,
141    LightBlue = 6,
142    Cyan = 7,
143    Teal = 8,
144    Green = 9,
145    LightGreen = 10,
146    Lime = 11,
147    Yellow = 12,
148    Amber = 13,
149    Orange = 14,
150    DeepOrange = 15,
151    Brown = 16,
152    BlueGrey = 17,
153    Grey = 18,
154    None = 0xFF,
155}
156
157/// Text alignment selector (`LV_TEXT_ALIGN_*`).
158pub const TEXT_ALIGN_AUTO: u32 = 0;
159pub const TEXT_ALIGN_LEFT: u32 = 1;
160pub const TEXT_ALIGN_CENTER: u32 = 2;
161pub const TEXT_ALIGN_RIGHT: u32 = 3;
162
163// =========================================================================
164//  Color
165// =========================================================================
166
167/// RGB888 color matching `lv_color_t { blue, green, red }` layout.
168#[repr(C)]
169#[derive(Clone, Copy)]
170pub struct Color {
171    pub blue: u8,
172    pub green: u8,
173    pub red: u8,
174}
175
176impl Color {
177    /// Construct a color from red, green, blue components (0–255 each).
178    pub const fn make(r: u8, g: u8, b: u8) -> Self {
179        Self {
180            blue: b,
181            green: g,
182            red: r,
183        }
184    }
185
186    /// Return pure white (R=255, G=255, B=255).
187    pub const fn white() -> Self {
188        Self::make(255, 255, 255)
189    }
190
191    /// Return pure black (R=0, G=0, B=0).
192    pub const fn black() -> Self {
193        Self::make(0, 0, 0)
194    }
195
196    /// Construct a color from a packed 24-bit RGB hex value (e.g. `0xFF8800`).
197    pub fn hex(hex: u32) -> Self {
198        Self::make(
199            ((hex >> 16) & 0xFF) as u8,
200            ((hex >> 8) & 0xFF) as u8,
201            (hex & 0xFF) as u8,
202        )
203    }
204
205    /// Return the main (500-shade) color of an LVGL palette family.
206    ///
207    /// `p` must be one of the `PALETTE_*` constants (e.g. [`PALETTE_BLUE`]).
208    pub fn palette_main(p: u32) -> Self {
209        // SAFETY: `Color` is `#[repr(C)]` with the same layout as `lv_color_t`.
210        unsafe {
211            let c = bindings::lv_palette_main(p as _);
212            core::mem::transmute(c)
213        }
214    }
215
216    /// Lighten a palette color by `level` (0..=5; 0 = original main).
217    pub fn palette_lighten(p: Palette, level: u8) -> Self {
218        // SAFETY: `Color` is `#[repr(C)]` with the same layout as `lv_color_t`.
219        unsafe {
220            let c = bindings::lv_palette_lighten(p as _, level);
221            core::mem::transmute(c)
222        }
223    }
224
225    /// Darken a palette color by `level` (0..=4; 0 = original main).
226    pub fn palette_darken(p: Palette, level: u8) -> Self {
227        // SAFETY: `Color` is `#[repr(C)]` with the same layout as `lv_color_t`.
228        unsafe {
229            let c = bindings::lv_palette_darken(p as _, level);
230            core::mem::transmute(c)
231        }
232    }
233
234    /// Construct a color from a packed 12-bit RGB hex value (e.g. `0xF80` → `0xFF8800`).
235    pub fn hex3(hex: u32) -> Self {
236        // SAFETY: `Color` is `#[repr(C)]` with the same layout as `lv_color_t`.
237        unsafe {
238            let c = bindings::lv_color_hex3(hex);
239            core::mem::transmute(c)
240        }
241    }
242
243    pub(crate) fn to_raw(self) -> bindings::lv_color_t {
244        // SAFETY: `Color` is `#[repr(C)]` with the same layout as `lv_color_t`.
245        unsafe { core::mem::transmute(self) }
246    }
247}
248
249/// LVGL palette index for the blue palette family (`LV_PALETTE_BLUE`).
250pub const PALETTE_BLUE: u32 = 6;
251
252// =========================================================================
253//  Font accessors
254// =========================================================================
255
256/// Safe pointer to `lv_font_montserrat_14`.
257pub fn font_montserrat_14() -> *const bindings::lv_font_t {
258    unsafe { &bindings::lv_font_montserrat_14 }
259}
260
261/// Safe pointer to `lv_font_montserrat_24`.
262pub fn font_montserrat_24() -> *const bindings::lv_font_t {
263    unsafe { &bindings::lv_font_montserrat_24 }
264}
265
266/// Safe pointer to `lv_font_montserrat_32`.
267pub fn font_montserrat_32() -> *const bindings::lv_font_t {
268    unsafe { &bindings::lv_font_montserrat_32 }
269}
270
271// =========================================================================
272//  Widget trait — core abstraction replacing C++ ObjectView
273// =========================================================================
274
275/// Core trait for all LVGL widget wrappers. Provides access to the raw
276/// `lv_obj_t` pointer. All higher-level traits (`Layout`, `Styleable`,
277/// `EventTarget`) are blanket-implemented for any type implementing `Widget`.
278///
279/// # Safety contract
280/// Implementors must ensure `raw()` returns a pointer obtained from LVGL.
281/// All access must happen under the LVGL lock.
282pub trait Widget: Copy {
283    /// Return the raw `lv_obj_t` pointer for this widget.
284    ///
285    /// # Safety
286    /// Must only be used while holding the LVGL lock (see [`lock`]).
287    fn raw(self) -> *mut bindings::lv_obj_t;
288}
289
290// =========================================================================
291//  Layout trait — fluent positioning/sizing (blanket impl on Widget)
292// =========================================================================
293
294/// Fluent positioning, sizing, and flag manipulation.
295/// Blanket-implemented for all `Widget` types.
296pub trait Layout: Widget + Sized {
297    /// Set both width and height of the widget in pixels.
298    fn size(self, w: i32, h: i32) -> Self {
299        unsafe { bindings::lv_obj_set_size(self.raw(), w, h) };
300        self
301    }
302
303    /// Set the width of the widget in pixels.
304    fn width(self, w: i32) -> Self {
305        unsafe { bindings::lv_obj_set_width(self.raw(), w) };
306        self
307    }
308
309    /// Set the height of the widget in pixels.
310    fn height(self, h: i32) -> Self {
311        unsafe { bindings::lv_obj_set_height(self.raw(), h) };
312        self
313    }
314
315    /// Set the position of the widget relative to its parent's top-left corner.
316    fn pos(self, x: i32, y: i32) -> Self {
317        unsafe { bindings::lv_obj_set_pos(self.raw(), x, y) };
318        self
319    }
320
321    /// Center the widget within its parent.
322    fn center(self) -> Self {
323        unsafe { bindings::lv_obj_center(self.raw()) };
324        self
325    }
326
327    /// Align the widget using an LVGL alignment constant and pixel offsets.
328    ///
329    /// `a` must be one of the `ALIGN_*` constants (e.g. [`ALIGN_CENTER`]).
330    fn align(self, a: u8, x_ofs: i32, y_ofs: i32) -> Self {
331        unsafe { bindings::lv_obj_align(self.raw(), a as _, x_ofs, y_ofs) };
332        self
333    }
334
335    /// Hide the widget by setting `LV_OBJ_FLAG_HIDDEN`.
336    fn hide(self) -> Self {
337        unsafe { bindings::lv_obj_add_flag(self.raw(), bindings::LV_OBJ_FLAG_HIDDEN) };
338        self
339    }
340
341    /// Make the widget visible by removing `LV_OBJ_FLAG_HIDDEN`.
342    fn show(self) -> Self {
343        unsafe { bindings::lv_obj_remove_flag(self.raw(), bindings::LV_OBJ_FLAG_HIDDEN) };
344        self
345    }
346
347    /// Show or hide the widget. Equivalent to calling [`show`](Layout::show) or [`hide`](Layout::hide).
348    fn visible(self, v: bool) -> Self {
349        if v { self.show() } else { self.hide() }
350    }
351
352    /// Add one or more LVGL object flags (bitwise OR of `LV_OBJ_FLAG_*` values).
353    fn add_flag(self, f: u32) -> Self {
354        unsafe { bindings::lv_obj_add_flag(self.raw(), f) };
355        self
356    }
357
358    /// Remove one or more LVGL object flags.
359    fn remove_flag(self, f: u32) -> Self {
360        unsafe { bindings::lv_obj_remove_flag(self.raw(), f) };
361        self
362    }
363
364    /// Add one or more LVGL object state bits (e.g. `LV_STATE_CHECKED`).
365    fn add_state(self, s: u32) -> Self {
366        unsafe { bindings::lv_obj_add_state(self.raw(), s as _) };
367        self
368    }
369
370    /// Remove one or more LVGL object state bits.
371    fn remove_state(self, s: u32) -> Self {
372        unsafe { bindings::lv_obj_remove_state(self.raw(), s as _) };
373        self
374    }
375
376    /// Enable or disable click events on the widget.
377    fn clickable(self, on: bool) -> Self {
378        if on {
379            self.add_flag(bindings::LV_OBJ_FLAG_CLICKABLE)
380        } else {
381            self.remove_flag(bindings::LV_OBJ_FLAG_CLICKABLE)
382        }
383    }
384
385    /// Configure this widget as a grid container.
386    ///
387    /// Both arrays must terminate with [`GRID_TEMPLATE_LAST`]. Track sizes
388    /// can be fixed pixel values, [`GRID_CONTENT`], or [`grid_fr`] for
389    /// fractional units. Switches the widget's layout to grid mode.
390    ///
391    /// # Safety
392    /// The caller must ensure `cols` and `rows` outlive the widget, since
393    /// LVGL retains the pointer rather than copying. `'static` slices are
394    /// the typical choice.
395    fn grid_dsc(self, cols: &'static [i32], rows: &'static [i32]) -> Self {
396        unsafe { bindings::lv_obj_set_grid_dsc_array(self.raw(), cols.as_ptr(), rows.as_ptr()) };
397        self
398    }
399
400    /// Place this widget into a cell of its parent grid.
401    #[allow(clippy::too_many_arguments)]
402    fn grid_cell(
403        self,
404        col_align: u32,
405        col_pos: i32,
406        col_span: i32,
407        row_align: u32,
408        row_pos: i32,
409        row_span: i32,
410    ) -> Self {
411        unsafe {
412            bindings::lv_obj_set_grid_cell(
413                self.raw(),
414                col_align as _,
415                col_pos,
416                col_span,
417                row_align as _,
418                row_pos,
419                row_span,
420            );
421        }
422        self
423    }
424
425    /// Configure this widget as a flex container with the given main-axis flow.
426    fn flex_flow(self, flow: FlexFlow) -> Self {
427        unsafe { bindings::lv_obj_set_flex_flow(self.raw(), flow as _) };
428        self
429    }
430
431    /// Set flex container alignment along the main, cross (per item), and
432    /// cross (per track) axes. Equivalent to `lv_obj_set_flex_align`.
433    fn flex_align(self, main: FlexAlign, cross: FlexAlign, track: FlexAlign) -> Self {
434        unsafe { bindings::lv_obj_set_flex_align(self.raw(), main as _, cross as _, track as _) };
435        self
436    }
437
438    /// Set this child's flex grow factor (0 disables growing).
439    fn flex_grow(self, grow: u8) -> Self {
440        unsafe { bindings::lv_obj_set_flex_grow(self.raw(), grow) };
441        self
442    }
443
444    /// Switch the widget's layout engine (None / Flex / Grid).
445    fn layout(self, kind: LayoutKind) -> Self {
446        unsafe { bindings::lv_obj_set_layout(self.raw(), kind as _) };
447        self
448    }
449
450    /// Scroll the widget so the given Y coordinate is visible. `anim` enables animation.
451    fn scroll_to_y(self, y: i32, anim: bool) -> Self {
452        unsafe {
453            bindings::lv_obj_scroll_to_y(self.raw(), y, anim);
454        }
455        self
456    }
457
458    /// Force layout recomputation immediately (for queries that need live values).
459    fn update_layout(self) -> Self {
460        unsafe { bindings::lv_obj_update_layout(self.raw()) };
461        self
462    }
463
464    /// Width of the inner content area (excludes paddings/scrollbars).
465    fn content_width(self) -> i32 {
466        unsafe { bindings::lv_obj_get_content_width(self.raw()) }
467    }
468
469    /// Distance the widget can still scroll towards its bottom edge.
470    fn scroll_bottom(self) -> i32 {
471        unsafe { bindings::lv_obj_get_scroll_bottom(self.raw()) }
472    }
473}
474
475impl<T: Widget> Layout for T {}
476
477// =========================================================================
478//  Styleable trait — fluent inline style setters (blanket impl on Widget)
479// =========================================================================
480
481/// Fluent inline style setters. Applied to `LV_PART_MAIN` by default.
482/// Blanket-implemented for all `Widget` types.
483pub trait Styleable: Widget + Sized {
484    /// Set the background color of the widget's main part.
485    fn bg_color(self, c: Color) -> Self {
486        unsafe { bindings::lv_obj_set_style_bg_color(self.raw(), c.to_raw(), PART_MAIN) };
487        self
488    }
489
490    /// Set the background opacity of the widget's main part (0 = transparent, 255 = opaque).
491    fn bg_opa(self, opa: u8) -> Self {
492        unsafe { bindings::lv_obj_set_style_bg_opa(self.raw(), opa, PART_MAIN) };
493        self
494    }
495
496    /// Set the border color of the widget's main part.
497    fn border_color(self, c: Color) -> Self {
498        unsafe { bindings::lv_obj_set_style_border_color(self.raw(), c.to_raw(), PART_MAIN) };
499        self
500    }
501
502    /// Set the border width in pixels.
503    fn border_width(self, w: i32) -> Self {
504        unsafe { bindings::lv_obj_set_style_border_width(self.raw(), w, PART_MAIN) };
505        self
506    }
507
508    /// Set the corner radius in pixels (use `LV_RADIUS_CIRCLE` for a pill shape).
509    fn radius(self, r: i32) -> Self {
510        unsafe { bindings::lv_obj_set_style_radius(self.raw(), r, PART_MAIN) };
511        self
512    }
513
514    /// Set equal padding on all four sides.
515    fn pad_all(self, p: i32) -> Self {
516        unsafe {
517            let r = self.raw();
518            bindings::lv_obj_set_style_pad_left(r, p, PART_MAIN);
519            bindings::lv_obj_set_style_pad_right(r, p, PART_MAIN);
520            bindings::lv_obj_set_style_pad_top(r, p, PART_MAIN);
521            bindings::lv_obj_set_style_pad_bottom(r, p, PART_MAIN);
522        }
523        self
524    }
525
526    /// Set equal left and right (horizontal) padding.
527    fn pad_hor(self, p: i32) -> Self {
528        unsafe {
529            let r = self.raw();
530            bindings::lv_obj_set_style_pad_left(r, p, PART_MAIN);
531            bindings::lv_obj_set_style_pad_right(r, p, PART_MAIN);
532        }
533        self
534    }
535
536    /// Set equal top and bottom (vertical) padding.
537    fn pad_ver(self, p: i32) -> Self {
538        unsafe {
539            let r = self.raw();
540            bindings::lv_obj_set_style_pad_top(r, p, PART_MAIN);
541            bindings::lv_obj_set_style_pad_bottom(r, p, PART_MAIN);
542        }
543        self
544    }
545
546    /// Set top padding.
547    fn pad_top(self, p: i32) -> Self {
548        unsafe { bindings::lv_obj_set_style_pad_top(self.raw(), p, PART_MAIN) };
549        self
550    }
551    /// Set bottom padding.
552    fn pad_bottom(self, p: i32) -> Self {
553        unsafe { bindings::lv_obj_set_style_pad_bottom(self.raw(), p, PART_MAIN) };
554        self
555    }
556    /// Set left padding.
557    fn pad_left(self, p: i32) -> Self {
558        unsafe { bindings::lv_obj_set_style_pad_left(self.raw(), p, PART_MAIN) };
559        self
560    }
561    /// Set right padding.
562    fn pad_right(self, p: i32) -> Self {
563        unsafe { bindings::lv_obj_set_style_pad_right(self.raw(), p, PART_MAIN) };
564        self
565    }
566
567    /// Set the row and column gap between flex children.
568    fn pad_gap(self, g: i32) -> Self {
569        unsafe {
570            let r = self.raw();
571            bindings::lv_obj_set_style_pad_row(r, g, PART_MAIN);
572            bindings::lv_obj_set_style_pad_column(r, g, PART_MAIN);
573        }
574        self
575    }
576
577    /// Set the text color for label-like widgets.
578    fn text_color(self, c: Color) -> Self {
579        unsafe { bindings::lv_obj_set_style_text_color(self.raw(), c.to_raw(), PART_MAIN) };
580        self
581    }
582
583    /// Set the text font. Use [`font_montserrat_32`] or [`font_montserrat_14`] for built-ins.
584    fn text_font(self, f: *const bindings::lv_font_t) -> Self {
585        unsafe { bindings::lv_obj_set_style_text_font(self.raw(), f, PART_MAIN) };
586        self
587    }
588
589    /// Apply a reusable [`Style`] to the given style selector (part + state bitmask).
590    ///
591    /// Takes `&Style` — LVGL reads the pointer but does not mutate the
592    /// style once added. `style` must outlive the widget (typical use:
593    /// store in a `InitCell<Style>` or `&'static`).
594    fn add_style(self, style: &Style, selector: u32) -> Self {
595        unsafe { bindings::lv_obj_add_style(self.raw(), style.ptr(), selector) };
596        self
597    }
598
599    /// Apply a vertical translation (post-layout offset, in pixels).
600    fn translate_y(self, v: i32, selector: u32) -> Self {
601        unsafe { bindings::lv_obj_set_style_translate_y(self.raw(), v, selector) };
602        self
603    }
604
605    /// Top margin in pixels.
606    fn margin_top(self, v: i32, selector: u32) -> Self {
607        unsafe { bindings::lv_obj_set_style_margin_top(self.raw(), v, selector) };
608        self
609    }
610    /// Bottom margin in pixels.
611    fn margin_bottom(self, v: i32, selector: u32) -> Self {
612        unsafe { bindings::lv_obj_set_style_margin_bottom(self.raw(), v, selector) };
613        self
614    }
615    /// Left margin in pixels.
616    fn margin_left(self, v: i32, selector: u32) -> Self {
617        unsafe { bindings::lv_obj_set_style_margin_left(self.raw(), v, selector) };
618        self
619    }
620    /// Right margin in pixels.
621    fn margin_right(self, v: i32, selector: u32) -> Self {
622        unsafe { bindings::lv_obj_set_style_margin_right(self.raw(), v, selector) };
623        self
624    }
625
626    /// Maximum height in pixels (caps `set_height`/content sizing).
627    fn max_height(self, v: i32, selector: u32) -> Self {
628        unsafe { bindings::lv_obj_set_style_max_height(self.raw(), v, selector) };
629        self
630    }
631
632    /// Arc opacity for arc-drawing widgets (0..=255).
633    fn arc_opa(self, opa: u8, selector: u32) -> Self {
634        unsafe { bindings::lv_obj_set_style_arc_opa(self.raw(), opa, selector) };
635        self
636    }
637
638    /// Whether the arc has rounded ends.
639    fn arc_rounded(self, rounded: bool, selector: u32) -> Self {
640        unsafe { bindings::lv_obj_set_style_arc_rounded(self.raw(), rounded, selector) };
641        self
642    }
643
644    /// Layered opacity (composites the whole subtree at the given alpha).
645    fn opa_layered(self, opa: u8, selector: u32) -> Self {
646        unsafe { bindings::lv_obj_set_style_opa_layered(self.raw(), opa, selector) };
647        self
648    }
649
650    /// Set the text alignment (`TEXT_ALIGN_*`) for the given selector.
651    fn text_align(self, align: u32, selector: u32) -> Self {
652        unsafe { bindings::lv_obj_set_style_text_align(self.raw(), align as _, selector) };
653        self
654    }
655
656    // ---- Selector variants of the common style setters ----
657
658    /// Background color with explicit selector (part + state bits).
659    fn bg_color_sel(self, c: Color, selector: u32) -> Self {
660        unsafe { bindings::lv_obj_set_style_bg_color(self.raw(), c.to_raw(), selector) };
661        self
662    }
663    /// Background opacity with explicit selector.
664    fn bg_opa_sel(self, opa: u8, selector: u32) -> Self {
665        unsafe { bindings::lv_obj_set_style_bg_opa(self.raw(), opa, selector) };
666        self
667    }
668    /// Border color with explicit selector.
669    fn border_color_sel(self, c: Color, selector: u32) -> Self {
670        unsafe { bindings::lv_obj_set_style_border_color(self.raw(), c.to_raw(), selector) };
671        self
672    }
673    /// Text color with explicit selector.
674    fn text_color_sel(self, c: Color, selector: u32) -> Self {
675        unsafe { bindings::lv_obj_set_style_text_color(self.raw(), c.to_raw(), selector) };
676        self
677    }
678    /// Arc track color with explicit selector.
679    fn arc_color_sel(self, c: Color, selector: u32) -> Self {
680        unsafe { bindings::lv_obj_set_style_arc_color(self.raw(), c.to_raw(), selector) };
681        self
682    }
683    /// Arc track width with explicit selector.
684    fn arc_width(self, w: i32, selector: u32) -> Self {
685        unsafe { bindings::lv_obj_set_style_arc_width(self.raw(), w, selector) };
686        self
687    }
688    /// Row gap (flex layout).
689    fn pad_row(self, p: i32) -> Self {
690        unsafe { bindings::lv_obj_set_style_pad_row(self.raw(), p, PART_MAIN) };
691        self
692    }
693    /// Column gap (flex layout).
694    fn pad_column(self, p: i32) -> Self {
695        unsafe { bindings::lv_obj_set_style_pad_column(self.raw(), p, PART_MAIN) };
696        self
697    }
698    /// Overall object opacity (0..=255).
699    fn set_opa(self, opa: u8) -> Self {
700        unsafe { bindings::lv_obj_set_style_opa(self.raw(), opa, PART_MAIN) };
701        self
702    }
703}
704
705impl<T: Widget> Styleable for T {}
706
707// =========================================================================
708//  EventCtx — borrowed view of a live lv_event_t
709// =========================================================================
710
711/// Borrowed view of an active `lv_event_t`. Valid only inside an event callback.
712///
713/// The lifetime parameter ties the context to the callback invocation —
714/// it cannot escape the handler.
715#[derive(Clone, Copy)]
716pub struct EventCtx<'a> {
717    raw: *mut bindings::lv_event_t,
718    _ph: core::marker::PhantomData<&'a ()>,
719}
720
721impl<'a> EventCtx<'a> {
722    /// Widget that originally fired the event (innermost).
723    pub fn target(self) -> Obj {
724        // lv_event_get_target() returns `*mut c_void` in real LVGL
725        // (lv_event_t typedef'd through a macro) but `*mut lv_obj_t` in
726        // the test stub; the explicit cast keeps both bindgen variants
727        // compiling.
728        let raw = unsafe { bindings::lv_event_get_target(self.raw) as *mut bindings::lv_obj_t };
729        Obj { raw }
730    }
731
732    /// Widget the user is interacting with — may differ from `target` for
733    /// bubbled events (this is the widget the callback was registered on).
734    pub fn current_target(self) -> Obj {
735        let raw =
736            unsafe { bindings::lv_event_get_current_target(self.raw) as *mut bindings::lv_obj_t };
737        Obj { raw }
738    }
739
740    /// Numeric event code (`LV_EVENT_*`).
741    pub fn code(self) -> bindings::lv_event_code_t {
742        unsafe { bindings::lv_event_get_code(self.raw) }
743    }
744
745    /// Event-specific parameter (opaque to safe callers — left raw because
746    /// the type depends on the event code; rarely needed in practice).
747    pub fn param_raw(self) -> *mut core::ffi::c_void {
748        unsafe { bindings::lv_event_get_param(self.raw) }
749    }
750}
751
752// =========================================================================
753//  Event handler soundness — why `fn` pointers and `'static EventHandler`?
754//
755// LVGL stores event handlers as raw C function pointers + a `void*` user
756// data, and invokes them later from the LVGL task.  That asynchrony forces
757// two constraints on the Rust side:
758//
759// 1. **No closure captures.** A `Box<dyn FnMut(...)>` would require us to
760//    leak the box and store a thin wrapper; we'd then have no safe way to
761//    free it when LVGL deletes the target widget.  Instead we pass plain
762//    `fn(EventCtx<'_>)` pointers (the stateless [`EventTarget::on_fn`]) or
763//    a `&'static EventHandler<T>` descriptor that bundles a static state
764//    cell with a `fn(&T, EventCtx<'_>)` (the stateful
765//    [`EventTarget::on_with`]).  Both shapes are `'static` by construction
766//    — there is no way to register a handler that outlives its captures.
767//
768// 2. **State must be `Send + Sync`.** `EventHandler<T>` requires
769//    `T: Send + Sync + 'static`; the trampoline calls
770//    `h.cell.try_get()` to fetch `&T` from an `InitCell<T>`, and LVGL may
771//    dispatch the event from a thread other than the one that registered
772//    the handler.  `Sync` makes shared `&T` reads sound; `'static` keeps
773//    the address stable for the trampoline's `&*ud` deref.
774//
775// The trampolines themselves (`event_trampoline_fn`,
776// `event_trampoline_with::<T>`) are `unsafe extern "C" fn` that cast the
777// `void*` back to its original Rust type.  The two `// SAFETY:` lines
778// inside each trampoline document why the cast is valid (the
779// corresponding `on_fn` / `on_with` set the pointer with the matching
780// type).
781// =========================================================================
782
783/// Static descriptor for a stateful event handler.
784///
785/// Bundles a `&'static InitCell<T>` of shared state with a `fn(&T, EventCtx)`
786/// user callback. Declare with the [`crate::event_handler!`] macro and pass to
787/// [`EventTarget::on_with`] / [`EventTarget::on_clicked_with`].
788pub struct EventHandler<T: 'static> {
789    cell: &'static crate::InitCell<T>,
790    user: fn(&T, EventCtx<'_>),
791}
792
793impl<T: 'static> EventHandler<T> {
794    /// Construct a handler descriptor — usable in `static` declarations.
795    pub const fn new(cell: &'static crate::InitCell<T>, user: fn(&T, EventCtx<'_>)) -> Self {
796        Self { cell, user }
797    }
798}
799
800// SAFETY: `EventHandler<T>` holds only `&'static InitCell<T>` (which is
801// itself `Sync` when `T: Send + Sync`) and a `fn(&T, EventCtx<'_>)`
802// pointer (functions are always `Send + Sync`).  No interior mutability
803// or thread-bound state — sharing an `&EventHandler<T>` across threads is
804// trivially sound.
805unsafe impl<T: Send + Sync + 'static> Sync for EventHandler<T> {}
806
807unsafe extern "C" fn event_trampoline_fn(e: *mut bindings::lv_event_t) {
808    let ud = unsafe { bindings::lv_event_get_user_data(e) };
809    if ud.is_null() {
810        return;
811    }
812    // SAFETY: `ud` was set by `EventTarget::on_fn` from a `fn(EventCtx)` pointer.
813    let cb: fn(EventCtx<'_>) = unsafe { core::mem::transmute(ud) };
814    cb(EventCtx {
815        raw: e,
816        _ph: core::marker::PhantomData,
817    });
818}
819
820unsafe extern "C" fn event_trampoline_with<T: Send + Sync + 'static>(e: *mut bindings::lv_event_t) {
821    let ud = unsafe { bindings::lv_event_get_user_data(e) } as *const EventHandler<T>;
822    if ud.is_null() {
823        return;
824    }
825    // SAFETY: `ud` points to a `'static EventHandler<T>` set by `EventTarget::on_with`.
826    let h = unsafe { &*ud };
827    if let Some(state) = h.cell.try_get() {
828        (h.user)(
829            state,
830            EventCtx {
831                raw: e,
832                _ph: core::marker::PhantomData,
833            },
834        );
835    }
836    // If the cell is uninitialized we silently drop the event — defensive
837    // posture matching the existing `if let Some(...) = CELL.try_get()` callsites.
838}
839
840// =========================================================================
841//  EventTarget trait — type-safe event callbacks (blanket impl on Widget)
842// =========================================================================
843
844/// Event callback registration. Uses `fn` pointers for `no_std` compatibility.
845/// Blanket-implemented for all `Widget` types.
846pub trait EventTarget: Widget + Sized {
847    /// Register a stateless safe handler for any LVGL event code.
848    fn on_fn(self, code: bindings::lv_event_code_t, handler: fn(EventCtx<'_>)) -> Self {
849        let ud = handler as *mut core::ffi::c_void;
850        unsafe { bindings::lv_obj_add_event_cb(self.raw(), Some(event_trampoline_fn), code, ud) };
851        self
852    }
853
854    /// Register a stateless safe handler for click events.
855    fn on_clicked(self, handler: fn(EventCtx<'_>)) -> Self {
856        self.on_fn(bindings::LV_EVENT_CLICKED, handler)
857    }
858
859    /// Register a stateless safe handler for value-changed events.
860    fn on_value_change(self, handler: fn(EventCtx<'_>)) -> Self {
861        self.on_fn(bindings::LV_EVENT_VALUE_CHANGED, handler)
862    }
863
864    /// Register a stateful safe handler — the handler receives the shared
865    /// state from the bundled `InitCell` and an `EventCtx`.
866    fn on_with<T: Send + Sync + 'static>(
867        self,
868        code: bindings::lv_event_code_t,
869        handler: &'static EventHandler<T>,
870    ) -> Self {
871        let ud = handler as *const EventHandler<T> as *mut core::ffi::c_void;
872        unsafe {
873            bindings::lv_obj_add_event_cb(self.raw(), Some(event_trampoline_with::<T>), code, ud);
874        }
875        self
876    }
877
878    /// Stateful click handler (see [`EventTarget::on_with`]).
879    fn on_clicked_with<T: Send + Sync + 'static>(self, handler: &'static EventHandler<T>) -> Self {
880        self.on_with(bindings::LV_EVENT_CLICKED, handler)
881    }
882
883    /// Stateful value-change handler (see [`EventTarget::on_with`]).
884    fn on_value_change_with<T: Send + Sync + 'static>(
885        self,
886        handler: &'static EventHandler<T>,
887    ) -> Self {
888        self.on_with(bindings::LV_EVENT_VALUE_CHANGED, handler)
889    }
890
891    /// **Legacy escape hatch** — register a raw `lv_event_cb_t`. Prefer
892    /// [`EventTarget::on_fn`] or [`EventTarget::on_with`] for typed safe
893    /// callbacks.
894    #[doc(hidden)]
895    fn on(
896        self,
897        code: bindings::lv_event_code_t,
898        cb: bindings::lv_event_cb_t,
899        user_data: *mut core::ffi::c_void,
900    ) -> Self {
901        unsafe { bindings::lv_obj_add_event_cb(self.raw(), cb, code, user_data) };
902        self
903    }
904}
905
906impl<T: Widget> EventTarget for T {}
907
908// =========================================================================
909//  Obj — base widget wrapper
910// =========================================================================
911
912/// Non-owning handle to an LVGL object (`lv_obj_t *`).
913///
914/// LVGL manages object lifetimes (parent owns children). No `Drop`.
915/// `Copy` enables fluent method chaining (`self -> Self`).
916#[derive(Clone, Copy)]
917pub struct Obj {
918    raw: *mut bindings::lv_obj_t,
919}
920
921impl Obj {
922    /// Wrap a raw LVGL object pointer.
923    ///
924    /// # Safety
925    /// The pointer must be valid and obtained from an LVGL function.
926    pub unsafe fn from_raw(raw: *mut bindings::lv_obj_t) -> Self {
927        Self { raw }
928    }
929
930    /// Get the raw pointer (for passing to FFI).
931    pub fn as_raw(self) -> *mut bindings::lv_obj_t {
932        self.raw
933    }
934
935    /// Create a plain container object.
936    pub fn create(parent: Obj) -> Self {
937        let raw = unsafe { bindings::lv_obj_create(parent.raw) };
938        Self { raw }
939    }
940
941    /// Delete this object and all its children.
942    pub fn del(self) {
943        unsafe { bindings::lv_obj_delete(self.raw) };
944    }
945
946    /// Delete all children of this object.
947    pub fn clean(self) {
948        unsafe { bindings::lv_obj_clean(self.raw) };
949    }
950
951    /// Get parent object.
952    pub fn parent(self) -> Self {
953        Self {
954            raw: unsafe { bindings::lv_obj_get_parent(self.raw) },
955        }
956    }
957
958    /// Get number of children.
959    pub fn child_count(self) -> u32 {
960        unsafe { bindings::lv_obj_get_child_count(self.raw) }
961    }
962
963    /// Get current width.
964    pub fn get_width(self) -> i32 {
965        unsafe { bindings::lv_obj_get_width(self.raw) }
966    }
967
968    /// Get current height.
969    pub fn get_height(self) -> i32 {
970        unsafe { bindings::lv_obj_get_height(self.raw) }
971    }
972
973    /// Set user data pointer.
974    pub fn set_user_data(self, data: *mut core::ffi::c_void) -> Self {
975        unsafe { bindings::lv_obj_set_user_data(self.raw, data) };
976        self
977    }
978
979    /// Get user data pointer.
980    pub fn get_user_data(self) -> *mut core::ffi::c_void {
981        unsafe { bindings::lv_obj_get_user_data(self.raw) }
982    }
983
984    /// Remove all inline and reused styles from this object.
985    pub fn remove_style_all(self) -> Self {
986        unsafe { bindings::lv_obj_remove_style_all(self.raw) };
987        self
988    }
989
990    /// Get the nth child (0-indexed). Returns an [`Obj`] wrapping the
991    /// LVGL pointer — may be null if out of range (check before using).
992    pub fn get_child(self, idx: i32) -> Obj {
993        let raw = unsafe { bindings::lv_obj_get_child(self.raw, idx) };
994        Obj { raw }
995    }
996
997    // Legacy API preserved for backward compatibility
998    /// Set the size of the widget (legacy, non-fluent variant).
999    pub fn set_size(self, w: i32, h: i32) {
1000        unsafe { bindings::lv_obj_set_size(self.raw, w, h) };
1001    }
1002
1003    /// Set the position of the widget (legacy, non-fluent variant).
1004    pub fn set_pos(self, x: i32, y: i32) {
1005        unsafe { bindings::lv_obj_set_pos(self.raw, x, y) };
1006    }
1007
1008    /// Set the background color for the given selector (legacy, non-fluent variant).
1009    pub fn set_style_bg_color(self, color: Color, selector: u32) {
1010        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, color.to_raw(), selector) };
1011    }
1012
1013    /// Set the text color for the given selector (legacy, non-fluent variant).
1014    pub fn set_style_text_color(self, color: Color, selector: u32) {
1015        unsafe { bindings::lv_obj_set_style_text_color(self.raw, color.to_raw(), selector) };
1016    }
1017
1018    /// Set the corner radius for the given selector (legacy, non-fluent variant).
1019    pub fn set_style_radius(self, radius: i32, selector: u32) {
1020        unsafe { bindings::lv_obj_set_style_radius(self.raw, radius, selector) };
1021    }
1022
1023    /// Set the text font for the given selector (legacy, non-fluent variant).
1024    pub fn set_style_text_font(self, font: *const bindings::lv_font_t, selector: u32) {
1025        unsafe { bindings::lv_obj_set_style_text_font(self.raw, font, selector) };
1026    }
1027}
1028
1029impl Widget for Obj {
1030    fn raw(self) -> *mut bindings::lv_obj_t {
1031        self.raw
1032    }
1033}
1034
1035// =========================================================================
1036//  Label
1037// =========================================================================
1038
1039/// LVGL label widget.
1040#[derive(Clone, Copy)]
1041pub struct Label {
1042    raw: *mut bindings::lv_obj_t,
1043}
1044
1045impl Label {
1046    /// Create a new label as a child of `parent`.
1047    pub fn create(parent: impl Widget) -> Self {
1048        let raw = unsafe { bindings::lv_label_create(parent.raw()) };
1049        Self { raw }
1050    }
1051
1052    /// Wrap a raw `lv_obj_t *` as a `Label`. The caller is responsible
1053    /// for ensuring the pointer was obtained from a Label widget.
1054    ///
1055    /// # Safety
1056    /// `raw` must point to a valid `lv_label_t` object.
1057    pub unsafe fn from_raw(raw: *mut bindings::lv_obj_t) -> Self {
1058        Self { raw }
1059    }
1060
1061    /// Set text from a null-terminated byte slice.
1062    pub fn set_text(self, text: &[u8]) {
1063        unsafe { bindings::lv_label_set_text(self.raw, text.as_ptr() as *const _) };
1064    }
1065
1066    /// Set text by pointer — caller-owned buffer must outlive the label.
1067    /// LVGL stores the pointer instead of duplicating, so re-rendering
1068    /// with the same buffer triggers a redraw without reallocation.
1069    pub fn set_text_static(self, text: &'static [u8]) {
1070        unsafe { bindings::lv_label_set_text_static(self.raw, text.as_ptr() as *const _) };
1071    }
1072
1073    /// Fluent: set text (copies string).
1074    pub fn text(self, txt: &[u8]) -> Self {
1075        unsafe { bindings::lv_label_set_text(self.raw, txt.as_ptr() as *const _) };
1076        self
1077    }
1078
1079    /// Fluent: set static text (pointer must remain valid).
1080    pub fn text_static(self, txt: &'static [u8]) -> Self {
1081        unsafe { bindings::lv_label_set_text_static(self.raw, txt.as_ptr() as *const _) };
1082        self
1083    }
1084
1085    /// Fluent: set font shorthand.
1086    pub fn font(self, f: *const bindings::lv_font_t) -> Self {
1087        unsafe { bindings::lv_obj_set_style_text_font(self.raw, f, PART_MAIN) };
1088        self
1089    }
1090
1091    /// Fluent: set text color shorthand.
1092    pub fn color(self, c: Color) -> Self {
1093        unsafe { bindings::lv_obj_set_style_text_color(self.raw, c.to_raw(), PART_MAIN) };
1094        self
1095    }
1096
1097    /// Fluent: set the long-text behaviour (wrap, scroll, dot, etc.).
1098    pub fn long_mode(self, mode: bindings::lv_label_long_mode_t) -> Self {
1099        unsafe { bindings::lv_label_set_long_mode(self.raw(), mode) };
1100        self
1101    }
1102}
1103
1104impl Widget for Label {
1105    fn raw(self) -> *mut bindings::lv_obj_t {
1106        self.raw
1107    }
1108}
1109
1110impl core::ops::Deref for Label {
1111    type Target = Obj;
1112    fn deref(&self) -> &Obj {
1113        // SAFETY: Label and Obj have identical layout (single *mut lv_obj_t)
1114        unsafe { &*(self as *const Label as *const Obj) }
1115    }
1116}
1117
1118// =========================================================================
1119//  Bar
1120// =========================================================================
1121
1122/// LVGL bar widget.
1123#[derive(Clone, Copy)]
1124pub struct Bar {
1125    raw: *mut bindings::lv_obj_t,
1126}
1127
1128impl Bar {
1129    /// Create a new bar widget as a child of `parent`.
1130    pub fn create(parent: impl Widget) -> Self {
1131        let raw = unsafe { bindings::lv_bar_create(parent.raw()) };
1132        Self { raw }
1133    }
1134
1135    /// Set bar value. `anim`: false = instant, true = animate.
1136    pub fn set_value(self, value: i32, anim: bool) {
1137        unsafe {
1138            #[allow(clippy::unnecessary_cast)]
1139            bindings::lv_bar_set_value(self.raw, value, anim as _);
1140        }
1141    }
1142
1143    /// Set bar range.
1144    pub fn set_range(self, min: i32, max: i32) {
1145        unsafe { bindings::lv_bar_set_range(self.raw, min, max) };
1146    }
1147
1148    /// Fluent: set value with animation.
1149    pub fn value(self, val: i32) -> Self {
1150        unsafe {
1151            #[allow(clippy::unnecessary_cast)]
1152            bindings::lv_bar_set_value(self.raw, val, true as _);
1153        }
1154        self
1155    }
1156
1157    /// Fluent: set value with animation control.
1158    pub fn value_anim(self, val: i32, anim: bool) -> Self {
1159        unsafe {
1160            #[allow(clippy::unnecessary_cast)]
1161            bindings::lv_bar_set_value(self.raw, val, anim as _);
1162        }
1163        self
1164    }
1165
1166    /// Fluent: set range.
1167    pub fn range(self, min: i32, max: i32) -> Self {
1168        unsafe { bindings::lv_bar_set_range(self.raw, min, max) };
1169        self
1170    }
1171
1172    /// Fluent: set indicator color.
1173    pub fn indicator_color(self, c: Color) -> Self {
1174        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, c.to_raw(), PART_INDICATOR) };
1175        self
1176    }
1177
1178    /// Fluent: set bar background color.
1179    pub fn bar_color(self, c: Color) -> Self {
1180        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, c.to_raw(), PART_MAIN) };
1181        self
1182    }
1183}
1184
1185impl Widget for Bar {
1186    fn raw(self) -> *mut bindings::lv_obj_t {
1187        self.raw
1188    }
1189}
1190
1191impl core::ops::Deref for Bar {
1192    type Target = Obj;
1193    fn deref(&self) -> &Obj {
1194        unsafe { &*(self as *const Bar as *const Obj) }
1195    }
1196}
1197
1198// =========================================================================
1199//  Box — container without scrollbar
1200// =========================================================================
1201
1202/// Plain container object with scrolling disabled.
1203#[derive(Clone, Copy)]
1204pub struct Box {
1205    raw: *mut bindings::lv_obj_t,
1206}
1207
1208impl Box {
1209    /// Create a plain container widget (scrolling disabled) as a child of `parent`.
1210    pub fn create(parent: impl Widget) -> Self {
1211        let raw = unsafe {
1212            let obj = bindings::lv_obj_create(parent.raw());
1213            bindings::lv_obj_remove_flag(obj, bindings::LV_OBJ_FLAG_SCROLLABLE);
1214            obj
1215        };
1216        Self { raw }
1217    }
1218}
1219
1220impl Widget for Box {
1221    fn raw(self) -> *mut bindings::lv_obj_t {
1222        self.raw
1223    }
1224}
1225
1226impl core::ops::Deref for Box {
1227    type Target = Obj;
1228    fn deref(&self) -> &Obj {
1229        unsafe { &*(self as *const Box as *const Obj) }
1230    }
1231}
1232
1233// =========================================================================
1234//  Button
1235// =========================================================================
1236
1237/// LVGL button widget — a clickable container.
1238///
1239/// Add a [`Label`] child to give the button text. Use [`Button::toggle_mode`]
1240/// to turn it into a checkable toggle whose state is tracked via
1241/// [`STATE_CHECKED`].
1242#[derive(Clone, Copy)]
1243pub struct Button {
1244    raw: *mut bindings::lv_obj_t,
1245}
1246
1247impl Button {
1248    /// Create a new button as a child of `parent`.
1249    pub fn create(parent: impl Widget) -> Self {
1250        let raw = unsafe { bindings::lv_button_create(parent.raw()) };
1251        Self { raw }
1252    }
1253
1254    /// Fluent: enable or disable toggle (checkable) behaviour.
1255    pub fn toggle_mode(self, on: bool) -> Self {
1256        if on {
1257            unsafe { bindings::lv_obj_add_flag(self.raw, bindings::LV_OBJ_FLAG_CHECKABLE) };
1258        } else {
1259            unsafe { bindings::lv_obj_remove_flag(self.raw, bindings::LV_OBJ_FLAG_CHECKABLE) };
1260        }
1261        self
1262    }
1263
1264    /// Fluent: set the checked state explicitly.
1265    pub fn checked(self, v: bool) -> Self {
1266        if v {
1267            unsafe { bindings::lv_obj_add_state(self.raw, STATE_CHECKED as _) };
1268        } else {
1269            unsafe { bindings::lv_obj_remove_state(self.raw, STATE_CHECKED as _) };
1270        }
1271        self
1272    }
1273
1274    /// Returns `true` if the button is currently in the checked state.
1275    pub fn is_checked(self) -> bool {
1276        unsafe { bindings::lv_obj_has_state(self.raw, STATE_CHECKED as _) }
1277    }
1278}
1279
1280impl Widget for Button {
1281    fn raw(self) -> *mut bindings::lv_obj_t {
1282        self.raw
1283    }
1284}
1285
1286impl core::ops::Deref for Button {
1287    type Target = Obj;
1288    fn deref(&self) -> &Obj {
1289        unsafe { &*(self as *const Button as *const Obj) }
1290    }
1291}
1292
1293// =========================================================================
1294//  Slider — interactive bar
1295// =========================================================================
1296
1297/// LVGL slider widget. Shares the bar value/range shape and adds knob
1298/// styling and drag interaction. Listen for changes with
1299/// [`EventTarget::on_value_change`].
1300#[derive(Clone, Copy)]
1301pub struct Slider {
1302    raw: *mut bindings::lv_obj_t,
1303}
1304
1305impl Slider {
1306    /// Create a new slider as a child of `parent`.
1307    pub fn create(parent: impl Widget) -> Self {
1308        let raw = unsafe { bindings::lv_slider_create(parent.raw()) };
1309        Self { raw }
1310    }
1311
1312    /// Fluent: set the current value (animated).
1313    pub fn value(self, val: i32) -> Self {
1314        unsafe {
1315            #[allow(clippy::unnecessary_cast)]
1316            bindings::lv_slider_set_value(self.raw, val, true as _);
1317        }
1318        self
1319    }
1320
1321    /// Fluent: set the current value with explicit animation control.
1322    pub fn value_anim(self, val: i32, anim: bool) -> Self {
1323        unsafe {
1324            #[allow(clippy::unnecessary_cast)]
1325            bindings::lv_slider_set_value(self.raw, val, anim as _);
1326        }
1327        self
1328    }
1329
1330    /// Fluent: set the min/max range.
1331    pub fn range(self, min: i32, max: i32) -> Self {
1332        unsafe { bindings::lv_slider_set_range(self.raw, min, max) };
1333        self
1334    }
1335
1336    /// Returns the slider's current value.
1337    pub fn get_value(self) -> i32 {
1338        unsafe { bindings::lv_slider_get_value(self.raw) }
1339    }
1340
1341    /// Fluent: set the filled indicator colour (`LV_PART_INDICATOR`).
1342    pub fn indicator_color(self, c: Color) -> Self {
1343        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, c.to_raw(), PART_INDICATOR) };
1344        self
1345    }
1346
1347    /// Fluent: set the knob colour (`LV_PART_KNOB`).
1348    pub fn knob_color(self, c: Color) -> Self {
1349        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, c.to_raw(), PART_KNOB) };
1350        self
1351    }
1352}
1353
1354impl Widget for Slider {
1355    fn raw(self) -> *mut bindings::lv_obj_t {
1356        self.raw
1357    }
1358}
1359
1360impl core::ops::Deref for Slider {
1361    type Target = Obj;
1362    fn deref(&self) -> &Obj {
1363        unsafe { &*(self as *const Slider as *const Obj) }
1364    }
1365}
1366
1367// =========================================================================
1368//  Switch — binary toggle
1369// =========================================================================
1370
1371/// LVGL switch widget (binary toggle). State is tracked via
1372/// [`STATE_CHECKED`]; listen for changes with
1373/// [`EventTarget::on_value_change`].
1374#[derive(Clone, Copy)]
1375pub struct Switch {
1376    raw: *mut bindings::lv_obj_t,
1377}
1378
1379impl Switch {
1380    /// Create a new switch as a child of `parent`.
1381    pub fn create(parent: impl Widget) -> Self {
1382        let raw = unsafe { bindings::lv_switch_create(parent.raw()) };
1383        Self { raw }
1384    }
1385
1386    /// Fluent: set the on/off state.
1387    pub fn checked(self, v: bool) -> Self {
1388        if v {
1389            unsafe { bindings::lv_obj_add_state(self.raw, STATE_CHECKED as _) };
1390        } else {
1391            unsafe { bindings::lv_obj_remove_state(self.raw, STATE_CHECKED as _) };
1392        }
1393        self
1394    }
1395
1396    /// Returns `true` when the switch is on.
1397    pub fn is_checked(self) -> bool {
1398        unsafe { bindings::lv_obj_has_state(self.raw, STATE_CHECKED as _) }
1399    }
1400}
1401
1402impl Widget for Switch {
1403    fn raw(self) -> *mut bindings::lv_obj_t {
1404        self.raw
1405    }
1406}
1407
1408impl core::ops::Deref for Switch {
1409    type Target = Obj;
1410    fn deref(&self) -> &Obj {
1411        unsafe { &*(self as *const Switch as *const Obj) }
1412    }
1413}
1414
1415// =========================================================================
1416//  Checkbox — labelled toggle
1417// =========================================================================
1418
1419/// LVGL checkbox widget — a labelled binary toggle.
1420#[derive(Clone, Copy)]
1421pub struct Checkbox {
1422    raw: *mut bindings::lv_obj_t,
1423}
1424
1425impl Checkbox {
1426    /// Create a new checkbox as a child of `parent`.
1427    pub fn create(parent: impl Widget) -> Self {
1428        let raw = unsafe { bindings::lv_checkbox_create(parent.raw()) };
1429        Self { raw }
1430    }
1431
1432    /// Fluent: set the label text (LVGL copies into its own buffer).
1433    pub fn text(self, txt: &[u8]) -> Self {
1434        unsafe { bindings::lv_checkbox_set_text(self.raw, txt.as_ptr() as *const _) };
1435        self
1436    }
1437
1438    /// Fluent: set the label text from a persistent static string (no copy).
1439    pub fn text_static(self, txt: &'static [u8]) -> Self {
1440        unsafe { bindings::lv_checkbox_set_text_static(self.raw, txt.as_ptr() as *const _) };
1441        self
1442    }
1443
1444    /// Fluent: set the checked state.
1445    pub fn checked(self, v: bool) -> Self {
1446        if v {
1447            unsafe { bindings::lv_obj_add_state(self.raw, STATE_CHECKED as _) };
1448        } else {
1449            unsafe { bindings::lv_obj_remove_state(self.raw, STATE_CHECKED as _) };
1450        }
1451        self
1452    }
1453
1454    /// Returns `true` if the checkbox is ticked.
1455    pub fn is_checked(self) -> bool {
1456        unsafe { bindings::lv_obj_has_state(self.raw, STATE_CHECKED as _) }
1457    }
1458}
1459
1460impl Widget for Checkbox {
1461    fn raw(self) -> *mut bindings::lv_obj_t {
1462        self.raw
1463    }
1464}
1465
1466impl core::ops::Deref for Checkbox {
1467    type Target = Obj;
1468    fn deref(&self) -> &Obj {
1469        unsafe { &*(self as *const Checkbox as *const Obj) }
1470    }
1471}
1472
1473// =========================================================================
1474//  Arc — circular slider / gauge
1475// =========================================================================
1476
1477/// LVGL arc widget — a circular gauge or slider. Track uses `arc_color`
1478/// on `LV_PART_MAIN`, the filled indicator uses `arc_color` on
1479/// `LV_PART_INDICATOR`, and the draggable knob uses `bg_color` on
1480/// `LV_PART_KNOB`.
1481#[derive(Clone, Copy)]
1482pub struct Arc {
1483    raw: *mut bindings::lv_obj_t,
1484}
1485
1486impl Arc {
1487    /// Create a new arc as a child of `parent`.
1488    pub fn create(parent: impl Widget) -> Self {
1489        let raw = unsafe { bindings::lv_arc_create(parent.raw()) };
1490        Self { raw }
1491    }
1492
1493    /// Fluent: set the current value.
1494    pub fn value(self, val: i32) -> Self {
1495        unsafe { bindings::lv_arc_set_value(self.raw, val) };
1496        self
1497    }
1498
1499    /// Fluent: set the min/max range.
1500    pub fn range(self, min: i32, max: i32) -> Self {
1501        unsafe { bindings::lv_arc_set_range(self.raw, min, max) };
1502        self
1503    }
1504
1505    /// Fluent: set the background arc angles in degrees.
1506    pub fn bg_angles(self, start: u32, end: u32) -> Self {
1507        unsafe { bindings::lv_arc_set_bg_angles(self.raw, start as _, end as _) };
1508        self
1509    }
1510
1511    /// Fluent: set the indicator arc angles in degrees.
1512    pub fn angles(self, start: u32, end: u32) -> Self {
1513        unsafe { bindings::lv_arc_set_angles(self.raw, start as _, end as _) };
1514        self
1515    }
1516
1517    /// Fluent: set the rotation offset of the arc in degrees.
1518    pub fn rotation(self, rot: u32) -> Self {
1519        unsafe { bindings::lv_arc_set_rotation(self.raw, rot as _) };
1520        self
1521    }
1522
1523    /// Returns the current value.
1524    pub fn get_value(self) -> i32 {
1525        unsafe { bindings::lv_arc_get_value(self.raw) }
1526    }
1527
1528    /// Fluent: set the track arc colour (`LV_PART_MAIN`).
1529    pub fn track_color(self, c: Color) -> Self {
1530        unsafe { bindings::lv_obj_set_style_arc_color(self.raw, c.to_raw(), PART_MAIN) };
1531        self
1532    }
1533
1534    /// Fluent: set the filled indicator arc colour (`LV_PART_INDICATOR`).
1535    pub fn indicator_color(self, c: Color) -> Self {
1536        unsafe { bindings::lv_obj_set_style_arc_color(self.raw, c.to_raw(), PART_INDICATOR) };
1537        self
1538    }
1539
1540    /// Fluent: set the indicator arc width in pixels.
1541    pub fn indicator_width(self, w: i32) -> Self {
1542        unsafe { bindings::lv_obj_set_style_arc_width(self.raw, w, PART_INDICATOR) };
1543        self
1544    }
1545
1546    /// Fluent: set the knob colour (`LV_PART_KNOB`).
1547    pub fn knob_color(self, c: Color) -> Self {
1548        unsafe { bindings::lv_obj_set_style_bg_color(self.raw, c.to_raw(), PART_KNOB) };
1549        self
1550    }
1551}
1552
1553impl Widget for Arc {
1554    fn raw(self) -> *mut bindings::lv_obj_t {
1555        self.raw
1556    }
1557}
1558
1559impl core::ops::Deref for Arc {
1560    type Target = Obj;
1561    fn deref(&self) -> &Obj {
1562        unsafe { &*(self as *const Arc as *const Obj) }
1563    }
1564}
1565
1566// =========================================================================
1567//  Msgbox — modal dialog
1568// =========================================================================
1569
1570/// LVGL message box widget — a modal dialog with optional title, body
1571/// text, close button, and footer buttons.
1572///
1573/// Passing a null parent to [`Msgbox::create`] centers the msgbox on the
1574/// active screen.
1575#[derive(Clone, Copy)]
1576pub struct Msgbox {
1577    raw: *mut bindings::lv_obj_t,
1578}
1579
1580impl Msgbox {
1581    /// Create a new msgbox. If `parent.raw()` is null, the msgbox is
1582    /// centered on the active screen.
1583    pub fn create(parent: impl Widget) -> Self {
1584        let raw = unsafe { bindings::lv_msgbox_create(parent.raw()) };
1585        Self { raw }
1586    }
1587
1588    /// Create a new top-level msgbox (centered on the active screen).
1589    pub fn create_modal() -> Self {
1590        let raw = unsafe { bindings::lv_msgbox_create(core::ptr::null_mut()) };
1591        Self { raw }
1592    }
1593
1594    /// Fluent: add a title in the header.
1595    pub fn add_title(self, txt: &[u8]) -> Self {
1596        unsafe { bindings::lv_msgbox_add_title(self.raw, txt.as_ptr() as *const _) };
1597        self
1598    }
1599
1600    /// Fluent: add body text.
1601    pub fn add_text(self, txt: &[u8]) -> Self {
1602        unsafe { bindings::lv_msgbox_add_text(self.raw, txt.as_ptr() as *const _) };
1603        self
1604    }
1605
1606    /// Fluent: add a close (X) button in the header.
1607    pub fn add_close_button(self) -> Self {
1608        unsafe { bindings::lv_msgbox_add_close_button(self.raw) };
1609        self
1610    }
1611
1612    /// Add a footer button with the given text. Returns the raw button
1613    /// handle for event handler attachment.
1614    pub fn add_footer_button(self, txt: &[u8]) -> Obj {
1615        let btn =
1616            unsafe { bindings::lv_msgbox_add_footer_button(self.raw, txt.as_ptr() as *const _) };
1617        unsafe { Obj::from_raw(btn) }
1618    }
1619
1620    /// Returns the content container.
1621    pub fn get_content(self) -> Obj {
1622        let c = unsafe { bindings::lv_msgbox_get_content(self.raw) };
1623        unsafe { Obj::from_raw(c) }
1624    }
1625
1626    /// Returns the header container.
1627    pub fn get_header(self) -> Obj {
1628        let h = unsafe { bindings::lv_msgbox_get_header(self.raw) };
1629        unsafe { Obj::from_raw(h) }
1630    }
1631
1632    /// Returns the footer container.
1633    pub fn get_footer(self) -> Obj {
1634        let f = unsafe { bindings::lv_msgbox_get_footer(self.raw) };
1635        unsafe { Obj::from_raw(f) }
1636    }
1637
1638    /// Close and delete the msgbox.
1639    pub fn close(self) {
1640        unsafe { bindings::lv_msgbox_close(self.raw) };
1641    }
1642}
1643
1644impl Widget for Msgbox {
1645    fn raw(self) -> *mut bindings::lv_obj_t {
1646        self.raw
1647    }
1648}
1649
1650impl core::ops::Deref for Msgbox {
1651    type Target = Obj;
1652    fn deref(&self) -> &Obj {
1653        unsafe { &*(self as *const Msgbox as *const Obj) }
1654    }
1655}
1656
1657// =========================================================================
1658//  Spinner — circular loading indicator
1659// =========================================================================
1660
1661/// LVGL spinner widget — a continuously rotating arc used as a loading
1662/// indicator.
1663#[derive(Clone, Copy)]
1664pub struct Spinner {
1665    raw: *mut bindings::lv_obj_t,
1666}
1667
1668impl Spinner {
1669    /// Create a new spinner as a child of `parent`.
1670    pub fn create(parent: impl Widget) -> Self {
1671        let raw = unsafe { bindings::lv_spinner_create(parent.raw()) };
1672        Self { raw }
1673    }
1674
1675    /// Fluent: set the animation period (ms) and indicator arc length (deg).
1676    pub fn anim_params(self, time_ms: u32, angle_deg: u32) -> Self {
1677        unsafe { bindings::lv_spinner_set_anim_params(self.raw, time_ms, angle_deg) };
1678        self
1679    }
1680}
1681
1682impl Widget for Spinner {
1683    fn raw(self) -> *mut bindings::lv_obj_t {
1684        self.raw
1685    }
1686}
1687
1688impl core::ops::Deref for Spinner {
1689    type Target = Obj;
1690    fn deref(&self) -> &Obj {
1691        unsafe { &*(self as *const Spinner as *const Obj) }
1692    }
1693}
1694
1695// =========================================================================
1696//  Led — colored indicator circle
1697// =========================================================================
1698
1699/// LVGL LED widget — a small colored indicator circle with adjustable
1700/// brightness.
1701#[derive(Clone, Copy)]
1702pub struct Led {
1703    raw: *mut bindings::lv_obj_t,
1704}
1705
1706impl Led {
1707    /// Create a new LED as a child of `parent`.
1708    pub fn create(parent: impl Widget) -> Self {
1709        let raw = unsafe { bindings::lv_led_create(parent.raw()) };
1710        Self { raw }
1711    }
1712
1713    /// Fluent: set the LED base colour.
1714    pub fn color(self, c: Color) -> Self {
1715        unsafe { bindings::lv_led_set_color(self.raw, c.to_raw()) };
1716        self
1717    }
1718
1719    /// Fluent: set the LED brightness (0–255).
1720    pub fn brightness(self, bright: u8) -> Self {
1721        unsafe { bindings::lv_led_set_brightness(self.raw, bright) };
1722        self
1723    }
1724
1725    /// Fluent: turn the LED on.
1726    pub fn on(self) -> Self {
1727        unsafe { bindings::lv_led_on(self.raw) };
1728        self
1729    }
1730
1731    /// Fluent: turn the LED off.
1732    pub fn off(self) -> Self {
1733        unsafe { bindings::lv_led_off(self.raw) };
1734        self
1735    }
1736
1737    /// Fluent: toggle on/off.
1738    pub fn toggle(self) -> Self {
1739        unsafe { bindings::lv_led_toggle(self.raw) };
1740        self
1741    }
1742
1743    /// Returns the current brightness.
1744    pub fn get_brightness(self) -> u8 {
1745        unsafe { bindings::lv_led_get_brightness(self.raw) }
1746    }
1747}
1748
1749impl Widget for Led {
1750    fn raw(self) -> *mut bindings::lv_obj_t {
1751        self.raw
1752    }
1753}
1754
1755impl core::ops::Deref for Led {
1756    type Target = Obj;
1757    fn deref(&self) -> &Obj {
1758        unsafe { &*(self as *const Led as *const Obj) }
1759    }
1760}
1761
1762// =========================================================================
1763//  Chart — line / bar / scatter chart
1764// =========================================================================
1765
1766/// LVGL chart type: no chart (empty).
1767pub const CHART_TYPE_NONE: u32 = 0;
1768/// LVGL chart type: line chart.
1769pub const CHART_TYPE_LINE: u32 = 1;
1770/// LVGL chart type: bar chart.
1771pub const CHART_TYPE_BAR: u32 = 2;
1772/// LVGL chart type: scatter chart.
1773pub const CHART_TYPE_SCATTER: u32 = 3;
1774
1775/// Chart axis: primary Y.
1776pub const CHART_AXIS_PRIMARY_Y: u32 = 0x00;
1777/// Chart axis: secondary Y.
1778pub const CHART_AXIS_SECONDARY_Y: u32 = 0x01;
1779/// Chart axis: primary X.
1780pub const CHART_AXIS_PRIMARY_X: u32 = 0x02;
1781/// Chart axis: secondary X.
1782pub const CHART_AXIS_SECONDARY_X: u32 = 0x04;
1783
1784/// Chart update mode: shift existing points left when adding.
1785pub const CHART_UPDATE_MODE_SHIFT: u32 = 0;
1786/// Chart update mode: wrap around (circular buffer).
1787pub const CHART_UPDATE_MODE_CIRCULAR: u32 = 1;
1788
1789/// Handle to an `lv_chart_series_t *`. Owned by the Chart; non-owning copy.
1790#[derive(Clone, Copy)]
1791pub struct Series {
1792    chart: *mut bindings::lv_obj_t,
1793    raw: *mut bindings::lv_chart_series_t,
1794}
1795
1796impl Series {
1797    /// Push a new value using the chart's current update mode.
1798    pub fn next_value(self, v: i32) -> Self {
1799        unsafe { bindings::lv_chart_set_next_value(self.chart, self.raw, v) };
1800        self
1801    }
1802
1803    /// Set a specific point index.
1804    pub fn set_value_by_idx(self, idx: u32, v: i32) -> Self {
1805        unsafe { bindings::lv_chart_set_series_value_by_id(self.chart, self.raw, idx, v) };
1806        self
1807    }
1808
1809    /// Returns the raw `lv_chart_series_t *` pointer.
1810    pub fn as_raw(self) -> *mut bindings::lv_chart_series_t {
1811        self.raw
1812    }
1813}
1814
1815// SAFETY: `Series` wraps an `lv_chart_series_t` pointer owned by its
1816// parent `Chart`.  Same contract as the widget Send/Sync block below —
1817// access only under `LvglGuard` (or from the LVGL task).
1818unsafe impl Send for Series {}
1819unsafe impl Sync for Series {}
1820
1821/// LVGL chart widget — line / bar / scatter with multiple series and axes.
1822#[derive(Clone, Copy)]
1823pub struct Chart {
1824    raw: *mut bindings::lv_obj_t,
1825}
1826
1827impl Chart {
1828    /// Create a new `Chart` widget as a child of `parent`.
1829    pub fn create(parent: impl Widget) -> Self {
1830        let raw = unsafe { bindings::lv_chart_create(parent.raw()) };
1831        Self { raw }
1832    }
1833
1834    /// Fluent: set the chart type (use `CHART_TYPE_*` constants).
1835    pub fn chart_type(self, t: u32) -> Self {
1836        unsafe { bindings::lv_chart_set_type(self.raw, t as _) };
1837        self
1838    }
1839
1840    /// Fluent: set the number of data points per series.
1841    pub fn point_count(self, count: u32) -> Self {
1842        unsafe { bindings::lv_chart_set_point_count(self.raw, count) };
1843        self
1844    }
1845
1846    /// Fluent: set the min/max range for an axis.
1847    pub fn range(self, axis: u32, min: i32, max: i32) -> Self {
1848        unsafe { bindings::lv_chart_set_axis_range(self.raw, axis as _, min, max) };
1849        self
1850    }
1851
1852    /// Fluent: set the update mode (shift or circular).
1853    pub fn update_mode(self, mode: u32) -> Self {
1854        unsafe { bindings::lv_chart_set_update_mode(self.raw, mode as _) };
1855        self
1856    }
1857
1858    /// Fluent: set horizontal/vertical division line count.
1859    pub fn div_line_count(self, hdiv: u8, vdiv: u8) -> Self {
1860        unsafe { bindings::lv_chart_set_div_line_count(self.raw, hdiv as u32, vdiv as u32) };
1861        self
1862    }
1863
1864    /// Add a new series with the given color and axis binding.
1865    pub fn add_series(self, color: Color, axis: u32) -> Series {
1866        let raw = unsafe { bindings::lv_chart_add_series(self.raw, color.to_raw(), axis as _) };
1867        Series {
1868            chart: self.raw,
1869            raw,
1870        }
1871    }
1872
1873    /// Remove a series from the chart.
1874    pub fn remove_series(self, s: Series) -> Self {
1875        unsafe { bindings::lv_chart_remove_series(self.raw, s.raw) };
1876        self
1877    }
1878}
1879
1880impl Widget for Chart {
1881    fn raw(self) -> *mut bindings::lv_obj_t {
1882        self.raw
1883    }
1884}
1885
1886impl core::ops::Deref for Chart {
1887    type Target = Obj;
1888    fn deref(&self) -> &Obj {
1889        unsafe { &*(self as *const Chart as *const Obj) }
1890    }
1891}
1892
1893// =========================================================================
1894//  Calendar — month-grid date picker
1895// =========================================================================
1896
1897/// LVGL calendar widget — month-grid date picker with optional
1898/// arrow/dropdown navigation header.
1899#[derive(Clone, Copy)]
1900pub struct Calendar {
1901    raw: *mut bindings::lv_obj_t,
1902}
1903
1904impl Calendar {
1905    /// Create a new `Calendar` widget as a child of `parent`.
1906    pub fn create(parent: impl Widget) -> Self {
1907        let raw = unsafe { bindings::lv_calendar_create(parent.raw()) };
1908        Self { raw }
1909    }
1910
1911    /// Fluent: set today's date (highlighted in the grid).
1912    pub fn today(self, year: u32, month: u32, day: u32) -> Self {
1913        unsafe { bindings::lv_calendar_set_today_date(self.raw, year, month, day) };
1914        self
1915    }
1916
1917    /// Fluent: set the currently visible month.
1918    pub fn showed(self, year: u32, month: u32) -> Self {
1919        unsafe { bindings::lv_calendar_set_month_shown(self.raw, year, month) };
1920        self
1921    }
1922
1923    /// Returns the last date the user pressed, as `(y, m, d)`, or `None`.
1924    pub fn get_pressed_date(self) -> Option<(u32, u32, u32)> {
1925        let mut date: bindings::lv_calendar_date_t = unsafe { core::mem::zeroed() };
1926        let ok = unsafe { bindings::lv_calendar_get_pressed_date(self.raw, &mut date) };
1927        // LVGL's lv_result_t: 1 = OK, 0 = INVALID. Non-zero means we got a date.
1928        if ok != 0 {
1929            // lv_calendar_date_t fields are uint16_t/uint8_t in real
1930            // LVGL but bindgen lifts them to u32 in the test stub; the
1931            // `as u32` keeps both variants compiling (the no-op cast in
1932            // the stub is silenced by clippy::unnecessary_cast at the
1933            // crate root).
1934            Some((date.year as u32, date.month as u32, date.day as u32))
1935        } else {
1936            None
1937        }
1938    }
1939
1940    /// Add an arrow-header navigation bar as a child. Returns the header.
1941    pub fn add_header_arrow(self) -> Obj {
1942        let raw = unsafe { bindings::lv_calendar_add_header_arrow(self.raw) };
1943        unsafe { Obj::from_raw(raw) }
1944    }
1945
1946    /// Add a dropdown-header navigation bar as a child. Returns the header.
1947    pub fn add_header_dropdown(self) -> Obj {
1948        let raw = unsafe { bindings::lv_calendar_add_header_dropdown(self.raw) };
1949        unsafe { Obj::from_raw(raw) }
1950    }
1951
1952    /// Fluent: mark a set of dates as highlighted. LVGL stores the pointer
1953    /// instead of copying, so `dates` must outlive the calendar.
1954    pub fn highlighted_dates(self, dates: &'static [bindings::lv_calendar_date_t]) -> Self {
1955        unsafe {
1956            bindings::lv_calendar_set_highlighted_dates(
1957                self.raw(),
1958                dates.as_ptr().cast_mut(),
1959                dates.len() as _,
1960            );
1961        }
1962        self
1963    }
1964}
1965
1966impl Widget for Calendar {
1967    fn raw(self) -> *mut bindings::lv_obj_t {
1968        self.raw
1969    }
1970}
1971
1972impl core::ops::Deref for Calendar {
1973    type Target = Obj;
1974    fn deref(&self) -> &Obj {
1975        unsafe { &*(self as *const Calendar as *const Obj) }
1976    }
1977}
1978
1979// =========================================================================
1980//  Canvas — user-buffer drawing surface
1981// =========================================================================
1982
1983/// LVGL canvas — a widget backed by a user-supplied pixel buffer.
1984///
1985/// The caller must keep the buffer alive for the lifetime of the widget.
1986/// Buffer size is `w * h * bytes_per_pixel(cf)`.
1987///
1988/// Initial cut exposes buffer setup, background fill, per-pixel writes,
1989/// and the layer init/finish dance that LVGL 9 requires for draw
1990/// operations. `draw_rect/arc/label` wrappers can be added later.
1991#[derive(Clone, Copy)]
1992pub struct Canvas {
1993    raw: *mut bindings::lv_obj_t,
1994}
1995
1996impl Canvas {
1997    /// Create a new `Canvas` widget as a child of `parent`.
1998    pub fn create(parent: impl Widget) -> Self {
1999        let raw = unsafe { bindings::lv_canvas_create(parent.raw()) };
2000        Self { raw }
2001    }
2002
2003    /// **Legacy escape hatch** — prefer [`Canvas::set_buffer`] which takes
2004    /// a typed, lifetime-tracked [`CanvasBuffer`]. Retained for code
2005    /// that must supply a raw pointer (e.g. hardware-allocated framebuffers).
2006    ///
2007    /// # Safety
2008    /// `buf` must remain valid for the lifetime of the canvas widget.
2009    /// The buffer size must be exactly `w * h * bytes_per_pixel(cf)`.
2010    #[doc(hidden)]
2011    pub unsafe fn buffer(
2012        self,
2013        buf: *mut core::ffi::c_void,
2014        w: i32,
2015        h: i32,
2016        cf: bindings::lv_color_format_t,
2017    ) -> Self {
2018        unsafe { bindings::lv_canvas_set_buffer(self.raw, buf, w, h, cf) };
2019        self
2020    }
2021
2022    /// Fill the entire canvas with the given colour and opacity.
2023    pub fn fill_bg(self, color: Color, opa: u8) -> Self {
2024        unsafe { bindings::lv_canvas_fill_bg(self.raw, color.to_raw(), opa) };
2025        self
2026    }
2027
2028    /// Set a single pixel to the given colour.
2029    pub fn set_pixel(self, x: i32, y: i32, color: Color) -> Self {
2030        unsafe { bindings::lv_canvas_set_px(self.raw, x, y, color.to_raw(), 255) };
2031        self
2032    }
2033
2034    /// Returns the raw `lv_obj_t *` for low-level canvas operations
2035    /// (e.g. passing to `lv_canvas_init_layer` / draw descriptor calls
2036    /// from other crates).
2037    pub fn raw_obj(self) -> *mut bindings::lv_obj_t {
2038        self.raw
2039    }
2040
2041    /// Fluent: initialise a draw layer bound to this canvas. Pair with
2042    /// [`Canvas::finish_layer`] around low-level draw operations.
2043    pub fn init_layer(self, layer: *mut bindings::lv_layer_t) -> Self {
2044        unsafe { bindings::lv_canvas_init_layer(self.raw(), layer) };
2045        self
2046    }
2047
2048    /// Fluent: flush a draw layer previously opened with
2049    /// [`Canvas::init_layer`], committing its draw tasks to the canvas.
2050    pub fn finish_layer(self, layer: *mut bindings::lv_layer_t) -> Self {
2051        unsafe { bindings::lv_canvas_finish_layer(self.raw(), layer) };
2052        self
2053    }
2054}
2055
2056impl Widget for Canvas {
2057    fn raw(self) -> *mut bindings::lv_obj_t {
2058        self.raw
2059    }
2060}
2061
2062impl core::ops::Deref for Canvas {
2063    type Target = Obj;
2064    fn deref(&self) -> &Obj {
2065        unsafe { &*(self as *const Canvas as *const Obj) }
2066    }
2067}
2068
2069// =========================================================================
2070//  Table — 2D data grid
2071// =========================================================================
2072
2073/// LVGL table — 2D grid of cells with per-cell text.
2074#[derive(Clone, Copy)]
2075pub struct Table {
2076    raw: *mut bindings::lv_obj_t,
2077}
2078
2079impl Table {
2080    /// Create a new `Table` widget as a child of `parent`.
2081    pub fn create(parent: impl Widget) -> Self {
2082        let raw = unsafe { bindings::lv_table_create(parent.raw()) };
2083        Self { raw }
2084    }
2085
2086    /// Fluent: set a cell's text (null-terminated byte slice).
2087    pub fn cell_value(self, row: u32, col: u32, txt: &[u8]) -> Self {
2088        unsafe { bindings::lv_table_set_cell_value(self.raw, row, col, txt.as_ptr() as *const _) };
2089        self
2090    }
2091
2092    /// Fluent: set the row count.
2093    pub fn row_count(self, n: u32) -> Self {
2094        unsafe { bindings::lv_table_set_row_count(self.raw, n) };
2095        self
2096    }
2097
2098    /// Fluent: set the column count.
2099    pub fn column_count(self, n: u32) -> Self {
2100        unsafe { bindings::lv_table_set_column_count(self.raw, n) };
2101        self
2102    }
2103
2104    /// Fluent: set the pixel width of a column.
2105    pub fn column_width(self, col: u32, w: i32) -> Self {
2106        unsafe { bindings::lv_table_set_column_width(self.raw, col, w) };
2107        self
2108    }
2109
2110    /// Returns the total row count.
2111    pub fn get_row_count(self) -> u32 {
2112        unsafe { bindings::lv_table_get_row_count(self.raw) }
2113    }
2114
2115    /// Returns the total column count.
2116    pub fn get_column_count(self) -> u32 {
2117        unsafe { bindings::lv_table_get_column_count(self.raw) }
2118    }
2119
2120    /// Returns the pixel width of a specific column.
2121    pub fn get_column_width(self, col: u32) -> i32 {
2122        unsafe { bindings::lv_table_get_column_width(self.raw, col) }
2123    }
2124
2125    /// Returns a cell's text as a raw C pointer owned by LVGL. The pointer
2126    /// is invalidated by any subsequent mutation of the table.
2127    pub fn get_cell_value(self, row: u32, col: u32) -> *const core::ffi::c_char {
2128        unsafe { bindings::lv_table_get_cell_value(self.raw(), row, col) }
2129    }
2130}
2131
2132impl Widget for Table {
2133    fn raw(self) -> *mut bindings::lv_obj_t {
2134        self.raw
2135    }
2136}
2137
2138impl core::ops::Deref for Table {
2139    type Target = Obj;
2140    fn deref(&self) -> &Obj {
2141        unsafe { &*(self as *const Table as *const Obj) }
2142    }
2143}
2144
2145// =========================================================================
2146//  Tabview — tabbed container with swipeable pages
2147// =========================================================================
2148
2149/// LVGL tabview — tabbed container with swipeable pages.
2150#[derive(Clone, Copy)]
2151pub struct Tabview {
2152    raw: *mut bindings::lv_obj_t,
2153}
2154
2155impl Tabview {
2156    /// Create a new `Tabview` widget as a child of `parent`.
2157    pub fn create(parent: impl Widget) -> Self {
2158        let raw = unsafe { bindings::lv_tabview_create(parent.raw()) };
2159        Self { raw }
2160    }
2161
2162    /// Add a new tab and return its content container as an [`Obj`].
2163    pub fn add_tab(self, name: &[u8]) -> Obj {
2164        let raw = unsafe { bindings::lv_tabview_add_tab(self.raw, name.as_ptr() as *const _) };
2165        unsafe { Obj::from_raw(raw) }
2166    }
2167
2168    /// Fluent: rename an existing tab by index.
2169    pub fn rename_tab(self, idx: u32, name: &[u8]) -> Self {
2170        unsafe { bindings::lv_tabview_set_tab_text(self.raw, idx, name.as_ptr() as *const _) };
2171        self
2172    }
2173
2174    /// Fluent: set the active tab index. `anim` = true to animate the switch.
2175    pub fn set_active(self, idx: u32, anim: bool) -> Self {
2176        unsafe {
2177            #[allow(clippy::unnecessary_cast)]
2178            bindings::lv_tabview_set_active(self.raw, idx, anim as _);
2179        }
2180        self
2181    }
2182
2183    /// Fluent: set the tab bar position. Use an `LV_DIR_*` value.
2184    pub fn tab_bar_position(self, dir: bindings::lv_dir_t) -> Self {
2185        unsafe { bindings::lv_tabview_set_tab_bar_position(self.raw, dir) };
2186        self
2187    }
2188
2189    /// Fluent: set the tab bar size in pixels.
2190    pub fn tab_bar_size(self, size: i32) -> Self {
2191        unsafe { bindings::lv_tabview_set_tab_bar_size(self.raw, size) };
2192        self
2193    }
2194
2195    /// Returns the total number of tabs.
2196    pub fn get_tab_count(self) -> u32 {
2197        unsafe { bindings::lv_tabview_get_tab_count(self.raw) }
2198    }
2199
2200    /// Returns the currently active tab index.
2201    pub fn get_tab_active(self) -> u32 {
2202        unsafe { bindings::lv_tabview_get_tab_active(self.raw) }
2203    }
2204
2205    /// Returns the content container that houses all tabs' content areas.
2206    pub fn get_content(self) -> Obj {
2207        let raw = unsafe { bindings::lv_tabview_get_content(self.raw) };
2208        unsafe { Obj::from_raw(raw) }
2209    }
2210}
2211
2212impl Widget for Tabview {
2213    fn raw(self) -> *mut bindings::lv_obj_t {
2214        self.raw
2215    }
2216}
2217
2218impl core::ops::Deref for Tabview {
2219    type Target = Obj;
2220    fn deref(&self) -> &Obj {
2221        unsafe { &*(self as *const Tabview as *const Obj) }
2222    }
2223}
2224
2225// =========================================================================
2226//  List — scrollable list of buttons and text headings
2227// =========================================================================
2228
2229/// LVGL list — scrollable list of text headings and icon+text buttons.
2230#[derive(Clone, Copy)]
2231pub struct List {
2232    raw: *mut bindings::lv_obj_t,
2233}
2234
2235impl List {
2236    /// Create a new `List` widget as a child of `parent`.
2237    pub fn create(parent: impl Widget) -> Self {
2238        let raw = unsafe { bindings::lv_list_create(parent.raw()) };
2239        Self { raw }
2240    }
2241
2242    /// Add a text heading row. Returns the label as an [`Obj`].
2243    pub fn add_text(self, text: &[u8]) -> Obj {
2244        let raw = unsafe { bindings::lv_list_add_text(self.raw, text.as_ptr() as *const _) };
2245        unsafe { Obj::from_raw(raw) }
2246    }
2247
2248    /// Add a button row with an optional icon. Returns the button as an [`Obj`].
2249    pub fn add_button(self, icon: Option<ImageSrc>, text: &[u8]) -> Obj {
2250        let icon_ptr = icon.map_or(core::ptr::null(), ImageSrc::raw_ptr);
2251        let raw =
2252            unsafe { bindings::lv_list_add_button(self.raw, icon_ptr, text.as_ptr() as *const _) };
2253        unsafe { Obj::from_raw(raw) }
2254    }
2255
2256    /// Returns a list button's text as a raw C pointer owned by LVGL. The
2257    /// pointer is invalidated by any subsequent mutation of the list.
2258    pub fn get_button_text(self, btn: Obj) -> *const core::ffi::c_char {
2259        unsafe { bindings::lv_list_get_button_text(self.raw(), btn.as_raw()) }
2260    }
2261}
2262
2263impl Widget for List {
2264    fn raw(self) -> *mut bindings::lv_obj_t {
2265        self.raw
2266    }
2267}
2268
2269impl core::ops::Deref for List {
2270    type Target = Obj;
2271    fn deref(&self) -> &Obj {
2272        unsafe { &*(self as *const List as *const Obj) }
2273    }
2274}
2275
2276// =========================================================================
2277//  Textarea — text input
2278// =========================================================================
2279
2280/// LVGL textarea — single- or multi-line text input with placeholder,
2281/// password mode, max length, and accepted-character filter.
2282#[derive(Clone, Copy)]
2283pub struct Textarea {
2284    raw: *mut bindings::lv_obj_t,
2285}
2286
2287impl Textarea {
2288    /// Create a new `Textarea` widget as a child of `parent`.
2289    pub fn create(parent: impl Widget) -> Self {
2290        let raw = unsafe { bindings::lv_textarea_create(parent.raw()) };
2291        Self { raw }
2292    }
2293
2294    /// Fluent: replace the text (null-terminated byte slice).
2295    pub fn text(self, txt: &[u8]) -> Self {
2296        unsafe { bindings::lv_textarea_set_text(self.raw, txt.as_ptr() as *const _) };
2297        self
2298    }
2299
2300    /// Fluent: append text.
2301    pub fn add_text(self, txt: &[u8]) -> Self {
2302        unsafe { bindings::lv_textarea_add_text(self.raw, txt.as_ptr() as *const _) };
2303        self
2304    }
2305
2306    /// Fluent: set placeholder shown while empty.
2307    pub fn placeholder(self, txt: &[u8]) -> Self {
2308        unsafe { bindings::lv_textarea_set_placeholder_text(self.raw, txt.as_ptr() as *const _) };
2309        self
2310    }
2311
2312    /// Fluent: toggle single-line mode.
2313    pub fn one_line(self, en: bool) -> Self {
2314        unsafe { bindings::lv_textarea_set_one_line(self.raw, en) };
2315        self
2316    }
2317
2318    /// Fluent: enable password masking.
2319    pub fn password_mode(self, en: bool) -> Self {
2320        unsafe { bindings::lv_textarea_set_password_mode(self.raw, en) };
2321        self
2322    }
2323
2324    /// Fluent: set max character count (0 = unlimited).
2325    pub fn max_length(self, n: u32) -> Self {
2326        unsafe { bindings::lv_textarea_set_max_length(self.raw, n) };
2327        self
2328    }
2329
2330    /// Fluent: restrict input to characters in `list` (null pointer = any).
2331    pub fn accepted_chars(self, list: &[u8]) -> Self {
2332        unsafe { bindings::lv_textarea_set_accepted_chars(self.raw, list.as_ptr() as *const _) };
2333        self
2334    }
2335
2336    /// Fluent: move cursor to character position.
2337    pub fn cursor_pos(self, pos: i32) -> Self {
2338        unsafe { bindings::lv_textarea_set_cursor_pos(self.raw, pos) };
2339        self
2340    }
2341
2342    /// Fluent: enable moving cursor by clicking.
2343    pub fn cursor_click_pos(self, en: bool) -> Self {
2344        unsafe { bindings::lv_textarea_set_cursor_click_pos(self.raw, en) };
2345        self
2346    }
2347
2348    /// Returns the current text as a raw C pointer owned by LVGL. The
2349    /// pointer is invalidated by any subsequent mutation of the textarea.
2350    pub fn get_text_ptr(&self) -> *const core::ffi::c_char {
2351        unsafe { bindings::lv_textarea_get_text(self.raw) }
2352    }
2353
2354    /// Returns the current cursor position.
2355    pub fn get_cursor_pos(self) -> u32 {
2356        unsafe { bindings::lv_textarea_get_cursor_pos(self.raw) }
2357    }
2358
2359    /// Returns `true` if password masking is enabled.
2360    pub fn is_password_mode(self) -> bool {
2361        unsafe { bindings::lv_textarea_get_password_mode(self.raw) }
2362    }
2363
2364    /// Returns `true` if the textarea is in single-line mode.
2365    pub fn is_one_line(self) -> bool {
2366        unsafe { bindings::lv_textarea_get_one_line(self.raw) }
2367    }
2368
2369    /// Fluent: append a single Unicode codepoint.
2370    pub fn add_char(self, c: u32) -> Self {
2371        unsafe { bindings::lv_textarea_add_char(self.raw, c) };
2372        self
2373    }
2374
2375    /// Fluent: delete the character before the cursor.
2376    pub fn delete_char(self) -> Self {
2377        unsafe { bindings::lv_textarea_delete_char(self.raw) };
2378        self
2379    }
2380}
2381
2382impl Widget for Textarea {
2383    fn raw(self) -> *mut bindings::lv_obj_t {
2384        self.raw
2385    }
2386}
2387
2388impl core::ops::Deref for Textarea {
2389    type Target = Obj;
2390    fn deref(&self) -> &Obj {
2391        unsafe { &*(self as *const Textarea as *const Obj) }
2392    }
2393}
2394
2395// =========================================================================
2396//  Dropdown — click-to-open selection list
2397// =========================================================================
2398
2399/// LVGL dropdown — click opens a popup list of options (newline-separated).
2400#[derive(Clone, Copy)]
2401pub struct Dropdown {
2402    raw: *mut bindings::lv_obj_t,
2403}
2404
2405impl Dropdown {
2406    /// Create a new `Dropdown` widget as a child of `parent`.
2407    pub fn create(parent: impl Widget) -> Self {
2408        let raw = unsafe { bindings::lv_dropdown_create(parent.raw()) };
2409        Self { raw }
2410    }
2411
2412    /// Fluent: set options from a newline-separated string (LVGL copies it).
2413    pub fn options(self, opts: &[u8]) -> Self {
2414        unsafe { bindings::lv_dropdown_set_options(self.raw, opts.as_ptr() as *const _) };
2415        self
2416    }
2417
2418    /// Fluent: set options from a persistent newline-separated string (no copy).
2419    pub fn options_static(self, opts: &'static [u8]) -> Self {
2420        unsafe { bindings::lv_dropdown_set_options_static(self.raw, opts.as_ptr() as *const _) };
2421        self
2422    }
2423
2424    /// Fluent: insert a single option at `pos`.
2425    pub fn add_option(self, opt: &[u8], pos: u32) -> Self {
2426        unsafe { bindings::lv_dropdown_add_option(self.raw, opt.as_ptr() as *const _, pos) };
2427        self
2428    }
2429
2430    /// Fluent: clear the entire option list.
2431    pub fn clear_options(self) -> Self {
2432        unsafe { bindings::lv_dropdown_clear_options(self.raw) };
2433        self
2434    }
2435
2436    /// Fluent: set the selected index.
2437    pub fn selected(self, sel: u32) -> Self {
2438        unsafe { bindings::lv_dropdown_set_selected(self.raw, sel) };
2439        self
2440    }
2441
2442    /// Fluent: set popup direction.
2443    pub fn dir(self, d: bindings::lv_dir_t) -> Self {
2444        unsafe { bindings::lv_dropdown_set_dir(self.raw, d) };
2445        self
2446    }
2447
2448    /// Returns the currently selected index.
2449    pub fn get_selected(self) -> u32 {
2450        unsafe { bindings::lv_dropdown_get_selected(self.raw) }
2451    }
2452
2453    /// Returns the total option count.
2454    pub fn get_option_count(self) -> u32 {
2455        unsafe { bindings::lv_dropdown_get_option_count(self.raw) }
2456    }
2457
2458    /// Fills `buf` with the currently selected option text.
2459    pub fn get_selected_str(self, buf: &mut [u8]) {
2460        unsafe {
2461            bindings::lv_dropdown_get_selected_str(
2462                self.raw,
2463                buf.as_mut_ptr() as *mut _,
2464                buf.len() as u32,
2465            );
2466        }
2467    }
2468
2469    /// Returns `true` if the popup list is currently expanded.
2470    pub fn is_open(self) -> bool {
2471        unsafe { bindings::lv_dropdown_is_open(self.raw) }
2472    }
2473
2474    /// Expands the popup list.
2475    pub fn open(self) {
2476        unsafe { bindings::lv_dropdown_open(self.raw) };
2477    }
2478
2479    /// Collapses the popup list.
2480    pub fn close(self) {
2481        unsafe { bindings::lv_dropdown_close(self.raw) };
2482    }
2483
2484    /// Fluent: set the icon/symbol shown on the dropdown button (e.g. an
2485    /// `LV_SYMBOL_*` string or an image source).
2486    pub fn symbol(self, symbol: *const core::ffi::c_void) -> Self {
2487        unsafe { bindings::lv_dropdown_set_symbol(self.raw(), symbol) };
2488        self
2489    }
2490
2491    /// Returns the newline-separated option list as a raw C pointer owned
2492    /// by LVGL. The pointer is invalidated by any subsequent mutation.
2493    pub fn get_options(self) -> *const core::ffi::c_char {
2494        unsafe { bindings::lv_dropdown_get_options(self.raw()) }
2495    }
2496}
2497
2498impl Widget for Dropdown {
2499    fn raw(self) -> *mut bindings::lv_obj_t {
2500        self.raw
2501    }
2502}
2503
2504impl core::ops::Deref for Dropdown {
2505    type Target = Obj;
2506    fn deref(&self) -> &Obj {
2507        unsafe { &*(self as *const Dropdown as *const Obj) }
2508    }
2509}
2510
2511/// Roller mode: normal — selection clamps at ends of the list.
2512pub const ROLLER_MODE_NORMAL: u32 = 0;
2513/// Roller mode: infinite — selection can wrap through the list repeatedly.
2514pub const ROLLER_MODE_INFINITE: u32 = 1;
2515
2516// =========================================================================
2517//  Roller — iOS-picker-style scrollable wheel
2518// =========================================================================
2519
2520/// LVGL roller — iOS-picker-style scrollable option wheel.
2521#[derive(Clone, Copy)]
2522pub struct Roller {
2523    raw: *mut bindings::lv_obj_t,
2524}
2525
2526impl Roller {
2527    /// Create a new `Roller` widget as a child of `parent`.
2528    pub fn create(parent: impl Widget) -> Self {
2529        let raw = unsafe { bindings::lv_roller_create(parent.raw()) };
2530        Self { raw }
2531    }
2532
2533    /// Fluent: set options and scroll mode. Use [`ROLLER_MODE_NORMAL`] or
2534    /// [`ROLLER_MODE_INFINITE`].
2535    pub fn options(self, opts: &[u8], mode: u32) -> Self {
2536        unsafe { bindings::lv_roller_set_options(self.raw, opts.as_ptr() as *const _, mode as _) };
2537        self
2538    }
2539
2540    /// Fluent: set selected index. `anim` = true to animate the scroll.
2541    pub fn selected(self, sel: u32, anim: bool) -> Self {
2542        unsafe {
2543            #[allow(clippy::unnecessary_cast)]
2544            bindings::lv_roller_set_selected(self.raw, sel, anim as _);
2545        }
2546        self
2547    }
2548
2549    /// Fluent: set how many rows are visible in the wheel.
2550    pub fn visible_row_count(self, rows: u32) -> Self {
2551        unsafe { bindings::lv_roller_set_visible_row_count(self.raw, rows) };
2552        self
2553    }
2554
2555    /// Returns the currently selected index.
2556    pub fn get_selected(self) -> u32 {
2557        unsafe { bindings::lv_roller_get_selected(self.raw) }
2558    }
2559
2560    /// Returns the total option count.
2561    pub fn get_option_count(self) -> u32 {
2562        unsafe { bindings::lv_roller_get_option_count(self.raw) }
2563    }
2564
2565    /// Fills `buf` with the currently selected option text.
2566    pub fn get_selected_str(self, buf: &mut [u8]) {
2567        unsafe {
2568            bindings::lv_roller_get_selected_str(
2569                self.raw,
2570                buf.as_mut_ptr() as *mut _,
2571                buf.len() as u32,
2572            );
2573        }
2574    }
2575
2576    /// Returns the newline-separated option list as a raw C pointer owned
2577    /// by LVGL. The pointer is invalidated by any subsequent mutation.
2578    pub fn get_options(self) -> *const core::ffi::c_char {
2579        unsafe { bindings::lv_roller_get_options(self.raw()) }
2580    }
2581}
2582
2583impl Widget for Roller {
2584    fn raw(self) -> *mut bindings::lv_obj_t {
2585        self.raw
2586    }
2587}
2588
2589impl core::ops::Deref for Roller {
2590    type Target = Obj;
2591    fn deref(&self) -> &Obj {
2592        unsafe { &*(self as *const Roller as *const Obj) }
2593    }
2594}
2595
2596// =========================================================================
2597//  Spinbox — numeric stepper
2598// =========================================================================
2599
2600/// LVGL spinbox — numeric stepper with `+` / `−` buttons.
2601#[derive(Clone, Copy)]
2602pub struct Spinbox {
2603    raw: *mut bindings::lv_obj_t,
2604}
2605
2606impl Spinbox {
2607    /// Create a new `Spinbox` widget as a child of `parent`.
2608    pub fn create(parent: impl Widget) -> Self {
2609        let raw = unsafe { bindings::lv_spinbox_create(parent.raw()) };
2610        Self { raw }
2611    }
2612
2613    /// Fluent: set the current integer value.
2614    pub fn value(self, v: i32) -> Self {
2615        unsafe { bindings::lv_spinbox_set_value(self.raw, v) };
2616        self
2617    }
2618
2619    /// Fluent: set the min/max range.
2620    pub fn range(self, min: i32, max: i32) -> Self {
2621        unsafe { bindings::lv_spinbox_set_range(self.raw, min, max) };
2622        self
2623    }
2624
2625    /// Fluent: set the increment/decrement step size.
2626    pub fn step(self, s: u32) -> Self {
2627        unsafe { bindings::lv_spinbox_set_step(self.raw, s) };
2628        self
2629    }
2630
2631    /// Fluent: configure display format.
2632    ///
2633    /// `digit_count` is the total number of digits, `sep_pos` is the
2634    /// (1-based) decimal-point position from the right. `digit_format(5, 2)`
2635    /// renders `###.##`.
2636    pub fn digit_format(self, digit_count: u32, sep_pos: u32) -> Self {
2637        unsafe { bindings::lv_spinbox_set_digit_format(self.raw, digit_count, sep_pos) };
2638        self
2639    }
2640
2641    /// Fluent: enable wrap-around at range bounds.
2642    pub fn rollover(self, on: bool) -> Self {
2643        unsafe { bindings::lv_spinbox_set_rollover(self.raw, on) };
2644        self
2645    }
2646
2647    /// Fluent: set the edit cursor to a specific digit.
2648    pub fn cursor_pos(self, pos: u32) -> Self {
2649        unsafe { bindings::lv_spinbox_set_cursor_pos(self.raw, pos) };
2650        self
2651    }
2652
2653    /// Returns the current integer value.
2654    pub fn get_value(self) -> i32 {
2655        unsafe { bindings::lv_spinbox_get_value(self.raw) }
2656    }
2657
2658    /// Returns the current step size.
2659    pub fn get_step(self) -> i32 {
2660        unsafe { bindings::lv_spinbox_get_step(self.raw) }
2661    }
2662
2663    /// Fluent: increment by one step.
2664    pub fn increment(self) -> Self {
2665        unsafe { bindings::lv_spinbox_increment(self.raw) };
2666        self
2667    }
2668
2669    /// Fluent: decrement by one step.
2670    pub fn decrement(self) -> Self {
2671        unsafe { bindings::lv_spinbox_decrement(self.raw) };
2672        self
2673    }
2674}
2675
2676impl Widget for Spinbox {
2677    fn raw(self) -> *mut bindings::lv_obj_t {
2678        self.raw
2679    }
2680}
2681
2682impl core::ops::Deref for Spinbox {
2683    type Target = Obj;
2684    fn deref(&self) -> &Obj {
2685        unsafe { &*(self as *const Spinbox as *const Obj) }
2686    }
2687}
2688
2689/// Keyboard layout: lowercase QWERTY.
2690pub const KEYBOARD_MODE_TEXT_LOWER: u32 = 0;
2691/// Keyboard layout: uppercase QWERTY.
2692pub const KEYBOARD_MODE_TEXT_UPPER: u32 = 1;
2693/// Keyboard layout: special characters.
2694pub const KEYBOARD_MODE_SPECIAL: u32 = 2;
2695/// Keyboard layout: numeric keypad.
2696pub const KEYBOARD_MODE_NUMBER: u32 = 3;
2697
2698// =========================================================================
2699//  Keyboard — on-screen keyboard attached to a Textarea
2700// =========================================================================
2701
2702/// LVGL on-screen keyboard. Attach to a [`Textarea`] so key presses
2703/// flow into it.
2704#[derive(Clone, Copy)]
2705pub struct Keyboard {
2706    raw: *mut bindings::lv_obj_t,
2707}
2708
2709impl Keyboard {
2710    /// Create a new `Keyboard` widget as a child of `parent`.
2711    pub fn create(parent: impl Widget) -> Self {
2712        let raw = unsafe { bindings::lv_keyboard_create(parent.raw()) };
2713        Self { raw }
2714    }
2715
2716    /// Fluent: attach to a Textarea so typing inserts into it.
2717    pub fn attach(self, ta: Textarea) -> Self {
2718        unsafe { bindings::lv_keyboard_set_textarea(self.raw, ta.raw) };
2719        self
2720    }
2721
2722    /// Fluent: switch keyboard mode (use the `KEYBOARD_MODE_*` constants).
2723    pub fn mode(self, m: u32) -> Self {
2724        unsafe { bindings::lv_keyboard_set_mode(self.raw, m as _) };
2725        self
2726    }
2727
2728    /// Fluent: enable/disable on-press letter popovers.
2729    pub fn popovers(self, en: bool) -> Self {
2730        unsafe { bindings::lv_keyboard_set_popovers(self.raw, en) };
2731        self
2732    }
2733
2734    /// Returns the Textarea this keyboard is attached to, wrapped as an
2735    /// [`Obj`] (null if none is attached).
2736    pub fn get_textarea(self) -> Obj {
2737        let raw = unsafe { bindings::lv_keyboard_get_textarea(self.raw()) };
2738        unsafe { Obj::from_raw(raw) }
2739    }
2740}
2741
2742impl Widget for Keyboard {
2743    fn raw(self) -> *mut bindings::lv_obj_t {
2744        self.raw
2745    }
2746}
2747
2748impl core::ops::Deref for Keyboard {
2749    type Target = Obj;
2750    fn deref(&self) -> &Obj {
2751        unsafe { &*(self as *const Keyboard as *const Obj) }
2752    }
2753}
2754
2755// =========================================================================
2756//  Screen — top-level display root
2757// =========================================================================
2758
2759/// Top-level LVGL screen (display root without a parent).
2760///
2761/// Create via [`Screen::create`], swap it in with [`Screen::load`] or
2762/// [`Screen::load_anim`] for animated transitions.
2763#[derive(Clone, Copy)]
2764pub struct Screen {
2765    raw: *mut bindings::lv_obj_t,
2766}
2767
2768impl Screen {
2769    /// Create a new top-level screen (parent = null).
2770    pub fn create() -> Self {
2771        let raw = unsafe { bindings::lv_obj_create(core::ptr::null_mut()) };
2772        Self { raw }
2773    }
2774
2775    /// Returns the currently active screen.
2776    pub fn active() -> Self {
2777        let raw = unsafe { bindings::lv_screen_active() };
2778        Self { raw }
2779    }
2780
2781    /// Instantly load this screen as the active one.
2782    pub fn load(self) {
2783        unsafe { bindings::lv_screen_load(self.raw) };
2784    }
2785
2786    /// Load this screen with an animated transition.
2787    ///
2788    /// # Safety
2789    /// When `auto_del` is `true`, LVGL deletes the previously active
2790    /// screen after the transition — any surviving Rust handle to the
2791    /// old screen becomes a dangling pointer. Callers must ensure no
2792    /// other references to the old screen are used afterwards.
2793    pub unsafe fn load_anim(
2794        self,
2795        anim: bindings::lv_screen_load_anim_t,
2796        time_ms: u32,
2797        delay_ms: u32,
2798        auto_del: bool,
2799    ) {
2800        unsafe { bindings::lv_screen_load_anim(self.raw, anim, time_ms, delay_ms, auto_del) };
2801    }
2802}
2803
2804impl Widget for Screen {
2805    fn raw(self) -> *mut bindings::lv_obj_t {
2806        self.raw
2807    }
2808}
2809
2810impl core::ops::Deref for Screen {
2811    type Target = Obj;
2812    fn deref(&self) -> &Obj {
2813        unsafe { &*(self as *const Screen as *const Obj) }
2814    }
2815}
2816
2817// =========================================================================
2818//  Image
2819// =========================================================================
2820
2821/// LVGL image widget.
2822///
2823/// Sources can be compiled-in image descriptors (`*const lv_image_dsc_t`),
2824/// symbol strings (`LV_SYMBOL_*`), or filesystem paths if a file system
2825/// driver is registered.
2826#[derive(Clone, Copy)]
2827pub struct Image {
2828    raw: *mut bindings::lv_obj_t,
2829}
2830
2831impl Image {
2832    /// Create a new image widget as a child of `parent`.
2833    pub fn create(parent: impl Widget) -> Self {
2834        let raw = unsafe { bindings::lv_image_create(parent.raw()) };
2835        Self { raw }
2836    }
2837
2838    /// **Legacy escape hatch** — prefer [`Image::source`] which takes a typed
2839    /// [`ImageSrc`]. This raw variant is kept for narrow cases where a
2840    /// non-`'static` pointer must be supplied.
2841    ///
2842    /// # Caller obligations
2843    /// `src` must point to an LVGL-compatible image source that outlives
2844    /// the widget: a static `lv_image_dsc_t`, a symbol string literal, or
2845    /// a null-terminated path string. The pointer is not validated; pass
2846    /// the wrong shape and LVGL will misinterpret it.
2847    #[doc(hidden)]
2848    pub fn src(self, src: *const core::ffi::c_void) -> Self {
2849        unsafe { bindings::lv_image_set_src(self.raw, src) };
2850        self
2851    }
2852
2853    /// Fluent: set the rotation angle in 0.1-degree units (0–3600).
2854    pub fn rotation(self, angle: i32) -> Self {
2855        unsafe { bindings::lv_image_set_rotation(self.raw, angle) };
2856        self
2857    }
2858
2859    /// Fluent: set the zoom factor (256 = 1.0x, 512 = 2.0x).
2860    pub fn scale(self, zoom: u32) -> Self {
2861        unsafe { bindings::lv_image_set_scale(self.raw, zoom) };
2862        self
2863    }
2864
2865    /// Fluent: set the rotation/scale pivot point.
2866    pub fn pivot(self, x: i32, y: i32) -> Self {
2867        unsafe { bindings::lv_image_set_pivot(self.raw, x, y) };
2868        self
2869    }
2870}
2871
2872impl Widget for Image {
2873    fn raw(self) -> *mut bindings::lv_obj_t {
2874        self.raw
2875    }
2876}
2877
2878impl core::ops::Deref for Image {
2879    type Target = Obj;
2880    fn deref(&self) -> &Obj {
2881        unsafe { &*(self as *const Image as *const Obj) }
2882    }
2883}
2884
2885// =========================================================================
2886//  Layout helpers — vbox / hbox
2887// =========================================================================
2888
2889/// Create a vertical flex container.
2890pub fn vbox(parent: impl Widget) -> Box {
2891    let b = Box::create(parent);
2892    unsafe {
2893        bindings::lv_obj_set_flex_flow(b.raw, bindings::LV_FLEX_FLOW_COLUMN);
2894        bindings::lv_obj_set_size(b.raw, SIZE_CONTENT, SIZE_CONTENT);
2895    }
2896    b
2897}
2898
2899/// Create a horizontal flex container.
2900pub fn hbox(parent: impl Widget) -> Box {
2901    let b = Box::create(parent);
2902    unsafe {
2903        bindings::lv_obj_set_flex_flow(b.raw, bindings::LV_FLEX_FLOW_ROW);
2904        bindings::lv_obj_set_size(b.raw, SIZE_CONTENT, SIZE_CONTENT);
2905    }
2906    b
2907}
2908
2909// =========================================================================
2910//  Group — keyboard / encoder focus group
2911// =========================================================================
2912
2913/// RAII wrapper for an LVGL input focus group (`lv_group_t`).
2914///
2915/// Groups route keyboard/encoder/button events to a focused member
2916/// widget. Essential for non-touch targets. Create a group, add widgets
2917/// via [`Group::add`], then wire it to an input device via the
2918/// backend-specific indev registration (outside this crate).
2919///
2920/// Dropping the `Group` deletes the underlying `lv_group_t`.
2921pub struct Group {
2922    raw: *mut bindings::lv_group_t,
2923}
2924
2925impl Group {
2926    /// Create a fresh focus group.
2927    pub fn new() -> Self {
2928        let raw = unsafe { bindings::lv_group_create() };
2929        Self { raw }
2930    }
2931
2932    /// Raw `lv_group_t *` for FFI interop.
2933    pub fn as_raw(&self) -> *mut bindings::lv_group_t {
2934        self.raw
2935    }
2936
2937    /// Fluent: mark this group as the default.
2938    pub fn set_as_default(&self) -> &Self {
2939        unsafe { bindings::lv_group_set_default(self.raw) };
2940        self
2941    }
2942
2943    /// Fluent: add a widget to the group's focusable list.
2944    pub fn add(&self, obj: impl Widget) -> &Self {
2945        unsafe { bindings::lv_group_add_obj(self.raw, obj.raw()) };
2946        self
2947    }
2948
2949    /// Fluent: remove all widgets from this group.
2950    pub fn remove_all(&self) -> &Self {
2951        unsafe { bindings::lv_group_remove_all_objs(self.raw) };
2952        self
2953    }
2954
2955    /// Fluent: move focus to the next member.
2956    pub fn focus_next(&self) -> &Self {
2957        unsafe { bindings::lv_group_focus_next(self.raw) };
2958        self
2959    }
2960
2961    /// Fluent: move focus to the previous member.
2962    pub fn focus_prev(&self) -> &Self {
2963        unsafe { bindings::lv_group_focus_prev(self.raw) };
2964        self
2965    }
2966
2967    /// Fluent: freeze/unfreeze focus movement.
2968    pub fn focus_freeze(&self, freeze: bool) -> &Self {
2969        unsafe { bindings::lv_group_focus_freeze(self.raw, freeze) };
2970        self
2971    }
2972
2973    /// Returns the currently focused widget as an [`Obj`] handle, or
2974    /// `None` if the group is empty.
2975    pub fn focused(&self) -> Option<Obj> {
2976        let raw = unsafe { bindings::lv_group_get_focused(self.raw) };
2977        if raw.is_null() {
2978            None
2979        } else {
2980            Some(unsafe { Obj::from_raw(raw) })
2981        }
2982    }
2983
2984    /// Fluent: enable/disable encoder edit mode.
2985    pub fn edit_mode(&self, enable: bool) -> &Self {
2986        unsafe { bindings::lv_group_set_editing(self.raw, enable) };
2987        self
2988    }
2989
2990    /// Returns the current edit-mode flag.
2991    pub fn is_editing(&self) -> bool {
2992        unsafe { bindings::lv_group_get_editing(self.raw) }
2993    }
2994
2995    /// Returns the number of widgets in the group.
2996    pub fn obj_count(&self) -> u32 {
2997        unsafe { bindings::lv_group_get_obj_count(self.raw) }
2998    }
2999
3000    /// Static helper: focus a widget regardless of its group.
3001    pub fn focus_obj(obj: impl Widget) {
3002        unsafe { bindings::lv_group_focus_obj(obj.raw()) };
3003    }
3004
3005    /// Static helper: remove a widget from whatever group it's in.
3006    pub fn remove_obj(obj: impl Widget) {
3007        unsafe { bindings::lv_group_remove_obj(obj.raw()) };
3008    }
3009}
3010
3011impl Drop for Group {
3012    fn drop(&mut self) {
3013        if !self.raw.is_null() {
3014            unsafe { bindings::lv_group_delete(self.raw) };
3015        }
3016    }
3017}
3018
3019impl Default for Group {
3020    fn default() -> Self {
3021        Self::new()
3022    }
3023}
3024
3025// SAFETY: `Group` wraps an `lv_group_t` focus group; LVGL maintains its
3026// own registry, and access is gated by `LvglGuard`.  Same contract as
3027// the widget block.
3028unsafe impl Send for Group {}
3029unsafe impl Sync for Group {}
3030
3031// =========================================================================
3032//  Timer — RAII wrapper around lv_timer_t
3033// =========================================================================
3034
3035/// RAII wrapper for LVGL's built-in timer.
3036///
3037/// LVGL timers fire from inside `lvgl::handler()` while the LVGL lock is
3038/// already held, so callbacks must NOT call [`lock`] again — the mutex is
3039/// not reentrant and you will deadlock. Prefer `Timer` over `ove::Timer`
3040/// for UI-update ticks that only touch LVGL state.
3041///
3042/// Dropping the `Timer` deletes the underlying `lv_timer_t`.
3043pub struct Timer {
3044    raw: *mut bindings::lv_timer_t,
3045}
3046
3047impl Timer {
3048    /// Create and start an LVGL timer.
3049    ///
3050    /// # Safety
3051    /// `cb` must be an `extern "C" fn(*mut lv_timer_t)` and `user_data`
3052    /// must remain valid until the timer is dropped or its repeat count
3053    /// reaches zero.
3054    pub fn new(
3055        cb: bindings::lv_timer_cb_t,
3056        period_ms: u32,
3057        user_data: *mut core::ffi::c_void,
3058    ) -> Self {
3059        let raw = unsafe { bindings::lv_timer_create(cb, period_ms, user_data) };
3060        Self { raw }
3061    }
3062
3063    /// Returns the raw `lv_timer_t *` for FFI interop.
3064    pub fn as_raw(&self) -> *mut bindings::lv_timer_t {
3065        self.raw
3066    }
3067
3068    /// Fluent: update the period in milliseconds.
3069    pub fn period(&self, ms: u32) -> &Self {
3070        unsafe { bindings::lv_timer_set_period(self.raw, ms) };
3071        self
3072    }
3073
3074    /// Fluent: pause the timer (can be resumed).
3075    pub fn pause(&self) -> &Self {
3076        unsafe { bindings::lv_timer_pause(self.raw) };
3077        self
3078    }
3079
3080    /// Fluent: resume a paused timer.
3081    pub fn resume(&self) -> &Self {
3082        unsafe { bindings::lv_timer_resume(self.raw) };
3083        self
3084    }
3085
3086    /// Fluent: set the number of times the timer fires; `-1` for infinite.
3087    pub fn repeat_count(&self, count: i32) -> &Self {
3088        unsafe { bindings::lv_timer_set_repeat_count(self.raw, count) };
3089        self
3090    }
3091
3092    /// Fluent: reset the internal elapsed-time counter.
3093    pub fn reset(&self) -> &Self {
3094        unsafe { bindings::lv_timer_reset(self.raw) };
3095        self
3096    }
3097
3098    /// Fluent: make the timer ready to fire on the next handler pass.
3099    pub fn ready(&self) -> &Self {
3100        unsafe { bindings::lv_timer_ready(self.raw) };
3101        self
3102    }
3103}
3104
3105impl Drop for Timer {
3106    fn drop(&mut self) {
3107        if !self.raw.is_null() {
3108            unsafe { bindings::lv_timer_delete(self.raw) };
3109        }
3110    }
3111}
3112
3113// SAFETY: `Timer` wraps `lv_timer_t`.  LVGL drives timer callbacks from
3114// its own task; the Rust `Timer` handle is just a registry pointer.
3115// Creation/destruction goes through the LVGL lock, same as widgets.
3116unsafe impl Send for Timer {}
3117unsafe impl Sync for Timer {}
3118
3119// =========================================================================
3120//  State<T> — reactive integer state (lv_subject_t wrapper)
3121// =========================================================================
3122
3123/// Reactive integer state backed by LVGL's observer subsystem
3124/// (`lv_subject_t`). Bind widget properties to a `State` and they
3125/// update automatically when you call [`State::set`].
3126///
3127/// # Stability requirement
3128///
3129/// `lv_subject_t` keeps a linked list of observer references, so the
3130/// `State` address must be stable from the moment any observer attaches.
3131/// The intended usage is inside a static slot — either a raw `static`
3132/// declared by the user, or via the `ove::shared!` macro. Dropping a
3133/// `State` while observers are still attached will corrupt the observer
3134/// list. `PhantomPinned` prevents move after construction.
3135///
3136/// ```ignore
3137/// use ove::lvgl::{State, Label};
3138/// ove::shared!(COUNTER: State<i32>);
3139/// // init once:
3140/// COUNTER.init(State::new(0));
3141/// // bind:
3142/// Label::create(screen).bind_text(COUNTER.get_ref(), b"Count: %d\0");
3143/// // update from anywhere:
3144/// COUNTER.get_ref().set(42);  // label updates automatically
3145/// ```
3146pub struct State<T: Copy> {
3147    subject: core::cell::UnsafeCell<bindings::lv_subject_t>,
3148    _pin: core::marker::PhantomPinned,
3149    _phantom: core::marker::PhantomData<T>,
3150}
3151
3152impl<T: Copy + Into<i32> + TryFrom<i32>> State<T> {
3153    /// Create a new reactive state with an initial value.
3154    pub fn new(initial: T) -> Self {
3155        let mut subject: bindings::lv_subject_t = unsafe { core::mem::zeroed() };
3156        unsafe { bindings::lv_subject_init_int(&mut subject, initial.into()) };
3157        Self {
3158            subject: core::cell::UnsafeCell::new(subject),
3159            _pin: core::marker::PhantomPinned,
3160            _phantom: core::marker::PhantomData,
3161        }
3162    }
3163
3164    /// Set a new value. All bound widgets update immediately.
3165    ///
3166    /// Takes `&self` rather than `&mut self` because LVGL serializes
3167    /// observer notification under the global LVGL lock — the lock is
3168    /// the real mutex, not Rust's borrow checker.
3169    pub fn set(&self, value: T) {
3170        unsafe {
3171            bindings::lv_subject_set_int(self.subject.get(), value.into());
3172        }
3173    }
3174
3175    /// Read the current value. Falls back to a zeroed `T` if the stored
3176    /// `i32` cannot be converted back to `T` (unreachable for well-typed
3177    /// integer states).
3178    pub fn get(&self) -> T {
3179        let raw = unsafe { bindings::lv_subject_get_int(self.subject.get()) };
3180        T::try_from(raw).unwrap_or_else(|_| {
3181            // Unreachable for well-typed integers; zero is the safest fallback.
3182            unsafe { core::mem::zeroed() }
3183        })
3184    }
3185
3186    /// Returns the raw `*mut lv_subject_t` for widget binding.
3187    pub fn subject_ptr(&self) -> *mut bindings::lv_subject_t {
3188        self.subject.get()
3189    }
3190}
3191
3192impl<T: Copy> Drop for State<T> {
3193    fn drop(&mut self) {
3194        unsafe { bindings::lv_subject_deinit(self.subject.get()) };
3195    }
3196}
3197
3198// SAFETY: `State<T>` is a reactive observer state.  Writes dispatch
3199// LV_EVENT_VALUE_CHANGED under the LVGL lock; the `T: Copy + Send` bound
3200// ensures the stored value can cross thread boundaries when read.
3201unsafe impl<T: Copy + Send> Send for State<T> {}
3202unsafe impl<T: Copy + Send> Sync for State<T> {}
3203
3204// Widget bind methods — only the widgets with native LVGL bind_* calls
3205// get these. Bar/Checkbox/Switch/Spinbox users must wire observers
3206// manually via `lv_subject_add_observer_obj` (not yet wrapped).
3207
3208impl Label {
3209    /// Bind this label's text to a reactive state with a printf-style format.
3210    ///
3211    /// `fmt` must be a null-terminated byte slice containing a format
3212    /// string (e.g. `b"Count: %d\0"`).
3213    pub fn bind_text<T: Copy + Into<i32> + TryFrom<i32>>(
3214        self,
3215        state: &State<T>,
3216        fmt: &'static [u8],
3217    ) -> Self {
3218        unsafe {
3219            bindings::lv_label_bind_text(self.raw(), state.subject_ptr(), fmt.as_ptr() as *const _);
3220        }
3221        self
3222    }
3223}
3224
3225impl Arc {
3226    /// Bind this arc's value to a reactive state.
3227    pub fn bind_value<T: Copy + Into<i32> + TryFrom<i32>>(self, state: &State<T>) -> Self {
3228        unsafe { bindings::lv_arc_bind_value(self.raw(), state.subject_ptr()) };
3229        self
3230    }
3231}
3232
3233impl Slider {
3234    /// Bind this slider's value to a reactive state.
3235    pub fn bind_value<T: Copy + Into<i32> + TryFrom<i32>>(self, state: &State<T>) -> Self {
3236        unsafe { bindings::lv_slider_bind_value(self.raw(), state.subject_ptr()) };
3237        self
3238    }
3239}
3240
3241impl Roller {
3242    /// Bind this roller's selected index to a reactive state.
3243    pub fn bind_value<T: Copy + Into<i32> + TryFrom<i32>>(self, state: &State<T>) -> Self {
3244        unsafe { bindings::lv_roller_bind_value(self.raw(), state.subject_ptr()) };
3245        self
3246    }
3247}
3248
3249impl Dropdown {
3250    /// Bind this dropdown's selected index to a reactive state.
3251    pub fn bind_value<T: Copy + Into<i32> + TryFrom<i32>>(self, state: &State<T>) -> Self {
3252        unsafe { bindings::lv_dropdown_bind_value(self.raw(), state.subject_ptr()) };
3253        self
3254    }
3255}
3256
3257// =========================================================================
3258//  Component primitives — user_data + from_event walk
3259// =========================================================================
3260
3261/// Walk up from the event's target through the parent chain looking for
3262/// any non-null `user_data` pointer, returning the first one found.
3263///
3264/// Used by Component-style patterns to recover the owning component
3265/// instance from inside an event callback. The caller must cast the
3266/// returned pointer to the correct type.
3267///
3268/// # Safety
3269///
3270/// `e` must be a valid `lv_event_t *` active inside an event callback.
3271/// The returned pointer is whatever the widget tree's user_data holds —
3272/// the caller is responsible for its type.
3273pub unsafe fn component_from_event(e: *mut bindings::lv_event_t) -> *mut core::ffi::c_void {
3274    unsafe {
3275        // lv_event_get_target may return *mut c_void or *mut lv_obj_t
3276        // depending on bindgen config; cast normalises to *mut lv_obj_t
3277        // for the lv_obj_get_user_data / lv_obj_get_parent calls below.
3278        let mut target = bindings::lv_event_get_target(e) as *mut bindings::lv_obj_t;
3279        while !target.is_null() {
3280            let ud = bindings::lv_obj_get_user_data(target);
3281            if !ud.is_null() {
3282                return ud;
3283            }
3284            target = bindings::lv_obj_get_parent(target);
3285        }
3286        core::ptr::null_mut()
3287    }
3288}
3289
3290// =========================================================================
3291//  Animation — builder for lv_anim_t
3292// =========================================================================
3293
3294/// LVGL repeat-forever sentinel for [`Animation::repeat_count`].
3295pub const ANIM_REPEAT_INFINITE: u32 = 0xFFFF_FFFF;
3296
3297/// Fluent builder for an LVGL animation.
3298///
3299/// Configure the animation step-by-step, then call [`Animation::start`] —
3300/// LVGL copies the state into its internal animation list, so the
3301/// builder can be dropped immediately after.
3302///
3303/// # Callback constraints
3304/// In `no_std` Rust we're stuck with `extern "C" fn` pointers for
3305/// `exec_cb` / `ready_cb` — no closures. Use the [`animate_x`],
3306/// [`animate_y`], [`animate_width`], [`animate_opa`] helpers for the
3307/// common cases where you just want to tween a single property.
3308pub struct Animation {
3309    inner: bindings::lv_anim_t,
3310}
3311
3312impl Animation {
3313    /// Create a fresh animation with default values (duration 0, no exec cb).
3314    pub fn new() -> Self {
3315        let mut a: bindings::lv_anim_t = unsafe { core::mem::zeroed() };
3316        unsafe { bindings::lv_anim_init(&mut a) };
3317        Self { inner: a }
3318    }
3319
3320    /// Set the target variable (typically `obj.raw() as *mut c_void`).
3321    pub fn target(mut self, var: *mut core::ffi::c_void) -> Self {
3322        unsafe { bindings::lv_anim_set_var(&mut self.inner, var) };
3323        self
3324    }
3325
3326    /// Set start and end values.
3327    pub fn values(mut self, from: i32, to: i32) -> Self {
3328        unsafe { bindings::lv_anim_set_values(&mut self.inner, from, to) };
3329        self
3330    }
3331
3332    /// Set duration in milliseconds.
3333    pub fn duration(mut self, ms: u32) -> Self {
3334        unsafe { bindings::lv_anim_set_duration(&mut self.inner, ms) };
3335        self
3336    }
3337
3338    /// Set the delay before the animation starts.
3339    pub fn delay(mut self, ms: u32) -> Self {
3340        unsafe { bindings::lv_anim_set_delay(&mut self.inner, ms) };
3341        self
3342    }
3343
3344    /// Set the easing path callback. Use e.g. [`path_ease_out`].
3345    pub fn path(mut self, cb: bindings::lv_anim_path_cb_t) -> Self {
3346        unsafe { bindings::lv_anim_set_path_cb(&mut self.inner, cb) };
3347        self
3348    }
3349
3350    /// Set the repeat count; use [`ANIM_REPEAT_INFINITE`] for endless.
3351    pub fn repeat_count(mut self, count: u32) -> Self {
3352        unsafe { bindings::lv_anim_set_repeat_count(&mut self.inner, count) };
3353        self
3354    }
3355
3356    /// Set the delay between repeats.
3357    pub fn repeat_delay(mut self, ms: u32) -> Self {
3358        unsafe { bindings::lv_anim_set_repeat_delay(&mut self.inner, ms) };
3359        self
3360    }
3361
3362    /// Set the duration of the reverse (playback) phase.
3363    pub fn playback_duration(mut self, ms: u32) -> Self {
3364        unsafe { bindings::lv_anim_set_reverse_duration(&mut self.inner, ms) };
3365        self
3366    }
3367
3368    /// Set the delay before the playback phase.
3369    pub fn playback_delay(mut self, ms: u32) -> Self {
3370        unsafe { bindings::lv_anim_set_reverse_delay(&mut self.inner, ms) };
3371        self
3372    }
3373
3374    /// Set the exec callback invoked every frame with `(var, value)`.
3375    pub fn exec_cb(mut self, cb: bindings::lv_anim_exec_xcb_t) -> Self {
3376        unsafe { bindings::lv_anim_set_exec_cb(&mut self.inner, cb) };
3377        self
3378    }
3379
3380    /// Set the callback invoked when the animation completes (`lv_anim_set_completed_cb`).
3381    pub fn completed_cb(mut self, cb: bindings::lv_anim_completed_cb_t) -> Self {
3382        unsafe { bindings::lv_anim_set_completed_cb(&mut self.inner, cb) };
3383        self
3384    }
3385
3386    /// Start the animation. LVGL copies internal state, so the builder
3387    /// can be dropped immediately after.
3388    pub fn start(self) {
3389        unsafe { bindings::lv_anim_start(&self.inner) };
3390    }
3391
3392    /// Stop any animations matching `(var, exec_cb)`.
3393    pub fn stop(var: *mut core::ffi::c_void, exec_cb: bindings::lv_anim_exec_xcb_t) -> bool {
3394        unsafe { bindings::lv_anim_delete(var, exec_cb) }
3395    }
3396}
3397
3398impl Default for Animation {
3399    fn default() -> Self {
3400        Self::new()
3401    }
3402}
3403
3404/// Linear easing path (`lv_anim_path_linear`).
3405pub fn path_linear() -> bindings::lv_anim_path_cb_t {
3406    Some(bindings::lv_anim_path_linear)
3407}
3408
3409/// Ease-in path (`lv_anim_path_ease_in`).
3410pub fn path_ease_in() -> bindings::lv_anim_path_cb_t {
3411    Some(bindings::lv_anim_path_ease_in)
3412}
3413
3414/// Ease-out path (`lv_anim_path_ease_out`).
3415pub fn path_ease_out() -> bindings::lv_anim_path_cb_t {
3416    Some(bindings::lv_anim_path_ease_out)
3417}
3418
3419/// Ease-in-out path (`lv_anim_path_ease_in_out`).
3420pub fn path_ease_in_out() -> bindings::lv_anim_path_cb_t {
3421    Some(bindings::lv_anim_path_ease_in_out)
3422}
3423
3424/// Overshoot path (`lv_anim_path_overshoot`).
3425pub fn path_overshoot() -> bindings::lv_anim_path_cb_t {
3426    Some(bindings::lv_anim_path_overshoot)
3427}
3428
3429/// Bounce path (`lv_anim_path_bounce`).
3430pub fn path_bounce() -> bindings::lv_anim_path_cb_t {
3431    Some(bindings::lv_anim_path_bounce)
3432}
3433
3434/// Step path (`lv_anim_path_step`).
3435pub fn path_step() -> bindings::lv_anim_path_cb_t {
3436    Some(bindings::lv_anim_path_step)
3437}
3438
3439// Shims that adapt widget setters (`fn(*mut lv_obj_t, i32)`) to the
3440// animation exec callback signature (`fn(*mut c_void, i32)`).
3441unsafe extern "C" fn anim_set_x_shim(var: *mut core::ffi::c_void, v: i32) {
3442    unsafe { bindings::lv_obj_set_x(var as *mut bindings::lv_obj_t, v) };
3443}
3444
3445unsafe extern "C" fn anim_set_y_shim(var: *mut core::ffi::c_void, v: i32) {
3446    unsafe { bindings::lv_obj_set_y(var as *mut bindings::lv_obj_t, v) };
3447}
3448
3449unsafe extern "C" fn anim_set_width_shim(var: *mut core::ffi::c_void, v: i32) {
3450    unsafe { bindings::lv_obj_set_width(var as *mut bindings::lv_obj_t, v) };
3451}
3452
3453unsafe extern "C" fn anim_set_opa_shim(var: *mut core::ffi::c_void, v: i32) {
3454    unsafe { bindings::lv_obj_set_style_opa(var as *mut bindings::lv_obj_t, v as u8, PART_MAIN) };
3455}
3456
3457/// Animate an object's X position to `to` over `duration_ms` (ease-out).
3458pub fn animate_x(obj: impl Widget, to: i32, duration_ms: u32) {
3459    let from = unsafe { bindings::lv_obj_get_x(obj.raw()) };
3460    Animation::new()
3461        .target(obj.raw() as *mut _)
3462        .values(from, to)
3463        .duration(duration_ms)
3464        .path(path_ease_out())
3465        .exec_cb(Some(anim_set_x_shim))
3466        .start();
3467}
3468
3469/// Animate an object's Y position to `to` over `duration_ms` (ease-out).
3470pub fn animate_y(obj: impl Widget, to: i32, duration_ms: u32) {
3471    let from = unsafe { bindings::lv_obj_get_y(obj.raw()) };
3472    Animation::new()
3473        .target(obj.raw() as *mut _)
3474        .values(from, to)
3475        .duration(duration_ms)
3476        .path(path_ease_out())
3477        .exec_cb(Some(anim_set_y_shim))
3478        .start();
3479}
3480
3481/// Animate an object's width to `to` over `duration_ms` (ease-out).
3482pub fn animate_width(obj: impl Widget, to: i32, duration_ms: u32) {
3483    let from = unsafe { bindings::lv_obj_get_width(obj.raw()) };
3484    Animation::new()
3485        .target(obj.raw() as *mut _)
3486        .values(from, to)
3487        .duration(duration_ms)
3488        .path(path_ease_out())
3489        .exec_cb(Some(anim_set_width_shim))
3490        .start();
3491}
3492
3493/// Fade an object's main-part opacity from `from` to `to` over `duration_ms`.
3494///
3495/// Unlike the C++/Zig counterparts this requires the caller to pass the
3496/// starting opacity explicitly because `lv_obj_get_style_opa` is a
3497/// static inline that Rust bindgen doesn't expose.
3498pub fn animate_opa(obj: impl Widget, from: u8, to: u8, duration_ms: u32) {
3499    Animation::new()
3500        .target(obj.raw() as *mut _)
3501        .values(from as i32, to as i32)
3502        .duration(duration_ms)
3503        .path(path_ease_in_out())
3504        .exec_cb(Some(anim_set_opa_shim))
3505        .start();
3506}
3507
3508// =========================================================================
3509//  Style — RAII style object
3510// =========================================================================
3511
3512/// RAII wrapper around `lv_style_t`. Calls `lv_style_reset` on drop.
3513pub struct Style {
3514    inner: bindings::lv_style_t,
3515}
3516
3517impl Default for Style {
3518    fn default() -> Self {
3519        Self::new()
3520    }
3521}
3522
3523impl Style {
3524    /// Create and initialize a new LVGL style object.
3525    pub fn new() -> Self {
3526        let mut s = Self {
3527            inner: unsafe { core::mem::zeroed() },
3528        };
3529        unsafe { bindings::lv_style_init(&mut s.inner) };
3530        s
3531    }
3532
3533    /// Return a raw mutable pointer for use with [`Styleable::add_style`].
3534    pub fn as_mut_ptr(&mut self) -> *mut bindings::lv_style_t {
3535        &mut self.inner
3536    }
3537
3538    /// Return the raw pointer from a shared reference, for APIs where LVGL
3539    /// only reads through the pointer (e.g. `lv_obj_add_style`,
3540    /// `lv_scale_section_set_style`). LVGL conventionally does not mutate
3541    /// styles once they've been applied.
3542    pub fn ptr(&self) -> *mut bindings::lv_style_t {
3543        &self.inner as *const _ as *mut _
3544    }
3545
3546    /// Set the arc color in this style.
3547    pub fn arc_color(mut self, c: Color) -> Self {
3548        unsafe { bindings::lv_style_set_arc_color(&mut self.inner, c.to_raw()) };
3549        self
3550    }
3551
3552    /// Set the arc width in this style.
3553    pub fn arc_width(mut self, w: i32) -> Self {
3554        unsafe { bindings::lv_style_set_arc_width(&mut self.inner, w) };
3555        self
3556    }
3557
3558    /// Set the background color in this style.
3559    pub fn bg_color(mut self, c: Color) -> Self {
3560        unsafe { bindings::lv_style_set_bg_color(&mut self.inner, c.to_raw()) };
3561        self
3562    }
3563
3564    /// Set the background opacity in this style (0 = transparent, 255 = opaque).
3565    pub fn bg_opa(mut self, opa: u8) -> Self {
3566        unsafe { bindings::lv_style_set_bg_opa(&mut self.inner, opa) };
3567        self
3568    }
3569
3570    /// Set the corner radius in this style.
3571    pub fn radius(mut self, r: i32) -> Self {
3572        unsafe { bindings::lv_style_set_radius(&mut self.inner, r) };
3573        self
3574    }
3575
3576    /// Set the border color in this style.
3577    pub fn border_color(mut self, c: Color) -> Self {
3578        unsafe { bindings::lv_style_set_border_color(&mut self.inner, c.to_raw()) };
3579        self
3580    }
3581
3582    /// Set the border width in this style.
3583    pub fn border_width(mut self, w: i32) -> Self {
3584        unsafe { bindings::lv_style_set_border_width(&mut self.inner, w) };
3585        self
3586    }
3587
3588    /// Set equal padding on all four sides in this style.
3589    pub fn pad_all(mut self, p: i32) -> Self {
3590        unsafe {
3591            let s = &mut self.inner;
3592            bindings::lv_style_set_pad_left(s, p);
3593            bindings::lv_style_set_pad_right(s, p);
3594            bindings::lv_style_set_pad_top(s, p);
3595            bindings::lv_style_set_pad_bottom(s, p);
3596        }
3597        self
3598    }
3599
3600    /// Set the text color in this style.
3601    pub fn text_color(mut self, c: Color) -> Self {
3602        unsafe { bindings::lv_style_set_text_color(&mut self.inner, c.to_raw()) };
3603        self
3604    }
3605
3606    /// Set the text font in this style.
3607    pub fn text_font(mut self, f: *const bindings::lv_font_t) -> Self {
3608        unsafe { bindings::lv_style_set_text_font(&mut self.inner, f) };
3609        self
3610    }
3611}
3612
3613// SAFETY: Style access is serialized by the global LVGL lock.
3614unsafe impl Send for Style {}
3615unsafe impl Sync for Style {}
3616
3617impl Drop for Style {
3618    fn drop(&mut self) {
3619        unsafe { bindings::lv_style_reset(&mut self.inner) };
3620    }
3621}
3622
3623// =========================================================================
3624//  Send + Sync (same contract as C/C++: all access under LVGL lock)
3625//
3626// SAFETY: every widget below is a `#[repr(transparent)]` newtype around
3627// `*mut bindings::lv_obj_t` (or a phantom alias such as `ObjectView`).
3628// LVGL maintains its own internal object registry, and every method that
3629// touches an `lv_obj_t` is required by the C library to run with
3630// `ove_lvgl_lock()` held (use `LvglGuard` from this module).
3631// Cross-thread ownership is therefore safe (Send): the receiving thread
3632// can only do anything with the handle after re-acquiring the lock.
3633// Shared aliasing is safe (Sync) for the same reason — concurrent
3634// readers serialise through the LVGL mutex.  Failing to hold the lock
3635// is a contract violation already documented at the LVGL API surface;
3636// it would be UB at the C level too.
3637// =========================================================================
3638
3639unsafe impl Send for Obj {}
3640unsafe impl Sync for Obj {}
3641unsafe impl Send for Label {}
3642unsafe impl Sync for Label {}
3643unsafe impl Send for Bar {}
3644unsafe impl Sync for Bar {}
3645unsafe impl Send for Box {}
3646unsafe impl Sync for Box {}
3647unsafe impl Send for Button {}
3648unsafe impl Sync for Button {}
3649unsafe impl Send for Slider {}
3650unsafe impl Sync for Slider {}
3651unsafe impl Send for Switch {}
3652unsafe impl Sync for Switch {}
3653unsafe impl Send for Checkbox {}
3654unsafe impl Sync for Checkbox {}
3655unsafe impl Send for Arc {}
3656unsafe impl Sync for Arc {}
3657unsafe impl Send for Image {}
3658unsafe impl Sync for Image {}
3659unsafe impl Send for Screen {}
3660unsafe impl Sync for Screen {}
3661unsafe impl Send for Msgbox {}
3662unsafe impl Sync for Msgbox {}
3663unsafe impl Send for Spinner {}
3664unsafe impl Sync for Spinner {}
3665unsafe impl Send for Led {}
3666unsafe impl Sync for Led {}
3667unsafe impl Send for Textarea {}
3668unsafe impl Sync for Textarea {}
3669unsafe impl Send for Dropdown {}
3670unsafe impl Sync for Dropdown {}
3671unsafe impl Send for Roller {}
3672unsafe impl Sync for Roller {}
3673unsafe impl Send for Spinbox {}
3674unsafe impl Sync for Spinbox {}
3675unsafe impl Send for Keyboard {}
3676unsafe impl Sync for Keyboard {}
3677unsafe impl Send for Chart {}
3678unsafe impl Sync for Chart {}
3679unsafe impl Send for Table {}
3680unsafe impl Sync for Table {}
3681unsafe impl Send for Tabview {}
3682unsafe impl Sync for Tabview {}
3683unsafe impl Send for List {}
3684unsafe impl Sync for List {}
3685unsafe impl Send for Canvas {}
3686unsafe impl Sync for Canvas {}
3687unsafe impl Send for Calendar {}
3688unsafe impl Sync for Calendar {}
3689
3690// =========================================================================
3691//  LvglGuard — RAII lock/unlock
3692// =========================================================================
3693
3694/// RAII guard for the LVGL mutex. `Drop` calls `ove_lvgl_unlock()`.
3695pub struct LvglGuard(());
3696
3697impl Drop for LvglGuard {
3698    fn drop(&mut self) {
3699        unsafe { bindings::ove_lvgl_unlock() };
3700    }
3701}
3702
3703// =========================================================================
3704//  Module-level functions
3705// =========================================================================
3706
3707/// Initialize LVGL via `ove_lvgl_init()`.
3708pub fn init() -> Result<()> {
3709    let ret = unsafe { bindings::ove_lvgl_init() };
3710    Error::from_code(ret)
3711}
3712
3713/// Feed LVGL tick counter.
3714pub fn tick(ms: u32) {
3715    unsafe { bindings::ove_lvgl_tick(ms) };
3716}
3717
3718/// Run the LVGL task handler.
3719pub fn handler() {
3720    unsafe { bindings::ove_lvgl_handler() };
3721}
3722
3723/// Acquire the LVGL mutex and return an RAII guard.
3724pub fn lock() -> LvglGuard {
3725    unsafe { bindings::ove_lvgl_lock() };
3726    LvglGuard(())
3727}
3728
3729/// Get the currently active screen.
3730pub fn screen_active() -> Obj {
3731    let raw = unsafe { bindings::lv_screen_active() };
3732    unsafe { Obj::from_raw(raw) }
3733}
3734
3735// =========================================================================
3736//  Display info (default display getters)
3737// =========================================================================
3738
3739/// Helpers querying the default LVGL display.
3740pub mod display {
3741    use super::bindings;
3742
3743    /// Horizontal resolution of the default display in pixels.
3744    pub fn width() -> i32 {
3745        unsafe { bindings::lv_display_get_horizontal_resolution(core::ptr::null_mut()) }
3746    }
3747
3748    /// Vertical resolution of the default display in pixels.
3749    pub fn height() -> i32 {
3750        unsafe { bindings::lv_display_get_vertical_resolution(core::ptr::null_mut()) }
3751    }
3752
3753    /// DPI of the default display.
3754    pub fn dpi() -> i32 {
3755        unsafe { bindings::lv_display_get_dpi(core::ptr::null_mut()) }
3756    }
3757}
3758
3759// =========================================================================
3760//  Top-layer access
3761// =========================================================================
3762
3763/// Top-most overlay layer of the default display (sits above all screens).
3764pub fn layer_top() -> Obj {
3765    let raw = unsafe { bindings::lv_layer_top() };
3766    unsafe { Obj::from_raw(raw) }
3767}
3768
3769// =========================================================================
3770//  Text metrics
3771// =========================================================================
3772
3773/// Measure the rendered size of `text` using `font`. Returns `(width, height)`
3774/// in pixels.
3775///
3776/// `text` must be NUL-terminated (LVGL reads C strings).
3777pub fn text_size(text: &[u8], font: *const bindings::lv_font_t) -> (i32, i32) {
3778    debug_assert!(
3779        text.last() == Some(&0),
3780        "text_size: text must be NUL-terminated"
3781    );
3782    let mut p = bindings::lv_point_t { x: 0, y: 0 };
3783    unsafe {
3784        bindings::lv_text_get_size(&mut p, text.as_ptr() as *const _, font, 0, 0, i32::MAX, 0);
3785    }
3786    (p.x, p.y)
3787}
3788
3789// =========================================================================
3790//  Animation extras
3791// =========================================================================
3792
3793impl Animation {
3794    /// Configure the animation duration based on a movement speed
3795    /// (LVGL's `lv_anim_speed`). Returns the precomputed duration value.
3796    pub fn duration_for_speed(speed: u32) -> u32 {
3797        unsafe { bindings::lv_anim_speed(speed) }
3798    }
3799
3800    /// Set both the animation target and a safe tick callback.
3801    ///
3802    /// `on_tick(obj, v)` is invoked every frame with the target widget
3803    /// (as [`Obj`]) and the interpolated value. Uses LVGL's custom exec
3804    /// callback so the `fn` pointer is smuggled through `user_data`.
3805    pub fn tick_fn<W: Widget>(mut self, obj: W, on_tick: fn(Obj, i32)) -> Self {
3806        unsafe {
3807            bindings::lv_anim_set_var(&mut self.inner, obj.raw() as *mut _);
3808            bindings::lv_anim_set_user_data(&mut self.inner, on_tick as *mut _);
3809            bindings::lv_anim_set_custom_exec_cb(
3810                &mut self.inner,
3811                Some(anim_custom_tick_trampoline),
3812            );
3813        }
3814        self
3815    }
3816}
3817
3818unsafe extern "C" fn anim_custom_tick_trampoline(a: *mut bindings::lv_anim_t, v: i32) {
3819    unsafe {
3820        let ud = bindings::lv_anim_get_user_data(a);
3821        if ud.is_null() {
3822            return;
3823        }
3824        // SAFETY: `ud` was stored from a `fn(Obj, i32)` pointer by the
3825        // custom-tick animation setup. Function pointers round-trip through
3826        // `*mut c_void` on supported targets.
3827        let cb: fn(Obj, i32) = core::mem::transmute(ud);
3828        let var = (*a).var;
3829        cb(
3830            Obj {
3831                raw: var as *mut bindings::lv_obj_t,
3832            },
3833            v,
3834        );
3835    }
3836}
3837
3838// Internal trampolines for additional convenience animators.
3839unsafe extern "C" fn anim_translate_y_shim(var: *mut core::ffi::c_void, v: i32) {
3840    unsafe {
3841        bindings::lv_obj_set_style_translate_y(var as *mut bindings::lv_obj_t, v, PART_MAIN);
3842    }
3843}
3844
3845unsafe extern "C" fn anim_scroll_y_shim(var: *mut core::ffi::c_void, v: i32) {
3846    unsafe {
3847        bindings::lv_obj_scroll_to_y(var as *mut bindings::lv_obj_t, v, false);
3848    }
3849}
3850
3851unsafe extern "C" fn anim_arc_value_shim(var: *mut core::ffi::c_void, v: i32) {
3852    unsafe {
3853        bindings::lv_arc_set_value(var as *mut bindings::lv_obj_t, v);
3854    }
3855}
3856
3857/// Animate the `translate_y` style property from `from` to `to` over
3858/// `duration_ms` (ease-in-out).
3859pub fn animate_translate_y(obj: impl Widget, from: i32, to: i32, duration_ms: u32) {
3860    Animation::new()
3861        .target(obj.raw() as *mut _)
3862        .values(from, to)
3863        .duration(duration_ms)
3864        .path(path_ease_in_out())
3865        .exec_cb(Some(anim_translate_y_shim))
3866        .start();
3867}
3868
3869/// Animate the vertical scroll position to `to` (no easing — direct scroll).
3870pub fn animate_scroll_y(obj: impl Widget, from: i32, to: i32, duration_ms: u32) {
3871    Animation::new()
3872        .target(obj.raw() as *mut _)
3873        .values(from, to)
3874        .duration(duration_ms)
3875        .path(path_linear())
3876        .exec_cb(Some(anim_scroll_y_shim))
3877        .start();
3878}
3879
3880/// Animate an [`Arc`] widget's value from `from` to `to` over `duration_ms`.
3881pub fn animate_arc_value(arc: Arc, from: i32, to: i32, duration_ms: u32) {
3882    Animation::new()
3883        .target(arc.raw() as *mut _)
3884        .values(from, to)
3885        .duration(duration_ms)
3886        .path(path_ease_in_out())
3887        .exec_cb(Some(anim_arc_value_shim))
3888        .start();
3889}
3890
3891/// Animate `translate_y` forward for `forward_ms` then back for `playback_ms`,
3892/// looping forever. Useful for subtle "shake" / bob animations.
3893pub fn animate_translate_y_playback(
3894    obj: impl Widget,
3895    from: i32,
3896    to: i32,
3897    forward_ms: u32,
3898    playback_ms: u32,
3899) {
3900    Animation::new()
3901        .target(obj.raw() as *mut _)
3902        .values(from, to)
3903        .duration(forward_ms)
3904        .playback_duration(playback_ms)
3905        .path(path_ease_in_out())
3906        .exec_cb(Some(anim_translate_y_shim))
3907        .repeat_count(ANIM_REPEAT_INFINITE)
3908        .start();
3909}
3910
3911/// Animate the vertical scroll position forward then back, looping forever.
3912pub fn animate_scroll_y_playback(
3913    obj: impl Widget,
3914    from: i32,
3915    to: i32,
3916    forward_ms: u32,
3917    playback_ms: u32,
3918) {
3919    Animation::new()
3920        .target(obj.raw() as *mut _)
3921        .values(from, to)
3922        .duration(forward_ms)
3923        .playback_duration(playback_ms)
3924        .path(path_linear())
3925        .exec_cb(Some(anim_scroll_y_shim))
3926        .repeat_count(ANIM_REPEAT_INFINITE)
3927        .start();
3928}
3929
3930/// Animate an [`Arc`] value forward then back, looping forever.
3931pub fn animate_arc_value_playback(arc: Arc, from: i32, to: i32, forward_ms: u32, playback_ms: u32) {
3932    Animation::new()
3933        .target(arc.raw() as *mut _)
3934        .values(from, to)
3935        .duration(forward_ms)
3936        .playback_duration(playback_ms)
3937        .path(path_ease_in_out())
3938        .exec_cb(Some(anim_arc_value_shim))
3939        .repeat_count(ANIM_REPEAT_INFINITE)
3940        .start();
3941}
3942
3943/// Stop all animations with any exec callback running on `obj`.
3944///
3945/// LVGL's `lv_anim_delete(var, NULL)` matches all callbacks for the given variable.
3946pub fn stop_animations(obj: impl Widget) {
3947    unsafe {
3948        bindings::lv_anim_delete(obj.raw() as *mut _, None);
3949    }
3950}
3951
3952// =========================================================================
3953//  LvCell / LvRefCell — re-exported from the top-level `cell` module
3954// =========================================================================
3955
3956pub use crate::cell::{LvCell, LvRefCell};
3957
3958// =========================================================================
3959//  ImageSrc — typed safe image source for Image::source
3960// =========================================================================
3961
3962/// A safe LVGL image source, constructible only from `'static` data.
3963///
3964/// LVGL recognizes three flavours of source pointer: an image descriptor
3965/// (`lv_image_dsc_t`), a built-in symbol byte string (`LV_SYMBOL_*`), and
3966/// a NUL-terminated filesystem path (FS driver required). All three are
3967/// covered with type-safe constructors.
3968#[derive(Clone, Copy)]
3969pub struct ImageSrc {
3970    raw: *const core::ffi::c_void,
3971}
3972
3973// SAFETY: the inner pointer is always `'static` and read-only.
3974unsafe impl Send for ImageSrc {}
3975unsafe impl Sync for ImageSrc {}
3976
3977impl ImageSrc {
3978    /// Wrap an LVGL image descriptor by pointer. The pointed-to data must
3979    /// outlive any widget using this source — `'static` in practice.
3980    ///
3981    /// Typical call: `ImageSrc::from_dsc(core::ptr::addr_of!(images::badge))`
3982    /// where `images::badge` is a generated `extern "C" static` image.
3983    pub fn from_dsc(dsc: *const ImageDsc) -> Self {
3984        Self {
3985            raw: dsc as *const _,
3986        }
3987    }
3988
3989    /// Wrap a NUL-terminated symbol byte string (e.g. `LV_SYMBOL_OK`).
3990    pub fn from_symbol(sym: &'static [u8]) -> Self {
3991        debug_assert!(sym.last() == Some(&0), "ImageSrc::from_symbol: missing NUL");
3992        Self {
3993            raw: sym.as_ptr() as *const _,
3994        }
3995    }
3996
3997    /// Wrap a NUL-terminated filesystem path (LVGL FS driver must be registered).
3998    pub fn from_path(path: &'static [u8]) -> Self {
3999        debug_assert!(path.last() == Some(&0), "ImageSrc::from_path: missing NUL");
4000        Self {
4001            raw: path.as_ptr() as *const _,
4002        }
4003    }
4004
4005    pub(crate) fn raw_ptr(self) -> *const core::ffi::c_void {
4006        self.raw
4007    }
4008}
4009
4010/// Safe re-export of the LVGL image descriptor type, so generated asset
4011/// modules (e.g. `images::badge`) can declare their externals without
4012/// needing to reach into the `ove::ffi` escape hatch.
4013pub type ImageDsc = bindings::lv_image_dsc_t;
4014
4015impl Image {
4016    /// Safe replacement for the legacy [`Image::src`] — accepts a typed
4017    /// [`ImageSrc`] guaranteeing the underlying memory is `'static`.
4018    pub fn source(self, src: ImageSrc) -> Self {
4019        unsafe { bindings::lv_image_set_src(self.raw, src.raw) };
4020        self
4021    }
4022
4023    /// Set the inner alignment of the image content (`LV_IMAGE_ALIGN_*`).
4024    pub fn inner_align(self, align: u32) -> Self {
4025        unsafe { bindings::lv_image_set_inner_align(self.raw, align as _) };
4026        self
4027    }
4028}
4029
4030// =========================================================================
4031//  ColorFormat / CanvasBuffer — typed safe canvas pixel storage
4032// =========================================================================
4033
4034/// LVGL color format selector (matches `LV_COLOR_FORMAT_*`).
4035#[derive(Clone, Copy)]
4036pub struct ColorFormat(pub bindings::lv_color_format_t);
4037
4038impl ColorFormat {
4039    pub const I1: Self = Self(0x07);
4040    pub const A8: Self = Self(0x0E);
4041    pub const RGB565: Self = Self(0x12);
4042    pub const RGB888: Self = Self(0x0F);
4043    pub const ARGB8888: Self = Self(0x10);
4044    pub const XRGB8888: Self = Self(0x11);
4045
4046    /// Bytes per pixel for this format.
4047    ///
4048    /// Returns `None` for unknown formats — the inner field is public,
4049    /// so callers can construct arbitrary `lv_color_format_t` values
4050    /// that aren't one of the named constants.  Match `None` if you
4051    /// receive a `ColorFormat` from raw FFI; otherwise use one of the
4052    /// associated constants (`I1`, `A8`, `RGB565`, `RGB888`, `ARGB8888`,
4053    /// `XRGB8888`) and `unwrap()` is safe.
4054    ///
4055    /// `I1` returns `Some(0)` — the format is sub-byte per pixel and
4056    /// callers must size the canvas buffer externally (one bit per pixel,
4057    /// rounded up to a whole byte per row).
4058    pub const fn bpp(self) -> Option<usize> {
4059        match self.0 {
4060            0x10 | 0x11 => Some(4), // ARGB8888 / XRGB8888
4061            0x0F => Some(3),        // RGB888
4062            0x12 => Some(2),        // RGB565
4063            0x0E => Some(1),        // A8
4064            0x07 => Some(0),        // I1: < 1 byte/px (caller must size externally)
4065            _ => None,
4066        }
4067    }
4068}
4069
4070/// Pixel storage for a [`Canvas`]. Borrows the buffer for the lifetime `'a`.
4071pub struct CanvasBuffer<'a> {
4072    ptr: *mut u8,
4073    w: i32,
4074    h: i32,
4075    cf: ColorFormat,
4076    _ph: core::marker::PhantomData<&'a mut [u8]>,
4077}
4078
4079impl<'a> CanvasBuffer<'a> {
4080    /// Wrap a mutable byte slice as canvas pixel storage.
4081    ///
4082    /// # Panics
4083    /// Panics if `cf` is not one of the named [`ColorFormat`] constants
4084    /// (unknown formats have no defined byte-per-pixel ratio), or if
4085    /// `buf.len() != w * h * cf.bpp()`.  Sizing errors are programming
4086    /// bugs, not runtime conditions.
4087    pub fn new(buf: &'a mut [u8], w: i32, h: i32, cf: ColorFormat) -> Self {
4088        let bpp = cf.bpp().expect("CanvasBuffer: unknown ColorFormat");
4089        let need = (w as usize) * (h as usize) * bpp;
4090        assert!(
4091            buf.len() == need,
4092            "CanvasBuffer: expected {} bytes, got {}",
4093            need,
4094            buf.len()
4095        );
4096        Self {
4097            ptr: buf.as_mut_ptr(),
4098            w,
4099            h,
4100            cf,
4101            _ph: core::marker::PhantomData,
4102        }
4103    }
4104}
4105
4106impl Canvas {
4107    /// Safe replacement for the legacy [`Canvas::buffer`] — accepts a
4108    /// typed [`CanvasBuffer`] borrowed for at least as long as the canvas
4109    /// is used.
4110    pub fn set_buffer(self, buf: CanvasBuffer<'_>) -> Self {
4111        unsafe {
4112            bindings::lv_canvas_set_buffer(self.raw, buf.ptr as *mut _, buf.w, buf.h, buf.cf.0);
4113        }
4114        self
4115    }
4116}
4117
4118// =========================================================================
4119//  Scale — radial / linear scale widget
4120// =========================================================================
4121
4122/// LVGL scale modes (`LV_SCALE_MODE_*`).
4123pub const SCALE_MODE_HORIZONTAL_TOP: u32 = 0x00;
4124pub const SCALE_MODE_HORIZONTAL_BOTTOM: u32 = 0x01;
4125pub const SCALE_MODE_VERTICAL_LEFT: u32 = 0x02;
4126pub const SCALE_MODE_VERTICAL_RIGHT: u32 = 0x04;
4127pub const SCALE_MODE_ROUND_INNER: u32 = 0x08;
4128pub const SCALE_MODE_ROUND_OUTER: u32 = 0x10;
4129
4130/// LVGL scale widget — linear or radial scale with ticks and labels.
4131#[derive(Clone, Copy)]
4132pub struct Scale {
4133    raw: *mut bindings::lv_obj_t,
4134}
4135
4136/// Handle to an `lv_scale_section_t *`.
4137#[derive(Clone, Copy)]
4138pub struct ScaleSection {
4139    raw: *mut bindings::lv_scale_section_t,
4140}
4141
4142impl Scale {
4143    /// Create a new scale as a child of `parent`.
4144    pub fn create(parent: impl Widget) -> Self {
4145        let raw = unsafe { bindings::lv_scale_create(parent.raw()) };
4146        Self { raw }
4147    }
4148
4149    /// Fluent: set the layout mode (use `SCALE_MODE_*`).
4150    pub fn mode(self, mode: u32) -> Self {
4151        unsafe { bindings::lv_scale_set_mode(self.raw, mode as _) };
4152        self
4153    }
4154
4155    /// Fluent: set the value range.
4156    pub fn range(self, min: i32, max: i32) -> Self {
4157        unsafe { bindings::lv_scale_set_range(self.raw, min, max) };
4158        self
4159    }
4160
4161    /// Fluent: total number of tick marks.
4162    pub fn total_tick_count(self, count: u32) -> Self {
4163        unsafe { bindings::lv_scale_set_total_tick_count(self.raw, count) };
4164        self
4165    }
4166
4167    /// Fluent: every Nth tick is a major tick.
4168    pub fn major_tick_every(self, nth: u32) -> Self {
4169        unsafe { bindings::lv_scale_set_major_tick_every(self.raw, nth) };
4170        self
4171    }
4172
4173    /// Fluent: angle range for round scales.
4174    pub fn angle_range(self, angle: u32) -> Self {
4175        unsafe { bindings::lv_scale_set_angle_range(self.raw, angle) };
4176        self
4177    }
4178
4179    /// Fluent: rotation offset for round scales.
4180    pub fn rotation(self, rot: i32) -> Self {
4181        unsafe { bindings::lv_scale_set_rotation(self.raw, rot as _) };
4182        self
4183    }
4184
4185    /// Add a coloured/styled section to the scale.
4186    pub fn add_section(self) -> ScaleSection {
4187        let raw = unsafe { bindings::lv_scale_add_section(self.raw) };
4188        ScaleSection { raw }
4189    }
4190}
4191
4192impl ScaleSection {
4193    /// Set the section value range.
4194    pub fn range(self, min: i32, max: i32) -> Self {
4195        unsafe { bindings::lv_scale_section_set_range(self.raw, min, max) };
4196        self
4197    }
4198
4199    /// Apply a [`Style`] to the given part of the section.
4200    ///
4201    /// Takes `&Style` — the style must outlive the scale (typical use:
4202    /// store in a `InitCell<Style>` or `&'static`).
4203    pub fn style(self, part: u32, style: &Style) -> Self {
4204        unsafe { bindings::lv_scale_section_set_style(self.raw, part, style.ptr()) };
4205        self
4206    }
4207}
4208
4209impl Widget for Scale {
4210    fn raw(self) -> *mut bindings::lv_obj_t {
4211        self.raw
4212    }
4213}
4214
4215impl core::ops::Deref for Scale {
4216    type Target = Obj;
4217    fn deref(&self) -> &Obj {
4218        unsafe { &*(self as *const Scale as *const Obj) }
4219    }
4220}
4221
4222// SAFETY: `Scale` wraps an `lv_scale_t` widget; same LVGL-lock contract
4223// as the widget block above.
4224unsafe impl Send for Scale {}
4225unsafe impl Sync for Scale {}
4226
4227// =========================================================================
4228//  LvTimer extension — safe `fn`-callback constructor
4229// =========================================================================
4230
4231unsafe extern "C" fn lvgl_timer_trampoline_fn(t: *mut bindings::lv_timer_t) {
4232    let ud = unsafe { bindings::lv_timer_get_user_data(t) };
4233    if ud.is_null() {
4234        return;
4235    }
4236    // SAFETY: stored as a `fn()` pointer by `Timer::new_fn`.
4237    let cb: fn() = unsafe { core::mem::transmute(ud) };
4238    cb();
4239}
4240
4241impl Timer {
4242    /// Create an LVGL timer with a safe `fn()` callback.
4243    ///
4244    /// The function pointer is smuggled through `lv_timer_t::user_data`;
4245    /// an internal trampoline calls it with no arguments.
4246    pub fn new_fn(callback: fn(), period_ms: u32) -> Self {
4247        let ud = callback as *mut core::ffi::c_void;
4248        let raw =
4249            unsafe { bindings::lv_timer_create(Some(lvgl_timer_trampoline_fn), period_ms, ud) };
4250        Self { raw }
4251    }
4252}
4253
4254// =========================================================================
4255//  Subject extension — safe Obj <-> Subject linking
4256// =========================================================================
4257
4258impl<T: Copy + Into<i32> + TryFrom<i32>> State<T> {
4259    /// Attach an observer that automatically refreshes `obj` when this
4260    /// state changes. The actual rebinding logic is on the widget side
4261    /// (e.g. [`Label::bind_text`]) — this is a low-level escape hatch.
4262    pub fn observe(&self, obj: impl Widget) {
4263        unsafe {
4264            bindings::lv_subject_add_observer_obj(
4265                self.subject_ptr(),
4266                None,
4267                obj.raw(),
4268                core::ptr::null_mut(),
4269            );
4270        }
4271    }
4272}