1use rlvgl_widgets::{
9 calendar::Calendar, message_box::MessageBox, spinbox::Spinbox, table::Table, window::Window,
10};
11
12use crate::{
13 style::Color,
14 theme::{ColorScheme, ComponentSize, ComponentStyle, SchemeColors, Theme, Variant},
15};
16
17pub trait ThemedPartsProps: Sized {
24 fn themed_parts(
26 self,
27 theme: &Theme,
28 scheme: ColorScheme,
29 variant: Variant,
30 size: ComponentSize,
31 ) -> Self;
32}
33
34impl ThemedPartsProps for Calendar {
35 fn themed_parts(
36 mut self,
37 theme: &Theme,
38 scheme: ColorScheme,
39 variant: Variant,
40 size: ComponentSize,
41 ) -> Self {
42 let resolved = theme.component_style(scheme, variant, size);
43 let colors = theme.scheme(scheme);
44 self.style = resolved.style;
45 self.day_cell_color = panel_color(resolved, colors);
46 self.selected_color = resolved.accent_color;
47 self.today_color = theme.scheme(ColorScheme::Warning).solid;
48 self.text_color = resolved.text_color;
49 self.header_text_color = colors.solid;
50 self.overflow_text_color = colors.border;
51 self
52 }
53}
54
55impl ThemedPartsProps for MessageBox {
56 fn themed_parts(
57 mut self,
58 theme: &Theme,
59 scheme: ColorScheme,
60 variant: Variant,
61 size: ComponentSize,
62 ) -> Self {
63 let resolved = theme.component_style(scheme, variant, size);
64 self.style = resolved.style;
65 self.text_color = resolved.text_color;
66 self.button_text_color = resolved.accent_color;
67 self
68 }
69}
70
71impl ThemedPartsProps for Spinbox {
72 fn themed_parts(
73 mut self,
74 theme: &Theme,
75 scheme: ColorScheme,
76 variant: Variant,
77 size: ComponentSize,
78 ) -> Self {
79 let resolved = theme.component_style(scheme, variant, size);
80 self.style = resolved.style;
81 self.text_color = resolved.text_color;
82 self.cursor_color = resolved.accent_color;
83 self
84 }
85}
86
87impl ThemedPartsProps for Table {
88 fn themed_parts(
89 mut self,
90 theme: &Theme,
91 scheme: ColorScheme,
92 variant: Variant,
93 size: ComponentSize,
94 ) -> Self {
95 let resolved = theme.component_style(scheme, variant, size);
96 let colors = theme.scheme(scheme);
97 self.style = resolved.style;
98 self.cell_bg_color = panel_color(resolved, colors);
99 self.selected_color = resolved.accent_color;
100 self.text_color = resolved.text_color;
101 self.grid_color = colors.border;
102 self
103 }
104}
105
106impl ThemedPartsProps for Window {
107 fn themed_parts(
108 mut self,
109 theme: &Theme,
110 scheme: ColorScheme,
111 variant: Variant,
112 size: ComponentSize,
113 ) -> Self {
114 let resolved = theme.component_style(scheme, variant, size);
115 let colors = theme.scheme(scheme);
116 self.style = resolved.style;
117 self.header_color = resolved.accent_color;
118 self.title_color = colors.contrast;
119 self.button_color = colors.muted;
120 self.button_text_color = colors.solid;
121 self
122 }
123}
124
125fn panel_color(resolved: ComponentStyle, colors: SchemeColors) -> Color {
126 if resolved.style.bg_color.3 == 0 {
127 colors.subtle
128 } else {
129 resolved.style.bg_color
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use rlvgl_core::widget::Rect;
137 use rlvgl_widgets::window::DEFAULT_HEADER_HEIGHT;
138
139 #[test]
140 fn table_themed_parts_apply_cell_and_selection_colors() {
141 let theme = Theme::material_light();
142 let table = Table::new(rect(0, 0, 100, 60)).themed_parts(
143 &theme,
144 ColorScheme::Info,
145 Variant::Outline,
146 ComponentSize::Lg,
147 );
148
149 assert_eq!(table.style.border_width, 2);
150 assert_eq!(table.cell_bg_color, theme.scheme(ColorScheme::Info).subtle);
151 assert_eq!(table.selected_color, theme.scheme(ColorScheme::Info).solid);
152 assert_eq!(table.grid_color, theme.scheme(ColorScheme::Info).border);
153 }
154
155 #[test]
156 fn calendar_themed_parts_apply_calendar_specific_colors() {
157 let theme = Theme::material_light();
158 let calendar = Calendar::new(rect(0, 0, 140, 120)).themed_parts(
159 &theme,
160 ColorScheme::Success,
161 Variant::Subtle,
162 ComponentSize::Md,
163 );
164
165 assert_eq!(
166 calendar.selected_color,
167 theme.scheme(ColorScheme::Success).solid
168 );
169 assert_eq!(
170 calendar.today_color,
171 theme.scheme(ColorScheme::Warning).solid
172 );
173 assert_eq!(
174 calendar.header_text_color,
175 theme.scheme(ColorScheme::Success).solid
176 );
177 }
178
179 #[test]
180 fn text_entry_direct_widgets_receive_accent_colors() {
181 let theme = Theme::material_light();
182 let spinbox = Spinbox::new(rect(0, 0, 80, 24)).themed_parts(
183 &theme,
184 ColorScheme::Danger,
185 Variant::Ghost,
186 ComponentSize::Sm,
187 );
188 let message_box = MessageBox::new(rect(0, 0, 120, 80), "Title", "Body", &["OK"])
189 .themed_parts(
190 &theme,
191 ColorScheme::Danger,
192 Variant::Ghost,
193 ComponentSize::Sm,
194 );
195
196 assert_eq!(
197 spinbox.cursor_color,
198 theme.scheme(ColorScheme::Danger).solid
199 );
200 assert_eq!(
201 message_box.button_text_color,
202 theme.scheme(ColorScheme::Danger).solid
203 );
204 }
205
206 #[test]
207 fn window_themed_parts_apply_header_palette() {
208 let theme = Theme::material_light();
209 let window = Window::new(rect(0, 0, 120, 80), DEFAULT_HEADER_HEIGHT).themed_parts(
210 &theme,
211 ColorScheme::Primary,
212 Variant::Solid,
213 ComponentSize::Md,
214 );
215
216 assert_eq!(
217 window.header_color,
218 theme.scheme(ColorScheme::Primary).solid
219 );
220 assert_eq!(
221 window.title_color,
222 theme.scheme(ColorScheme::Primary).contrast
223 );
224 assert_eq!(
225 window.button_color,
226 theme.scheme(ColorScheme::Primary).muted
227 );
228 }
229
230 fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
231 Rect {
232 x,
233 y,
234 width,
235 height,
236 }
237 }
238}