1use super::border::ResolvedBorderSpec;
8use super::font::ResolvedFontSpec;
9use crate::Rgba;
10
11#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
15pub struct ResolvedIconSizes {
16 pub toolbar: f32,
18 pub small: f32,
20 pub large: f32,
22 pub dialog: f32,
24 pub panel: f32,
26}
27
28#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
32pub struct ResolvedTextScaleEntry {
33 pub size: f32,
36 pub weight: u16,
38 pub line_height: f32,
42}
43
44#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
48pub struct ResolvedTextScale {
49 pub caption: ResolvedTextScaleEntry,
51 pub section_heading: ResolvedTextScaleEntry,
53 pub dialog_title: ResolvedTextScaleEntry,
55 pub display: ResolvedTextScaleEntry,
57}
58
59#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
66pub struct ResolvedThemeDefaults {
67 pub font: ResolvedFontSpec,
70 pub line_height: f32,
72 pub mono_font: ResolvedFontSpec,
74
75 pub background_color: Rgba,
78 pub text_color: Rgba,
80 pub accent_color: Rgba,
82 pub accent_text_color: Rgba,
84 pub surface_color: Rgba,
86 pub muted_color: Rgba,
88 pub shadow_color: Rgba,
90 pub link_color: Rgba,
92 pub selection_background: Rgba,
94 pub selection_text_color: Rgba,
96 pub selection_inactive_background: Rgba,
98 pub text_selection_background: Rgba,
100 pub text_selection_color: Rgba,
102 pub disabled_text_color: Rgba,
104
105 pub danger_color: Rgba,
108 pub danger_text_color: Rgba,
110 pub warning_color: Rgba,
112 pub warning_text_color: Rgba,
114 pub success_color: Rgba,
116 pub success_text_color: Rgba,
118 pub info_color: Rgba,
120 pub info_text_color: Rgba,
122
123 pub border: ResolvedBorderSpec,
126 pub disabled_opacity: f32,
128
129 pub focus_ring_color: Rgba,
132 pub focus_ring_width: f32,
134 pub focus_ring_offset: f32,
136
137 pub icon_sizes: ResolvedIconSizes,
140
141 pub font_dpi: f32,
145
146 pub text_scaling_factor: f32,
153 pub reduce_motion: bool,
155 pub high_contrast: bool,
157 pub reduce_transparency: bool,
159}
160
161#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
169pub struct ResolvedThemeVariant {
170 pub defaults: ResolvedThemeDefaults,
172 pub text_scale: ResolvedTextScale,
174
175 pub window: super::widgets::ResolvedWindowTheme,
178 pub button: super::widgets::ResolvedButtonTheme,
180 pub input: super::widgets::ResolvedInputTheme,
182 pub checkbox: super::widgets::ResolvedCheckboxTheme,
184 pub menu: super::widgets::ResolvedMenuTheme,
186 pub tooltip: super::widgets::ResolvedTooltipTheme,
188 pub scrollbar: super::widgets::ResolvedScrollbarTheme,
190 pub slider: super::widgets::ResolvedSliderTheme,
192 pub progress_bar: super::widgets::ResolvedProgressBarTheme,
194 pub tab: super::widgets::ResolvedTabTheme,
196 pub sidebar: super::widgets::ResolvedSidebarTheme,
198 pub toolbar: super::widgets::ResolvedToolbarTheme,
200 pub status_bar: super::widgets::ResolvedStatusBarTheme,
202 pub list: super::widgets::ResolvedListTheme,
204 pub popover: super::widgets::ResolvedPopoverTheme,
206 pub splitter: super::widgets::ResolvedSplitterTheme,
208 pub separator: super::widgets::ResolvedSeparatorTheme,
210 pub switch: super::widgets::ResolvedSwitchTheme,
212 pub dialog: super::widgets::ResolvedDialogTheme,
214 pub spinner: super::widgets::ResolvedSpinnerTheme,
216 pub combo_box: super::widgets::ResolvedComboBoxTheme,
218 pub segmented_control: super::widgets::ResolvedSegmentedControlTheme,
220 pub card: super::widgets::ResolvedCardTheme,
222 pub expander: super::widgets::ResolvedExpanderTheme,
224 pub link: super::widgets::ResolvedLinkTheme,
226
227 pub icon_set: crate::IconSet,
230
231 pub icon_theme: String,
234}
235
236#[cfg(test)]
237#[allow(
238 clippy::unwrap_used,
239 clippy::expect_used,
240 clippy::bool_assert_comparison
241)]
242mod tests {
243 use super::*;
244 use crate::Rgba;
245 use crate::model::ResolvedFontSpec;
246 use crate::model::border::ResolvedBorderSpec;
247 use crate::model::font::FontStyle;
248
249 fn sample_font() -> ResolvedFontSpec {
250 ResolvedFontSpec {
251 family: "Inter".into(),
252 size: 14.0,
253 weight: 400,
254 style: FontStyle::Normal,
255 color: Rgba::rgb(128, 128, 128),
256 }
257 }
258
259 fn sample_border() -> ResolvedBorderSpec {
260 ResolvedBorderSpec {
261 color: Rgba::rgb(200, 200, 200),
262 corner_radius: 4.0,
263 corner_radius_lg: 8.0,
264 line_width: 1.0,
265 opacity: 0.15,
266 shadow_enabled: true,
267 padding_horizontal: 0.0,
268 padding_vertical: 0.0,
269 }
270 }
271
272 fn sample_icon_sizes() -> ResolvedIconSizes {
273 ResolvedIconSizes {
274 toolbar: 24.0,
275 small: 16.0,
276 large: 32.0,
277 dialog: 22.0,
278 panel: 20.0,
279 }
280 }
281
282 fn sample_text_scale_entry() -> ResolvedTextScaleEntry {
283 ResolvedTextScaleEntry {
284 size: 12.0,
285 weight: 400,
286 line_height: 1.4,
287 }
288 }
289
290 fn sample_defaults() -> ResolvedThemeDefaults {
291 let c = Rgba::rgb(128, 128, 128);
292 ResolvedThemeDefaults {
293 font: sample_font(),
294 line_height: 1.4,
295 mono_font: ResolvedFontSpec {
296 family: "JetBrains Mono".into(),
297 size: 12.0,
298 weight: 400,
299 style: FontStyle::Normal,
300 color: Rgba::rgb(128, 128, 128),
301 },
302 background_color: c,
303 text_color: c,
304 accent_color: c,
305 accent_text_color: c,
306 surface_color: c,
307 muted_color: c,
308 shadow_color: c,
309 link_color: c,
310 selection_background: c,
311 selection_text_color: c,
312 selection_inactive_background: c,
313 text_selection_background: c,
314 text_selection_color: c,
315 disabled_text_color: c,
316 danger_color: c,
317 danger_text_color: c,
318 warning_color: c,
319 warning_text_color: c,
320 success_color: c,
321 success_text_color: c,
322 info_color: c,
323 info_text_color: c,
324 border: sample_border(),
325 disabled_opacity: 0.5,
326 focus_ring_color: c,
327 focus_ring_width: 2.0,
328 focus_ring_offset: 1.0,
329 icon_sizes: sample_icon_sizes(),
330 font_dpi: 96.0,
331 text_scaling_factor: 1.0,
332 reduce_motion: false,
333 high_contrast: false,
334 reduce_transparency: false,
335 }
336 }
337
338 #[test]
341 fn resolved_icon_sizes_has_5_concrete_fields() {
342 let i = sample_icon_sizes();
343 assert_eq!(i.toolbar, 24.0);
344 assert_eq!(i.small, 16.0);
345 assert_eq!(i.large, 32.0);
346 assert_eq!(i.dialog, 22.0);
347 assert_eq!(i.panel, 20.0);
348 }
349
350 #[test]
351 fn resolved_icon_sizes_derives_clone_debug_partialeq() {
352 let i = sample_icon_sizes();
353 let i2 = i.clone();
354 assert_eq!(i, i2);
355 let dbg = format!("{i:?}");
356 assert!(dbg.contains("ResolvedIconSizes"));
357 }
358
359 #[test]
362 fn resolved_text_scale_entry_has_3_concrete_fields() {
363 let e = sample_text_scale_entry();
364 assert_eq!(e.size, 12.0);
365 assert_eq!(e.weight, 400);
366 assert_eq!(e.line_height, 1.4);
367 }
368
369 #[test]
370 fn resolved_text_scale_entry_derives_clone_debug_partialeq() {
371 let e = sample_text_scale_entry();
372 let e2 = e.clone();
373 assert_eq!(e, e2);
374 let dbg = format!("{e:?}");
375 assert!(dbg.contains("ResolvedTextScaleEntry"));
376 }
377
378 #[test]
381 fn resolved_text_scale_has_4_entries() {
382 let ts = ResolvedTextScale {
383 caption: ResolvedTextScaleEntry {
384 size: 11.0,
385 weight: 400,
386 line_height: 1.3,
387 },
388 section_heading: ResolvedTextScaleEntry {
389 size: 14.0,
390 weight: 600,
391 line_height: 1.4,
392 },
393 dialog_title: ResolvedTextScaleEntry {
394 size: 16.0,
395 weight: 700,
396 line_height: 1.2,
397 },
398 display: ResolvedTextScaleEntry {
399 size: 24.0,
400 weight: 300,
401 line_height: 1.1,
402 },
403 };
404 assert_eq!(ts.caption.size, 11.0);
405 assert_eq!(ts.section_heading.weight, 600);
406 assert_eq!(ts.dialog_title.size, 16.0);
407 assert_eq!(ts.display.weight, 300);
408 }
409
410 #[test]
411 fn resolved_text_scale_derives_clone_debug_partialeq() {
412 let e = sample_text_scale_entry();
413 let ts = ResolvedTextScale {
414 caption: e.clone(),
415 section_heading: e.clone(),
416 dialog_title: e.clone(),
417 display: e,
418 };
419 let ts2 = ts.clone();
420 assert_eq!(ts, ts2);
421 let dbg = format!("{ts:?}");
422 assert!(dbg.contains("ResolvedTextScale"));
423 }
424
425 #[test]
428 fn resolved_defaults_all_fields_concrete() {
429 let d = sample_defaults();
430 assert_eq!(d.font.family, "Inter");
432 assert_eq!(d.mono_font.family, "JetBrains Mono");
433 assert_eq!(d.line_height, 1.4);
434 assert_eq!(d.background_color, Rgba::rgb(128, 128, 128));
436 assert_eq!(d.accent_color, Rgba::rgb(128, 128, 128));
437 assert_eq!(d.border.corner_radius, 4.0);
439 assert_eq!(d.border.shadow_enabled, true);
440 assert_eq!(d.focus_ring_width, 2.0);
442 assert_eq!(d.icon_sizes.toolbar, 24.0);
444 assert_eq!(d.text_scaling_factor, 1.0);
446 assert_eq!(d.reduce_motion, false);
447 }
448
449 #[test]
450 fn resolved_defaults_derives_clone_debug_partialeq() {
451 let d = sample_defaults();
452 let d2 = d.clone();
453 assert_eq!(d, d2);
454 let dbg = format!("{d:?}");
455 assert!(dbg.contains("ResolvedThemeDefaults"));
456 }
457
458 }