What changed, and why it matters
This commit fixes a bug in how the BitBox02 hardware wallet's user-interface library clears style transitions. Previously, passing 'None' for a transition style would store a null pointer in LVGL's style state, which could later be dereferenced during state changes and crash or corrupt the UI. The patch now removes the local transition property instead of storing a null pointer, and wraps the transition setter in a safer Rust type. It also tightens other optional pointer-style setters and grid-template inputs so null pointers cannot be silently stored. The change is defensive hardening rather than a demonstrated remote exploit, but on a security device any UI crash or memory corruption is relevant.
Treat as a defensive fix and include in the next firmware release. Review other LVGL wrapper functions for the same anti-pattern (passing NULL for optional pointer-valued styles). No immediate incident response is indicated because no external exploit or disclosure is referenced.
Security signals we found
Null-pointer dereference risk in LVGL transition style handling eliminated by removing the local property instead of storing NULL
Optional pointer style setters now remove the property on None rather than passing NULL to LVGL
Transition descriptor access restricted to crate-private, forcing callers through the LvStyleTransition wrapper
Grid template arrays now enforced at compile time to be non-empty and LV_GRID_TEMPLATE_LAST-terminated
LvFont constructor marked unsafe with documented lifetime contract
New regression tests added for transition/optional-pointer None behavior
Evidence from the diff
The patch refactors bitbox-lvgl’s style setters. Key changes: (1) LvStyleTransition::as_dsc() is made crate-private and ObjExt::set_style_transition now takes Option<&’static LvStyleTransition> instead of a raw descriptor pointer, preventing callers from passing arbitrary or null descriptors. (2) When None is passed, the code calls lv_obj_remove_local_style_prop(LV_STYLE_TRANSITION, …) instead of lv_obj_set_style_transition(…, NULL), because LVGL dereferences the transition descriptor during state changes and a stored null pointer is unsafe. (3) The same None-handling is applied to other optional reference/void-pointer style setters (bg_grad, image_colorkey, color_filter_dsc, anim, bg_image_src, arc_image_src, bitmap_mask_src) and to span styles. (4) Grid column/row descriptor arrays now require a new LvGridTemplate type that is compile-time validated to be non-empty and terminated by LV_GRID_TEMPLATE_LAST, replacing runtime panic-on-bad-slice logic. (5) LvFont::new is marked unsafe with a documented contract. New unit tests verify that setting transition/anim/bg_image_src/grid_column to None removes the property. The commit is purely a Rust wrapper change; no C/LVGL behavior is altered except via the safer call patterns.
Changed components
src/rust/bitbox-lvgl/src/style.rssrc/rust/bitbox-lvgl/src/widgets/obj.rssrc/rust/bitbox-lvgl/src/widgets/span.rssrc/rust/bitbox-lvgl/src/font.rssrc/rust/bitbox-lvgl/src/lib.rssrc/rust/bitbox03/src/ui/nav_button.rsInspect captured patch +298 / −101
diff --git a/src/rust/bitbox-lvgl/src/font.rs b/src/rust/bitbox-lvgl/src/font.rs
index 445511d..240fba9 100644
--- a/src/rust/bitbox-lvgl/src/font.rs
+++ b/src/rust/bitbox-lvgl/src/font.rs
@@ -8,7 +8,10 @@ pub struct LvFont {
}
impl LvFont {
- pub const fn new(raw: &'static ffi::lv_font_t) -> Self {
+ /// # Safety
+ /// `raw` must point to a valid LVGL font descriptor and all pointers reachable from it must
+ /// remain valid for the program lifetime.
+ pub const unsafe fn new(raw: &'static ffi::lv_font_t) -> Self {
Self { raw }
}
@@ -44,7 +47,7 @@ mod tests {
let font = fonts::INTER_BOLD_48;
assert_eq!(
font.as_ptr(),
- LvFont::new(unsafe { &crate::ffi::inter_bold_48 }).as_ptr()
+ unsafe { LvFont::new(&crate::ffi::inter_bold_48) }.as_ptr()
);
}
}
diff --git a/src/rust/bitbox-lvgl/src/lib.rs b/src/rust/bitbox-lvgl/src/lib.rs
index e32b410..41a615e 100644
--- a/src/rust/bitbox-lvgl/src/lib.rs
+++ b/src/rust/bitbox-lvgl/src/lib.rs
@@ -71,7 +71,7 @@ pub use display::{LvDisplay, LvDisplayBufferError};
pub use font::LvFont;
pub use font::fonts;
pub use indev::LvIndev;
-pub use style::LvStyleTransition;
+pub use style::{LvGridTemplate, LvStyleTransition};
pub use widgets::arc::{ArcExt, LvArc};
pub use widgets::bar::{BarExt, LvBar};
pub use widgets::button::{ButtonExt, LvButton};
diff --git a/src/rust/bitbox-lvgl/src/style.rs b/src/rust/bitbox-lvgl/src/style.rs
index e90565f..d29c0a0 100644
--- a/src/rust/bitbox-lvgl/src/style.rs
+++ b/src/rust/bitbox-lvgl/src/style.rs
@@ -42,13 +42,33 @@ impl LvStyleTransition {
})
}
- /// Borrows the underlying descriptor for [`crate::ObjExt::set_style_transition`]. When `self` is
- /// a `static`, the returned reference is `'static`.
- pub fn as_dsc(&self) -> &ffi::lv_style_transition_dsc_t {
+ pub(crate) fn as_dsc(&self) -> &ffi::lv_style_transition_dsc_t {
&self.0
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct LvGridTemplate {
+ values: &'static [i32],
+}
+
+impl LvGridTemplate {
+ /// `values` must be a non-empty `'static` slice terminated by `LV_GRID_TEMPLATE_LAST`.
+ pub const fn new(values: &'static [i32]) -> Self {
+ if values.is_empty() {
+ panic!("grid template must not be empty");
+ }
+ if values[values.len() - 1] != ffi::LV_GRID_TEMPLATE_LAST as i32 {
+ panic!("grid template must be terminated by LV_GRID_TEMPLATE_LAST");
+ }
+ Self { values }
+ }
+
+ pub(crate) fn as_ptr(self) -> *const i32 {
+ self.values.as_ptr()
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -75,4 +95,24 @@ mod tests {
fn test_style_transition_new_rejects_empty_props() {
let _ = LvStyleTransition::new(&[], 1, 0);
}
+
+ #[test]
+ fn test_grid_template_new() {
+ const TEMPLATE_VALUES: &[i32] = &[42, ffi::LV_GRID_TEMPLATE_LAST as i32];
+ const TEMPLATE: LvGridTemplate = LvGridTemplate::new(TEMPLATE_VALUES);
+
+ assert_eq!(TEMPLATE.as_ptr(), TEMPLATE_VALUES.as_ptr());
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_grid_template_new_rejects_empty_values() {
+ let _ = LvGridTemplate::new(&[]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_grid_template_new_rejects_missing_terminator() {
+ let _ = LvGridTemplate::new(&[42]);
+ }
}
diff --git a/src/rust/bitbox-lvgl/src/widgets/obj.rs b/src/rust/bitbox-lvgl/src/widgets/obj.rs
index e4330fd..282f115 100644
--- a/src/rust/bitbox-lvgl/src/widgets/obj.rs
+++ b/src/rust/bitbox-lvgl/src/widgets/obj.rs
@@ -8,8 +8,8 @@ use core::ptr::NonNull;
use crate::{
LvAlign, LvBaseDir, LvBlendMode, LvBorderSide, LvColor, LvEventCode, LvFlexAlign, LvFlexFlow,
- LvFont, LvGradDir, LvGridAlign, LvObjFlag, LvOpa, LvState, LvStyleSelector, LvTextAlign,
- LvTextDecor, class, ffi,
+ LvFont, LvGradDir, LvGridAlign, LvGridTemplate, LvObjFlag, LvOpa, LvState, LvStyleSelector,
+ LvStyleTransition, LvTextAlign, LvTextDecor, class, ffi,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -64,6 +64,14 @@ impl LvHandle<class::ObjTag> {
}
}
+fn remove_local_style_prop(
+ obj: *mut ffi::lv_obj_t,
+ prop: ffi::_lv_style_id_t,
+ selector: LvStyleSelector,
+) -> bool {
+ unsafe { ffi::lv_obj_remove_local_style_prop(obj, prop as ffi::lv_style_prop_t, selector) }
+}
+
macro_rules! impl_obj_style_setter_methods {
($($name:ident => $ffi_name:ident: $value_ty:ty),+ $(,)?) => {
$(
@@ -75,15 +83,23 @@ macro_rules! impl_obj_style_setter_methods {
}
macro_rules! impl_obj_style_optional_ref_setter_methods {
- ($($name:ident => $ffi_name:ident: $value_ty:ty),+ $(,)?) => {
+ ($($name:ident => $ffi_name:ident: $prop:ident: $value_ty:ty),+ $(,)?) => {
$(
- fn $name(&self, value: Option<&'static $value_ty>, selector: LvStyleSelector) {
+ /// # Safety
+ /// LVGL stores the raw pointer in the style state. The pointed value and any
+ /// transitive pointers it contains must remain valid and must not be repurposed for as
+ /// long as the style can be used.
+ unsafe fn $name(&self, value: Option<&'static $value_ty>, selector: LvStyleSelector) {
unsafe {
- ffi::$ffi_name(
- self.as_ptr(),
- value.map_or(core::ptr::null(), |value| value as *const $value_ty),
- selector,
- )
+ if let Some(value) = value {
+ ffi::$ffi_name(self.as_ptr(), value as *const $value_ty, selector);
+ } else {
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::$prop,
+ selector,
+ );
+ }
}
}
)+
@@ -91,7 +107,7 @@ macro_rules! impl_obj_style_optional_ref_setter_methods {
}
macro_rules! impl_obj_style_optional_void_ptr_setter_methods {
- ($($name:ident => $ffi_name:ident),+ $(,)?) => {
+ ($($name:ident => $ffi_name:ident: $prop:ident),+ $(,)?) => {
$(
/// # Safety
/// The pointed value type must exactly match what LVGL expects for this style field.
@@ -100,11 +116,15 @@ macro_rules! impl_obj_style_optional_void_ptr_setter_methods {
/// fields must also satisfy LVGL's image source tagging rules.
unsafe fn $name<T>(&self, value: Option<&'static T>, selector: LvStyleSelector) {
unsafe {
- ffi::$ffi_name(
- self.as_ptr(),
- value.map_or(core::ptr::null(), |value| value as *const T as *const c_void),
- selector,
- )
+ if let Some(value) = value {
+ ffi::$ffi_name(self.as_ptr(), value as *const T as *const c_void, selector);
+ } else {
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::$prop,
+ selector,
+ );
+ }
}
}
)+
@@ -304,63 +324,97 @@ pub trait ObjExt {
);
impl_obj_style_optional_ref_setter_methods!(
- set_style_bg_grad => lv_obj_set_style_bg_grad: ffi::lv_grad_dsc_t,
- set_style_image_colorkey => lv_obj_set_style_image_colorkey: ffi::lv_image_colorkey_t,
- set_style_color_filter_dsc => lv_obj_set_style_color_filter_dsc: ffi::lv_color_filter_dsc_t,
- set_style_anim => lv_obj_set_style_anim: ffi::lv_anim_t,
- set_style_transition => lv_obj_set_style_transition: ffi::lv_style_transition_dsc_t,
+ set_style_bg_grad => lv_obj_set_style_bg_grad: LV_STYLE_BG_GRAD: ffi::lv_grad_dsc_t,
+ set_style_image_colorkey => lv_obj_set_style_image_colorkey: LV_STYLE_IMAGE_COLORKEY: ffi::lv_image_colorkey_t,
+ set_style_color_filter_dsc => lv_obj_set_style_color_filter_dsc: LV_STYLE_COLOR_FILTER_DSC: ffi::lv_color_filter_dsc_t,
+ set_style_anim => lv_obj_set_style_anim: LV_STYLE_ANIM: ffi::lv_anim_t,
);
impl_obj_style_optional_void_ptr_setter_methods!(
- set_style_bg_image_src => lv_obj_set_style_bg_image_src,
- set_style_arc_image_src => lv_obj_set_style_arc_image_src,
- set_style_bitmap_mask_src => lv_obj_set_style_bitmap_mask_src,
+ set_style_bg_image_src => lv_obj_set_style_bg_image_src: LV_STYLE_BG_IMAGE_SRC,
+ set_style_arc_image_src => lv_obj_set_style_arc_image_src: LV_STYLE_ARC_IMAGE_SRC,
+ set_style_bitmap_mask_src => lv_obj_set_style_bitmap_mask_src: LV_STYLE_BITMAP_MASK_SRC,
);
+ /// Sets or removes the local transition style property.
+ ///
+ /// LVGL dereferences a present transition descriptor during state changes, so `None` removes
+ /// the local property instead of storing a null transition pointer. This can reveal a
+ /// themed/shared-style transition again. To locally suppress one, set a static
+ /// [`LvStyleTransition`] listing the same properties with a near-zero duration; local
+ /// transition properties are collected first and de-duplicated by LVGL. See
+ /// `bitbox03/src/ui/nav_button.rs`'s `PRESS_TRANSITION` for the pattern.
+ fn set_style_transition(
+ &self,
+ value: Option<&'static LvStyleTransition>,
+ selector: LvStyleSelector,
+ ) {
+ unsafe {
+ if let Some(value) = value {
+ ffi::lv_obj_set_style_transition(self.as_ptr(), value.as_dsc(), selector);
+ } else {
+ self.remove_style_transition(selector);
+ }
+ }
+ }
+
+ /// Removes the local transition style property. Returns whether a property was removed.
+ fn remove_style_transition(&self, selector: LvStyleSelector) -> bool {
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_TRANSITION,
+ selector,
+ )
+ }
+
fn set_style_text_font(&self, value: LvFont, selector: LvStyleSelector) {
unsafe { ffi::lv_obj_set_style_text_font(self.as_ptr(), value.as_ptr(), selector) }
}
fn remove_style_text_font(&self, selector: LvStyleSelector) -> bool {
- unsafe {
- ffi::lv_obj_remove_local_style_prop(
- self.as_ptr(),
- ffi::_lv_style_id_t::LV_STYLE_TEXT_FONT as ffi::lv_style_prop_t,
- selector,
- )
- }
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_TEXT_FONT,
+ selector,
+ )
}
fn set_style_grid_column_dsc_array(
&self,
- value: Option<&'static [i32]>,
+ value: Option<LvGridTemplate>,
selector: LvStyleSelector,
) {
- if let Some(value) = value
- && let Some(last) = value.last()
- {
- if *last != ffi::LV_GRID_TEMPLATE_LAST as i32 {
- panic!("invalid input");
- }
- unsafe {
- ffi::lv_obj_set_style_grid_column_dsc_array(self.as_ptr(), value.as_ptr(), selector)
+ unsafe {
+ if let Some(value) = value {
+ ffi::lv_obj_set_style_grid_column_dsc_array(
+ self.as_ptr(),
+ value.as_ptr(),
+ selector,
+ );
+ } else {
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_GRID_COLUMN_DSC_ARRAY,
+ selector,
+ );
}
}
}
fn set_style_grid_row_dsc_array(
&self,
- value: Option<&'static [i32]>,
+ value: Option<LvGridTemplate>,
selector: LvStyleSelector,
) {
- if let Some(value) = value
- && let Some(last) = value.last()
- {
- if *last != ffi::LV_GRID_TEMPLATE_LAST as i32 {
- panic!("invalid input");
- }
- unsafe {
- ffi::lv_obj_set_style_grid_row_dsc_array(self.as_ptr(), value.as_ptr(), selector)
+ unsafe {
+ if let Some(value) = value {
+ ffi::lv_obj_set_style_grid_row_dsc_array(self.as_ptr(), value.as_ptr(), selector);
+ } else {
+ remove_local_style_prop(
+ self.as_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_GRID_ROW_DSC_ARRAY,
+ selector,
+ );
}
}
}
@@ -389,6 +443,9 @@ mod tests {
let _: fn(&LvObj, LvStyleSelector) -> bool = <LvObj as ObjExt>::remove_style_text_font;
let _: unsafe fn(&LvObj, Option<&'static u8>, LvStyleSelector) =
<LvObj as ObjExt>::set_style_bg_image_src::<u8>;
+ let _: fn(&LvObj, Option<&'static LvStyleTransition>, LvStyleSelector) =
+ <LvObj as ObjExt>::set_style_transition;
+ let _: fn(&LvObj, LvStyleSelector) -> bool = <LvObj as ObjExt>::remove_style_transition;
let _: fn(&LvObj, crate::LvEventCode, fn()) -> Result<(), crate::LvEventRegistrationError> =
<LvObj as ObjExt>::add_event_cb::<fn()>;
let _: fn(&LvObj, fn()) -> Result<(), crate::LvEventRegistrationError> =
@@ -422,6 +479,97 @@ mod tests {
unsafe { obj.delete() };
}
+ #[test]
+ fn test_set_style_transition_none_removes_property() {
+ const TRANSITION_PROPS: &[u8] = &[crate::style::prop::BG_OPA, crate::style::prop::INV];
+ static TRANSITION: LvStyleTransition = LvStyleTransition::new(TRANSITION_PROPS, 1, 0);
+
+ let _lock = crate::test_util::lock_and_init();
+
+ let display = crate::LvDisplay::new(16, 16).unwrap();
+ let screen = display.screen_active().unwrap();
+ let obj = LvObj::with_parent(&screen).unwrap();
+
+ obj.set_style_transition(Some(&TRANSITION), 0);
+ assert!(unsafe {
+ ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_TRANSITION as ffi::lv_style_prop_t,
+ )
+ });
+
+ obj.set_style_transition(None, 0);
+ assert!(!unsafe {
+ ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_TRANSITION as ffi::lv_style_prop_t,
+ )
+ });
+
+ obj.set_style_transition(Some(&TRANSITION), 0);
+ assert!(obj.remove_style_transition(0));
+ assert!(!obj.remove_style_transition(0));
+
+ unsafe { obj.delete() };
+ }
+
+ #[test]
+ fn test_set_optional_pointer_style_none_removes_property() {
+ let _lock = crate::test_util::lock_and_init();
+
+ let display = crate::LvDisplay::new(16, 16).unwrap();
+ let screen = display.screen_active().unwrap();
+ let obj = LvObj::with_parent(&screen).unwrap();
+
+ unsafe {
+ ffi::lv_obj_set_style_anim(obj.as_ptr(), core::ptr::null(), 0);
+ assert!(ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_ANIM as ffi::lv_style_prop_t,
+ ));
+
+ obj.set_style_anim(None, 0);
+ assert!(!ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_ANIM as ffi::lv_style_prop_t,
+ ));
+
+ ffi::lv_obj_set_style_bg_image_src(obj.as_ptr(), core::ptr::null(), 0);
+ assert!(ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_BG_IMAGE_SRC as ffi::lv_style_prop_t,
+ ));
+
+ obj.set_style_bg_image_src::<u8>(None, 0);
+ assert!(!ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_BG_IMAGE_SRC as ffi::lv_style_prop_t,
+ ));
+
+ ffi::lv_obj_set_style_grid_column_dsc_array(obj.as_ptr(), core::ptr::null(), 0);
+ assert!(ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_GRID_COLUMN_DSC_ARRAY as ffi::lv_style_prop_t,
+ ));
+
+ obj.set_style_grid_column_dsc_array(None, 0);
+ assert!(!ffi::lv_obj_has_style_prop(
+ obj.as_ptr(),
+ 0,
+ ffi::_lv_style_id_t::LV_STYLE_GRID_COLUMN_DSC_ARRAY as ffi::lv_style_prop_t,
+ ));
+
+ obj.delete();
+ }
+ }
+
#[test]
fn test_add_event_cb_delete_event_invokes_callback() {
let _lock = crate::test_util::lock_and_init();
diff --git a/src/rust/bitbox-lvgl/src/widgets/span.rs b/src/rust/bitbox-lvgl/src/widgets/span.rs
index cb514ff..e39322a 100644
--- a/src/rust/bitbox-lvgl/src/widgets/span.rs
+++ b/src/rust/bitbox-lvgl/src/widgets/span.rs
@@ -9,8 +9,8 @@ use alloc::ffi::CString;
use crate::{
LvAlign, LvBaseDir, LvBlendMode, LvBorderSide, LvColor, LvFlexAlign, LvFlexFlow, LvFont,
- LvGradDir, LvGridAlign, LvHandle, LvObj, LvOpa, LvPoint, LvSpanCoords, LvSpanMode,
- LvSpanOverflow, LvTextAlign, LvTextDecor, ObjExt, class, ffi,
+ LvGradDir, LvGridAlign, LvGridTemplate, LvHandle, LvObj, LvOpa, LvPoint, LvSpanCoords,
+ LvSpanMode, LvSpanOverflow, LvTextAlign, LvTextDecor, ObjExt, class, ffi,
};
pub type LvSpanTextError = super::LvTextError;
@@ -31,15 +31,24 @@ macro_rules! impl_span_style_setter_methods {
};
}
+fn remove_style_prop(style: *mut ffi::lv_style_t, prop: ffi::_lv_style_id_t) -> bool {
+ unsafe { ffi::lv_style_remove_prop(style, prop as ffi::lv_style_prop_t) }
+}
+
macro_rules! impl_span_style_optional_ref_setter_methods {
- ($($name:ident => $ffi_name:ident: $value_ty:ty),+ $(,)?) => {
+ ($($name:ident => $ffi_name:ident: $prop:ident: $value_ty:ty),+ $(,)?) => {
$(
- pub fn $name(&self, value: Option<&'static $value_ty>) {
+ /// # Safety
+ /// LVGL stores the raw pointer in the span style. The pointed value and any transitive
+ /// pointers it contains must remain valid and must not be repurposed for as long as the
+ /// style can be used.
+ pub unsafe fn $name(&self, value: Option<&'static $value_ty>) {
unsafe {
- ffi::$ffi_name(
- self.style_ptr(),
- value.map_or(core::ptr::null(), |value| value as *const $value_ty),
- )
+ if let Some(value) = value {
+ ffi::$ffi_name(self.style_ptr(), value as *const $value_ty);
+ } else {
+ remove_style_prop(self.style_ptr(), ffi::_lv_style_id_t::$prop);
+ }
}
}
)+
@@ -47,7 +56,7 @@ macro_rules! impl_span_style_optional_ref_setter_methods {
}
macro_rules! impl_span_style_optional_void_ptr_setter_methods {
- ($($name:ident => $ffi_name:ident),+ $(,)?) => {
+ ($($name:ident => $ffi_name:ident: $prop:ident),+ $(,)?) => {
$(
/// # Safety
/// The pointed value type must exactly match what LVGL expects for this style field.
@@ -56,12 +65,11 @@ macro_rules! impl_span_style_optional_void_ptr_setter_methods {
/// fields must also satisfy LVGL's image source tagging rules.
pub unsafe fn $name<T>(&self, value: Option<&'static T>) {
unsafe {
- ffi::$ffi_name(
- self.style_ptr(),
- value.map_or(core::ptr::null(), |value| {
- value as *const T as *const core::ffi::c_void
- }),
- )
+ if let Some(value) = value {
+ ffi::$ffi_name(self.style_ptr(), value as *const T as *const core::ffi::c_void);
+ } else {
+ remove_style_prop(self.style_ptr(), ffi::_lv_style_id_t::$prop);
+ }
}
}
)+
@@ -223,17 +231,16 @@ impl LvSpan {
);
impl_span_style_optional_ref_setter_methods!(
- set_style_bg_grad => lv_style_set_bg_grad: ffi::lv_grad_dsc_t,
- set_style_image_colorkey => lv_style_set_image_colorkey: ffi::lv_image_colorkey_t,
- set_style_color_filter_dsc => lv_style_set_color_filter_dsc: ffi::lv_color_filter_dsc_t,
- set_style_anim => lv_style_set_anim: ffi::lv_anim_t,
- set_style_transition => lv_style_set_transition: ffi::lv_style_transition_dsc_t,
+ set_style_bg_grad => lv_style_set_bg_grad: LV_STYLE_BG_GRAD: ffi::lv_grad_dsc_t,
+ set_style_image_colorkey => lv_style_set_image_colorkey: LV_STYLE_IMAGE_COLORKEY: ffi::lv_image_colorkey_t,
+ set_style_color_filter_dsc => lv_style_set_color_filter_dsc: LV_STYLE_COLOR_FILTER_DSC: ffi::lv_color_filter_dsc_t,
+ set_style_anim => lv_style_set_anim: LV_STYLE_ANIM: ffi::lv_anim_t,
);
impl_span_style_optional_void_ptr_setter_methods!(
- set_style_bg_image_src => lv_style_set_bg_image_src,
- set_style_arc_image_src => lv_style_set_arc_image_src,
- set_style_bitmap_mask_src => lv_style_set_bitmap_mask_src,
+ set_style_bg_image_src => lv_style_set_bg_image_src: LV_STYLE_BG_IMAGE_SRC,
+ set_style_arc_image_src => lv_style_set_arc_image_src: LV_STYLE_ARC_IMAGE_SRC,
+ set_style_bitmap_mask_src => lv_style_set_bitmap_mask_src: LV_STYLE_BITMAP_MASK_SRC,
);
pub fn set_style_text_font(&self, value: LvFont) {
@@ -241,33 +248,32 @@ impl LvSpan {
}
pub fn remove_style_text_font(&self) -> bool {
- unsafe {
- ffi::lv_style_remove_prop(
- self.style_ptr(),
- ffi::_lv_style_id_t::LV_STYLE_TEXT_FONT as ffi::lv_style_prop_t,
- )
- }
+ remove_style_prop(self.style_ptr(), ffi::_lv_style_id_t::LV_STYLE_TEXT_FONT)
}
- pub fn set_style_grid_column_dsc_array(&self, value: Option<&'static [i32]>) {
- if let Some(value) = value
- && let Some(last) = value.last()
- {
- if *last != ffi::LV_GRID_TEMPLATE_LAST as i32 {
- panic!("invalid input");
+ pub fn set_style_grid_column_dsc_array(&self, value: Option<LvGridTemplate>) {
+ unsafe {
+ if let Some(value) = value {
+ ffi::lv_style_set_grid_column_dsc_array(self.style_ptr(), value.as_ptr());
+ } else {
+ remove_style_prop(
+ self.style_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_GRID_COLUMN_DSC_ARRAY,
+ );
}
- unsafe { ffi::lv_style_set_grid_column_dsc_array(self.style_ptr(), value.as_ptr()) }
}
}
- pub fn set_style_grid_row_dsc_array(&self, value: Option<&'static [i32]>) {
- if let Some(value) = value
- && let Some(last) = value.last()
- {
- if *last != ffi::LV_GRID_TEMPLATE_LAST as i32 {
- panic!("invalid input");
+ pub fn set_style_grid_row_dsc_array(&self, value: Option<LvGridTemplate>) {
+ unsafe {
+ if let Some(value) = value {
+ ffi::lv_style_set_grid_row_dsc_array(self.style_ptr(), value.as_ptr());
+ } else {
+ remove_style_prop(
+ self.style_ptr(),
+ ffi::_lv_style_id_t::LV_STYLE_GRID_ROW_DSC_ARRAY,
+ );
}
- unsafe { ffi::lv_style_set_grid_row_dsc_array(self.style_ptr(), value.as_ptr()) }
}
}
}
@@ -408,8 +414,8 @@ mod tests {
let _: fn(&LvSpan, crate::LvColor) = LvSpan::set_style_text_color;
let _: fn(&LvSpan, crate::LvFont) = LvSpan::set_style_text_font;
let _: fn(&LvSpan) -> bool = LvSpan::remove_style_text_font;
- let _: fn(&LvSpan, Option<&'static [i32]>) = LvSpan::set_style_grid_column_dsc_array;
- let _: fn(&LvSpan, Option<&'static [i32]>) = LvSpan::set_style_grid_row_dsc_array;
+ let _: fn(&LvSpan, Option<crate::LvGridTemplate>) = LvSpan::set_style_grid_column_dsc_array;
+ let _: fn(&LvSpan, Option<crate::LvGridTemplate>) = LvSpan::set_style_grid_row_dsc_array;
let _: unsafe fn(&LvSpan, Option<&'static u8>) = LvSpan::set_style_bg_image_src::<u8>;
}
}
diff --git a/src/rust/bitbox03/src/ui/nav_button.rs b/src/rust/bitbox03/src/ui/nav_button.rs
index d8cc42a..6286dab 100644
--- a/src/rust/bitbox03/src/ui/nav_button.rs
+++ b/src/rust/bitbox03/src/ui/nav_button.rs
@@ -85,8 +85,8 @@ fn enable_press_invert(button: &LvButton, parts: Vec<LvObj>) {
button.set_style_transform_width(0, PRESSED_SELECTOR);
button.set_style_transform_height(0, PRESSED_SELECTOR);
// Override the theme's ~80ms fade so the fill appears/clears instantly.
- button.set_style_transition(Some(PRESS_TRANSITION.as_dsc()), PRESSED_SELECTOR);
- button.set_style_transition(Some(PRESS_TRANSITION.as_dsc()), 0);
+ button.set_style_transition(Some(&PRESS_TRANSITION), PRESSED_SELECTOR);
+ button.set_style_transition(Some(&PRESS_TRANSITION), 0);
let parts = Rc::new(parts);
let on_press = Rc::clone(&parts);
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.