graphics/
theme.rs

1use alloc::boxed::Box;
2
3use crate::{Color, Display, fonts, lvgl, palette};
4
5pub const PRIMARY_COLOR: Color = Color::new(0xFA, 0xFA, 0xFA);
6pub const BACKGROUND_COLOR_PRIMARY: Color = Color::new(0x09, 0x09, 0x0b);
7pub const BACKGROUND_COLOR_PRIMARY_MUTED: Color = Color::new(0x18, 0x18, 0x1b);
8pub const BORDER_COLOR_PRIMARY: Color = Color::new(0x27, 0x27, 0x2a);
9pub const SECONDARY_COLOR: Color = palette::get(palette::Hue::Red, palette::Tone::MAIN);
10pub const IS_DARK: bool = true;
11
12pub const BORDER_WIDTH: i32 = 2;
13pub const RADIUS: i32 = BORDER_WIDTH * 4;
14pub const PADDING: i32 = 16;
15
16/// Rust representation of LVGL's `lv_theme_t` structure
17///
18/// This struct is C FFI compatible and must match the memory layout of the C struct.
19#[derive(Clone, Copy)]
20#[repr(C)]
21#[allow(dead_code)]
22pub(crate) struct Theme {
23    pub apply_cb: Option<lvgl::lv_theme_apply_cb_t>,
24    /// Apply the current theme's style on top of this theme.
25    pub parent: *mut Theme,
26    pub user_data: *mut core::ffi::c_void,
27    pub disp: *mut lvgl::lv_display_t,
28    pub color_primary: lvgl::lv_color_t,
29    pub color_secondary: lvgl::lv_color_t,
30    pub font_small: *const lvgl::lv_font_t,
31    pub font_normal: *const lvgl::lv_font_t,
32    pub font_large: *const lvgl::lv_font_t,
33    /// Any custom flag used by the theme
34    pub flags: u32,
35}
36
37pub(crate) fn initialize(display: &Display) {
38    update(display, PRIMARY_COLOR, SECONDARY_COLOR, IS_DARK);
39
40    unsafe {
41        let default_theme = lvgl::lv_theme_default_get() as *const Theme;
42
43        let theme = Box::new(*default_theme);
44        let theme = Box::into_raw(theme) as *mut lvgl::lv_theme_t;
45
46        lvgl::lv_theme_set_parent(theme, default_theme as *mut lvgl::lv_theme_t);
47        lvgl::lv_theme_set_apply_cb(theme, Some(theme_apply));
48        lvgl::lv_display_set_theme(display.get_lvgl_display(), theme);
49        lvgl::lv_obj_set_style_bg_color(
50            display.get_object(),
51            BACKGROUND_COLOR_PRIMARY.into_lvgl_color(),
52            lvgl::LV_PART_MAIN,
53        );
54    }
55}
56
57pub(crate) fn update(
58    display: &Display,
59    primary_color: Color,
60    secondary_color: Color,
61    is_dark: bool,
62) {
63    unsafe {
64        lvgl::lv_theme_default_init(
65            display.get_lvgl_display(),
66            primary_color.into_lvgl_color(),
67            secondary_color.into_lvgl_color(),
68            is_dark,
69            fonts::get_font_medium(),
70        );
71    }
72}
73
74pub fn get_background_color_primary() -> Color {
75    BACKGROUND_COLOR_PRIMARY
76}
77
78pub fn get_background_color_primary_muted() -> Color {
79    BACKGROUND_COLOR_PRIMARY_MUTED
80}
81
82pub fn get_primary_color() -> Color {
83    PRIMARY_COLOR
84}
85
86pub fn get_secondary_color() -> Color {
87    SECONDARY_COLOR
88}
89
90pub fn get_border_color_primary() -> Color {
91    BORDER_COLOR_PRIMARY
92}
93
94/// Apply the default style to the given object
95///
96/// # Safety
97///
98/// This function is unsafe because it dereferences raw pointers.
99pub unsafe fn apply_default_style(
100    object: *mut lvgl::lv_obj_t,
101    selector: lvgl::lv_style_selector_t,
102) {
103    unsafe {
104        lvgl::lv_obj_set_style_bg_color(
105            object,
106            BACKGROUND_COLOR_PRIMARY.into_lvgl_color(),
107            selector,
108        );
109        lvgl::lv_obj_set_style_border_color(
110            object,
111            BORDER_COLOR_PRIMARY.into_lvgl_color(),
112            selector,
113        );
114    }
115}
116
117/// Theme apply callback
118///
119/// # Safety
120///
121/// This function is unsafe because it dereferences raw pointers.
122pub unsafe extern "C" fn theme_apply(_: *mut lvgl::lv_theme_t, object: *mut lvgl::lv_obj_t) {
123    unsafe {
124        let class = lvgl::lv_obj_get_class(object);
125        let parent = lvgl::lv_obj_get_parent(object);
126
127        if class == &lvgl::lv_button_class {
128            lvgl::lv_obj_set_style_bg_color(
129                object,
130                PRIMARY_COLOR.into_lvgl_color(),
131                lvgl::LV_PART_MAIN,
132            );
133            lvgl::lv_obj_set_style_text_color(
134                object,
135                BACKGROUND_COLOR_PRIMARY.into_lvgl_color(),
136                lvgl::LV_PART_MAIN,
137            );
138
139            let tab_view = lvgl::lv_obj_get_parent(parent);
140
141            if !tab_view.is_null()
142                && lvgl::lv_obj_get_child(tab_view, 0) == parent
143                && lvgl::lv_obj_check_type(tab_view, &lvgl::lv_tabview_class)
144            {
145                lvgl::lv_obj_set_style_pad_all(object, BORDER_WIDTH * 4, lvgl::LV_PART_MAIN);
146                lvgl::lv_obj_set_style_radius(object, RADIUS, lvgl::LV_PART_MAIN);
147
148                lvgl::lv_obj_set_style_border_side(
149                    object,
150                    lvgl::lv_border_side_t_LV_BORDER_SIDE_FULL,
151                    lvgl::LV_STATE_CHECKED,
152                );
153                lvgl::lv_obj_set_style_border_side(
154                    object,
155                    lvgl::lv_border_side_t_LV_BORDER_SIDE_FULL,
156                    lvgl::LV_PART_MAIN,
157                );
158                lvgl::lv_obj_set_style_border_color(
159                    object,
160                    PRIMARY_COLOR.into_lvgl_color(),
161                    lvgl::LV_STATE_CHECKED,
162                );
163                lvgl::lv_obj_set_style_border_color(
164                    object,
165                    PRIMARY_COLOR.into_lvgl_color(),
166                    lvgl::LV_PART_MAIN,
167                );
168
169                lvgl::lv_obj_set_style_border_width(object, BORDER_WIDTH, lvgl::LV_PART_MAIN);
170                lvgl::lv_obj_set_style_border_width(object, BORDER_WIDTH, lvgl::LV_STATE_CHECKED);
171                lvgl::lv_obj_set_style_border_opa(
172                    object,
173                    lvgl::LV_OPA_TRANSP as _,
174                    lvgl::LV_PART_MAIN,
175                );
176                lvgl::lv_obj_set_style_border_opa(
177                    object,
178                    lvgl::LV_OPA_COVER as _,
179                    lvgl::LV_STATE_CHECKED,
180                );
181
182                lvgl::lv_obj_set_style_bg_opa(object, lvgl::LV_OPA_TRANSP as _, lvgl::LV_PART_MAIN);
183
184                lvgl::lv_obj_set_style_text_color(
185                    object,
186                    PRIMARY_COLOR.into_lvgl_color(),
187                    lvgl::LV_PART_MAIN,
188                );
189
190                lvgl::lv_obj_set_style_bg_color(
191                    parent,
192                    BACKGROUND_COLOR_PRIMARY_MUTED.into_lvgl_color(),
193                    lvgl::LV_PART_MAIN,
194                );
195                lvgl::lv_obj_set_style_radius(parent, RADIUS, lvgl::LV_PART_MAIN);
196                lvgl::lv_obj_set_style_pad_all(parent, BORDER_WIDTH * 2, lvgl::LV_PART_MAIN);
197                lvgl::lv_obj_set_flex_align(
198                    parent,
199                    lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER,
200                    lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER,
201                    lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER,
202                );
203            }
204        } else if class == &lvgl::lv_buttonmatrix_class {
205            apply_default_style(object, lvgl::LV_PART_MAIN);
206            apply_default_style(object, lvgl::LV_PART_ITEMS);
207            lvgl::lv_obj_set_style_text_color(
208                object,
209                PRIMARY_COLOR.into_lvgl_color(),
210                lvgl::LV_PART_MAIN,
211            );
212
213            lvgl::lv_obj_set_style_border_width(object, BORDER_WIDTH, lvgl::LV_PART_ITEMS);
214            lvgl::lv_obj_set_style_text_color(
215                object,
216                PRIMARY_COLOR.into_lvgl_color(),
217                lvgl::LV_PART_ITEMS,
218            );
219        } else if class == &lvgl::lv_checkbox_class {
220            apply_default_style(object, lvgl::LV_PART_MAIN);
221            apply_default_style(object, lvgl::LV_PART_INDICATOR);
222            lvgl::lv_obj_set_style_border_color(
223                object,
224                PRIMARY_COLOR.into_lvgl_color(),
225                lvgl::LV_PART_INDICATOR | lvgl::LV_STATE_CHECKED,
226            );
227            lvgl::lv_obj_set_style_text_color(
228                object,
229                BACKGROUND_COLOR_PRIMARY.into_lvgl_color(),
230                lvgl::LV_PART_INDICATOR | lvgl::LV_STATE_CHECKED,
231            );
232        } else if class == &lvgl::lv_list_text_class {
233            apply_default_style(object, lvgl::LV_PART_MAIN);
234            lvgl::lv_obj_set_style_pad_left(object, PADDING, lvgl::LV_PART_MAIN);
235            lvgl::lv_obj_set_style_bg_color(
236                object,
237                BACKGROUND_COLOR_PRIMARY_MUTED.into_lvgl_color(),
238                lvgl::LV_PART_MAIN,
239            );
240        } else if class == &lvgl::lv_list_button_class {
241            apply_default_style(object, lvgl::LV_PART_MAIN);
242            lvgl::lv_obj_set_style_pad_left(object, 2 * PADDING, lvgl::LV_PART_MAIN);
243        } else if class == &lvgl::lv_switch_class {
244            apply_default_style(object, lvgl::LV_PART_MAIN);
245            apply_default_style(object, lvgl::LV_PART_INDICATOR);
246            lvgl::lv_obj_set_style_bg_color(
247                object,
248                BACKGROUND_COLOR_PRIMARY.into_lvgl_color(),
249                lvgl::LV_PART_KNOB | lvgl::LV_STATE_CHECKED,
250            );
251        } else {
252            apply_default_style(object, lvgl::LV_PART_MAIN);
253        }
254    }
255}