Skip to main content

native_theme/model/
resolved.rs

1// Resolved (non-optional) theme types produced after theme resolution.
2//
3// These types mirror their Option-based counterparts in defaults.rs, font.rs,
4// spacing.rs, icon_sizes.rs, and mod.rs (ThemeVariant), but with all fields
5// guaranteed populated. Produced by validate() after resolve().
6
7use super::font::ResolvedFontSpec;
8use crate::Rgba;
9
10// --- ResolvedThemeSpacing ---
11
12/// A fully resolved spacing scale where every tier is guaranteed populated.
13#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
14pub struct ResolvedThemeSpacing {
15    /// Extra-extra-small spacing in logical pixels.
16    pub xxs: f32,
17    /// Extra-small spacing in logical pixels.
18    pub xs: f32,
19    /// Small spacing in logical pixels.
20    pub s: f32,
21    /// Medium spacing in logical pixels.
22    pub m: f32,
23    /// Large spacing in logical pixels.
24    pub l: f32,
25    /// Extra-large spacing in logical pixels.
26    pub xl: f32,
27    /// Extra-extra-large spacing in logical pixels.
28    pub xxl: f32,
29}
30
31// --- ResolvedIconSizes ---
32
33/// Fully resolved per-context icon sizes where every context is guaranteed populated.
34#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
35pub struct ResolvedIconSizes {
36    /// Icon size for toolbar buttons.
37    pub toolbar: f32,
38    /// Small icon size for inline use.
39    pub small: f32,
40    /// Large icon size for menus/lists.
41    pub large: f32,
42    /// Icon size for dialog buttons.
43    pub dialog: f32,
44    /// Icon size for panel headers.
45    pub panel: f32,
46}
47
48// --- ResolvedTextScaleEntry ---
49
50/// A single resolved text scale entry with guaranteed size, weight, and line height.
51#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
52pub struct ResolvedTextScaleEntry {
53    /// Font size in logical pixels.
54    pub size: f32,
55    /// CSS font weight (100-900).
56    pub weight: u16,
57    /// Line height in logical pixels (computed as `defaults.line_height × size`).
58    pub line_height: f32,
59}
60
61// --- ResolvedTextScale ---
62
63/// A fully resolved text scale with all four typographic roles populated.
64#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
65pub struct ResolvedTextScale {
66    /// Caption / small label text.
67    pub caption: ResolvedTextScaleEntry,
68    /// Section heading text.
69    pub section_heading: ResolvedTextScaleEntry,
70    /// Dialog title text.
71    pub dialog_title: ResolvedTextScaleEntry,
72    /// Large display / hero text.
73    pub display: ResolvedTextScaleEntry,
74}
75
76// --- ResolvedThemeDefaults ---
77
78/// Fully resolved global theme defaults where every field is guaranteed populated.
79///
80/// Mirrors [`crate::model::ThemeDefaults`] but with concrete (non-Option) types.
81/// Produced by the resolution/validation pipeline.
82#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
83pub struct ResolvedThemeDefaults {
84    // ---- Base font ----
85    /// Primary UI font.
86    pub font: ResolvedFontSpec,
87    /// Line height multiplier.
88    pub line_height: f32,
89    /// Monospace font for code/terminal content.
90    pub mono_font: ResolvedFontSpec,
91
92    // ---- Base colors ----
93    /// Main window/surface background color.
94    pub background: Rgba,
95    /// Default text color.
96    pub foreground: Rgba,
97    /// Accent/brand color for interactive elements.
98    pub accent: Rgba,
99    /// Text color used on accent-colored backgrounds.
100    pub accent_foreground: Rgba,
101    /// Elevated surface color.
102    pub surface: Rgba,
103    /// Border/divider color.
104    pub border: Rgba,
105    /// Secondary/subdued text color.
106    pub muted: Rgba,
107    /// Drop shadow color.
108    pub shadow: Rgba,
109    /// Hyperlink text color.
110    pub link: Rgba,
111    /// Selection highlight background.
112    pub selection: Rgba,
113    /// Text color over selection highlight.
114    pub selection_foreground: Rgba,
115    /// Selection background when window is unfocused.
116    pub selection_inactive: Rgba,
117    /// Text color for disabled controls.
118    pub disabled_foreground: Rgba,
119
120    // ---- Status colors ----
121    /// Danger/error color.
122    pub danger: Rgba,
123    /// Text color on danger-colored backgrounds.
124    pub danger_foreground: Rgba,
125    /// Warning color.
126    pub warning: Rgba,
127    /// Text color on warning-colored backgrounds.
128    pub warning_foreground: Rgba,
129    /// Success/confirmation color.
130    pub success: Rgba,
131    /// Text color on success-colored backgrounds.
132    pub success_foreground: Rgba,
133    /// Informational color.
134    pub info: Rgba,
135    /// Text color on info-colored backgrounds.
136    pub info_foreground: Rgba,
137
138    // ---- Global geometry ----
139    /// Default corner radius in logical pixels.
140    pub radius: f32,
141    /// Large corner radius.
142    pub radius_lg: f32,
143    /// Border/frame width in logical pixels.
144    pub frame_width: f32,
145    /// Opacity for disabled controls.
146    pub disabled_opacity: f32,
147    /// Border alpha multiplier.
148    pub border_opacity: f32,
149    /// Whether drop shadows are enabled.
150    pub shadow_enabled: bool,
151
152    // ---- Focus ring ----
153    /// Focus indicator outline color.
154    pub focus_ring_color: Rgba,
155    /// Focus indicator outline width.
156    pub focus_ring_width: f32,
157    /// Gap between element edge and focus indicator.
158    pub focus_ring_offset: f32,
159
160    // ---- Spacing scale ----
161    /// Logical spacing scale.
162    pub spacing: ResolvedThemeSpacing,
163
164    // ---- Icon sizes ----
165    /// Per-context icon sizes.
166    pub icon_sizes: ResolvedIconSizes,
167
168    // ---- Accessibility ----
169    /// Text scaling factor (1.0 = no scaling).
170    pub text_scaling_factor: f32,
171    /// Whether the user has requested reduced motion.
172    pub reduce_motion: bool,
173    /// Whether a high-contrast mode is active.
174    pub high_contrast: bool,
175    /// Whether the user has requested reduced transparency.
176    pub reduce_transparency: bool,
177}
178
179// --- ResolvedThemeVariant ---
180
181/// A fully resolved theme where every field is guaranteed populated.
182///
183/// Produced by `validate()` after `resolve()`. Consumed by toolkit connectors.
184/// Mirrors [`crate::model::ThemeVariant`] but with concrete (non-Option) types
185/// for all 25 per-widget structs plus defaults and text scale.
186#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
187pub struct ResolvedThemeVariant {
188    /// Global defaults.
189    pub defaults: ResolvedThemeDefaults,
190    /// Per-role text scale.
191    pub text_scale: ResolvedTextScale,
192
193    // ---- Per-widget resolved structs ----
194    /// Window chrome.
195    pub window: super::widgets::ResolvedWindowTheme,
196    /// Push button.
197    pub button: super::widgets::ResolvedButtonTheme,
198    /// Text input.
199    pub input: super::widgets::ResolvedInputTheme,
200    /// Checkbox / radio button.
201    pub checkbox: super::widgets::ResolvedCheckboxTheme,
202    /// Popup / context menu.
203    pub menu: super::widgets::ResolvedMenuTheme,
204    /// Tooltip.
205    pub tooltip: super::widgets::ResolvedTooltipTheme,
206    /// Scrollbar.
207    pub scrollbar: super::widgets::ResolvedScrollbarTheme,
208    /// Slider.
209    pub slider: super::widgets::ResolvedSliderTheme,
210    /// Progress bar.
211    pub progress_bar: super::widgets::ResolvedProgressBarTheme,
212    /// Tab bar.
213    pub tab: super::widgets::ResolvedTabTheme,
214    /// Sidebar panel.
215    pub sidebar: super::widgets::ResolvedSidebarTheme,
216    /// Toolbar.
217    pub toolbar: super::widgets::ResolvedToolbarTheme,
218    /// Status bar.
219    pub status_bar: super::widgets::ResolvedStatusBarTheme,
220    /// List / table.
221    pub list: super::widgets::ResolvedListTheme,
222    /// Popover / dropdown.
223    pub popover: super::widgets::ResolvedPopoverTheme,
224    /// Splitter handle.
225    pub splitter: super::widgets::ResolvedSplitterTheme,
226    /// Separator line.
227    pub separator: super::widgets::ResolvedSeparatorTheme,
228    /// Toggle switch.
229    pub switch: super::widgets::ResolvedSwitchTheme,
230    /// Dialog.
231    pub dialog: super::widgets::ResolvedDialogTheme,
232    /// Spinner / progress ring.
233    pub spinner: super::widgets::ResolvedSpinnerTheme,
234    /// ComboBox / dropdown trigger.
235    pub combo_box: super::widgets::ResolvedComboBoxTheme,
236    /// Segmented control.
237    pub segmented_control: super::widgets::ResolvedSegmentedControlTheme,
238    /// Card / container.
239    pub card: super::widgets::ResolvedCardTheme,
240    /// Expander / disclosure.
241    pub expander: super::widgets::ResolvedExpanderTheme,
242    /// Hyperlink.
243    pub link: super::widgets::ResolvedLinkTheme,
244
245    /// Icon set / naming convention.
246    pub icon_set: crate::IconSet,
247
248    /// Icon theme name (e.g., "breeze", "Adwaita", "material").
249    pub icon_theme: String,
250}
251
252#[cfg(test)]
253#[allow(
254    clippy::unwrap_used,
255    clippy::expect_used,
256    clippy::bool_assert_comparison
257)]
258mod tests {
259    use super::*;
260    use crate::Rgba;
261    use crate::model::DialogButtonOrder;
262    use crate::model::ResolvedFontSpec;
263    use crate::model::widgets::{
264        ResolvedButtonTheme, ResolvedCardTheme, ResolvedCheckboxTheme, ResolvedComboBoxTheme,
265        ResolvedDialogTheme, ResolvedExpanderTheme, ResolvedInputTheme, ResolvedLinkTheme,
266        ResolvedListTheme, ResolvedMenuTheme, ResolvedPopoverTheme, ResolvedProgressBarTheme,
267        ResolvedScrollbarTheme, ResolvedSegmentedControlTheme, ResolvedSeparatorTheme,
268        ResolvedSidebarTheme, ResolvedSliderTheme, ResolvedSpinnerTheme, ResolvedSplitterTheme,
269        ResolvedStatusBarTheme, ResolvedSwitchTheme, ResolvedTabTheme, ResolvedToolbarTheme,
270        ResolvedTooltipTheme, ResolvedWindowTheme,
271    };
272
273    fn sample_font() -> ResolvedFontSpec {
274        ResolvedFontSpec {
275            family: "Inter".into(),
276            size: 14.0,
277            weight: 400,
278        }
279    }
280
281    fn sample_spacing() -> ResolvedThemeSpacing {
282        ResolvedThemeSpacing {
283            xxs: 2.0,
284            xs: 4.0,
285            s: 6.0,
286            m: 12.0,
287            l: 18.0,
288            xl: 24.0,
289            xxl: 36.0,
290        }
291    }
292
293    fn sample_icon_sizes() -> ResolvedIconSizes {
294        ResolvedIconSizes {
295            toolbar: 24.0,
296            small: 16.0,
297            large: 32.0,
298            dialog: 22.0,
299            panel: 20.0,
300        }
301    }
302
303    fn sample_text_scale_entry() -> ResolvedTextScaleEntry {
304        ResolvedTextScaleEntry {
305            size: 12.0,
306            weight: 400,
307            line_height: 1.4,
308        }
309    }
310
311    fn sample_defaults() -> ResolvedThemeDefaults {
312        let c = Rgba::rgb(128, 128, 128);
313        ResolvedThemeDefaults {
314            font: sample_font(),
315            line_height: 1.4,
316            mono_font: ResolvedFontSpec {
317                family: "JetBrains Mono".into(),
318                size: 12.0,
319                weight: 400,
320            },
321            background: c,
322            foreground: c,
323            accent: c,
324            accent_foreground: c,
325            surface: c,
326            border: c,
327            muted: c,
328            shadow: c,
329            link: c,
330            selection: c,
331            selection_foreground: c,
332            selection_inactive: c,
333            disabled_foreground: c,
334            danger: c,
335            danger_foreground: c,
336            warning: c,
337            warning_foreground: c,
338            success: c,
339            success_foreground: c,
340            info: c,
341            info_foreground: c,
342            radius: 4.0,
343            radius_lg: 8.0,
344            frame_width: 1.0,
345            disabled_opacity: 0.5,
346            border_opacity: 0.15,
347            shadow_enabled: true,
348            focus_ring_color: c,
349            focus_ring_width: 2.0,
350            focus_ring_offset: 1.0,
351            spacing: sample_spacing(),
352            icon_sizes: sample_icon_sizes(),
353            text_scaling_factor: 1.0,
354            reduce_motion: false,
355            high_contrast: false,
356            reduce_transparency: false,
357        }
358    }
359
360    // --- ResolvedThemeSpacing tests ---
361
362    #[test]
363    fn resolved_spacing_has_7_concrete_fields() {
364        let s = sample_spacing();
365        assert_eq!(s.xxs, 2.0);
366        assert_eq!(s.xs, 4.0);
367        assert_eq!(s.s, 6.0);
368        assert_eq!(s.m, 12.0);
369        assert_eq!(s.l, 18.0);
370        assert_eq!(s.xl, 24.0);
371        assert_eq!(s.xxl, 36.0);
372    }
373
374    #[test]
375    fn resolved_spacing_derives_clone_debug_partialeq() {
376        let s = sample_spacing();
377        let s2 = s.clone();
378        assert_eq!(s, s2);
379        let dbg = format!("{s:?}");
380        assert!(dbg.contains("ResolvedThemeSpacing"));
381    }
382
383    // --- ResolvedIconSizes tests ---
384
385    #[test]
386    fn resolved_icon_sizes_has_5_concrete_fields() {
387        let i = sample_icon_sizes();
388        assert_eq!(i.toolbar, 24.0);
389        assert_eq!(i.small, 16.0);
390        assert_eq!(i.large, 32.0);
391        assert_eq!(i.dialog, 22.0);
392        assert_eq!(i.panel, 20.0);
393    }
394
395    #[test]
396    fn resolved_icon_sizes_derives_clone_debug_partialeq() {
397        let i = sample_icon_sizes();
398        let i2 = i.clone();
399        assert_eq!(i, i2);
400        let dbg = format!("{i:?}");
401        assert!(dbg.contains("ResolvedIconSizes"));
402    }
403
404    // --- ResolvedTextScaleEntry tests ---
405
406    #[test]
407    fn resolved_text_scale_entry_has_3_concrete_fields() {
408        let e = sample_text_scale_entry();
409        assert_eq!(e.size, 12.0);
410        assert_eq!(e.weight, 400);
411        assert_eq!(e.line_height, 1.4);
412    }
413
414    #[test]
415    fn resolved_text_scale_entry_derives_clone_debug_partialeq() {
416        let e = sample_text_scale_entry();
417        let e2 = e.clone();
418        assert_eq!(e, e2);
419        let dbg = format!("{e:?}");
420        assert!(dbg.contains("ResolvedTextScaleEntry"));
421    }
422
423    // --- ResolvedTextScale tests ---
424
425    #[test]
426    fn resolved_text_scale_has_4_entries() {
427        let ts = ResolvedTextScale {
428            caption: ResolvedTextScaleEntry {
429                size: 11.0,
430                weight: 400,
431                line_height: 1.3,
432            },
433            section_heading: ResolvedTextScaleEntry {
434                size: 14.0,
435                weight: 600,
436                line_height: 1.4,
437            },
438            dialog_title: ResolvedTextScaleEntry {
439                size: 16.0,
440                weight: 700,
441                line_height: 1.2,
442            },
443            display: ResolvedTextScaleEntry {
444                size: 24.0,
445                weight: 300,
446                line_height: 1.1,
447            },
448        };
449        assert_eq!(ts.caption.size, 11.0);
450        assert_eq!(ts.section_heading.weight, 600);
451        assert_eq!(ts.dialog_title.size, 16.0);
452        assert_eq!(ts.display.weight, 300);
453    }
454
455    #[test]
456    fn resolved_text_scale_derives_clone_debug_partialeq() {
457        let e = sample_text_scale_entry();
458        let ts = ResolvedTextScale {
459            caption: e.clone(),
460            section_heading: e.clone(),
461            dialog_title: e.clone(),
462            display: e,
463        };
464        let ts2 = ts.clone();
465        assert_eq!(ts, ts2);
466        let dbg = format!("{ts:?}");
467        assert!(dbg.contains("ResolvedTextScale"));
468    }
469
470    // --- ResolvedThemeDefaults tests ---
471
472    #[test]
473    fn resolved_defaults_all_fields_concrete() {
474        let d = sample_defaults();
475        // Fonts
476        assert_eq!(d.font.family, "Inter");
477        assert_eq!(d.mono_font.family, "JetBrains Mono");
478        assert_eq!(d.line_height, 1.4);
479        // Some colors
480        assert_eq!(d.background, Rgba::rgb(128, 128, 128));
481        assert_eq!(d.accent, Rgba::rgb(128, 128, 128));
482        // Geometry
483        assert_eq!(d.radius, 4.0);
484        assert_eq!(d.shadow_enabled, true);
485        // Focus ring
486        assert_eq!(d.focus_ring_width, 2.0);
487        // Spacing and icon sizes
488        assert_eq!(d.spacing.m, 12.0);
489        assert_eq!(d.icon_sizes.toolbar, 24.0);
490        // Accessibility
491        assert_eq!(d.text_scaling_factor, 1.0);
492        assert_eq!(d.reduce_motion, false);
493    }
494
495    #[test]
496    fn resolved_defaults_derives_clone_debug_partialeq() {
497        let d = sample_defaults();
498        let d2 = d.clone();
499        assert_eq!(d, d2);
500        let dbg = format!("{d:?}");
501        assert!(dbg.contains("ResolvedThemeDefaults"));
502    }
503
504    // --- ResolvedThemeVariant tests ---
505
506    #[test]
507    fn resolved_theme_construction_with_all_widgets() {
508        let c = Rgba::rgb(100, 100, 100);
509        let f = sample_font();
510        let e = sample_text_scale_entry();
511
512        let theme = ResolvedThemeVariant {
513            defaults: sample_defaults(),
514            text_scale: ResolvedTextScale {
515                caption: e.clone(),
516                section_heading: e.clone(),
517                dialog_title: e.clone(),
518                display: e,
519            },
520            window: ResolvedWindowTheme {
521                background: c,
522                foreground: c,
523                border: c,
524                title_bar_background: c,
525                title_bar_foreground: c,
526                inactive_title_bar_background: c,
527                inactive_title_bar_foreground: c,
528                radius: 4.0,
529                shadow: true,
530                title_bar_font: f.clone(),
531            },
532            button: ResolvedButtonTheme {
533                background: c,
534                foreground: c,
535                border: c,
536                primary_bg: c,
537                primary_fg: c,
538                min_width: 64.0,
539                min_height: 28.0,
540                padding_horizontal: 12.0,
541                padding_vertical: 6.0,
542                radius: 4.0,
543                icon_spacing: 6.0,
544                disabled_opacity: 0.5,
545                shadow: false,
546                font: f.clone(),
547            },
548            input: ResolvedInputTheme {
549                background: c,
550                foreground: c,
551                border: c,
552                placeholder: c,
553                caret: c,
554                selection: c,
555                selection_foreground: c,
556                min_height: 28.0,
557                padding_horizontal: 8.0,
558                padding_vertical: 4.0,
559                radius: 4.0,
560                border_width: 1.0,
561                font: f.clone(),
562            },
563            checkbox: ResolvedCheckboxTheme {
564                checked_bg: c,
565                indicator_size: 18.0,
566                spacing: 6.0,
567                radius: 2.0,
568                border_width: 1.0,
569            },
570            menu: ResolvedMenuTheme {
571                background: c,
572                foreground: c,
573                separator: c,
574                item_height: 28.0,
575                padding_horizontal: 8.0,
576                padding_vertical: 4.0,
577                icon_spacing: 6.0,
578                font: f.clone(),
579            },
580            tooltip: ResolvedTooltipTheme {
581                background: c,
582                foreground: c,
583                padding_horizontal: 6.0,
584                padding_vertical: 4.0,
585                max_width: 300.0,
586                radius: 4.0,
587                font: f.clone(),
588            },
589            scrollbar: ResolvedScrollbarTheme {
590                track: c,
591                thumb: c,
592                thumb_hover: c,
593                width: 14.0,
594                min_thumb_height: 20.0,
595                slider_width: 8.0,
596                overlay_mode: false,
597            },
598            slider: ResolvedSliderTheme {
599                fill: c,
600                track: c,
601                thumb: c,
602                track_height: 4.0,
603                thumb_size: 16.0,
604                tick_length: 6.0,
605            },
606            progress_bar: ResolvedProgressBarTheme {
607                fill: c,
608                track: c,
609                height: 6.0,
610                min_width: 100.0,
611                radius: 3.0,
612            },
613            tab: ResolvedTabTheme {
614                background: c,
615                foreground: c,
616                active_background: c,
617                active_foreground: c,
618                bar_background: c,
619                min_width: 60.0,
620                min_height: 32.0,
621                padding_horizontal: 12.0,
622                padding_vertical: 6.0,
623            },
624            sidebar: ResolvedSidebarTheme {
625                background: c,
626                foreground: c,
627            },
628            toolbar: ResolvedToolbarTheme {
629                height: 40.0,
630                item_spacing: 4.0,
631                padding: 4.0,
632                font: f.clone(),
633            },
634            status_bar: ResolvedStatusBarTheme { font: f.clone() },
635            list: ResolvedListTheme {
636                background: c,
637                foreground: c,
638                alternate_row: c,
639                selection: c,
640                selection_foreground: c,
641                header_background: c,
642                header_foreground: c,
643                grid_color: c,
644                item_height: 28.0,
645                padding_horizontal: 8.0,
646                padding_vertical: 4.0,
647            },
648            popover: ResolvedPopoverTheme {
649                background: c,
650                foreground: c,
651                border: c,
652                radius: 6.0,
653            },
654            splitter: ResolvedSplitterTheme { width: 4.0 },
655            separator: ResolvedSeparatorTheme { color: c },
656            switch: ResolvedSwitchTheme {
657                checked_bg: c,
658                unchecked_bg: c,
659                thumb_bg: c,
660                track_width: 40.0,
661                track_height: 20.0,
662                thumb_size: 14.0,
663                track_radius: 10.0,
664            },
665            dialog: ResolvedDialogTheme {
666                min_width: 320.0,
667                max_width: 600.0,
668                min_height: 200.0,
669                max_height: 800.0,
670                content_padding: 16.0,
671                button_spacing: 8.0,
672                radius: 8.0,
673                icon_size: 22.0,
674                button_order: DialogButtonOrder::TrailingAffirmative,
675                title_font: f.clone(),
676            },
677            spinner: ResolvedSpinnerTheme {
678                fill: c,
679                diameter: 24.0,
680                min_size: 16.0,
681                stroke_width: 2.0,
682            },
683            combo_box: ResolvedComboBoxTheme {
684                min_height: 28.0,
685                min_width: 80.0,
686                padding_horizontal: 8.0,
687                arrow_size: 12.0,
688                arrow_area_width: 20.0,
689                radius: 4.0,
690            },
691            segmented_control: ResolvedSegmentedControlTheme {
692                segment_height: 28.0,
693                separator_width: 1.0,
694                padding_horizontal: 12.0,
695                radius: 4.0,
696            },
697            card: ResolvedCardTheme {
698                background: c,
699                border: c,
700                radius: 8.0,
701                padding: 12.0,
702                shadow: true,
703            },
704            expander: ResolvedExpanderTheme {
705                header_height: 32.0,
706                arrow_size: 12.0,
707                content_padding: 8.0,
708                radius: 4.0,
709            },
710            link: ResolvedLinkTheme {
711                color: c,
712                visited: c,
713                background: c,
714                hover_bg: c,
715                underline: true,
716            },
717            icon_set: crate::IconSet::Freedesktop,
718            icon_theme: "breeze".into(),
719        };
720
721        // Verify key fields
722        assert_eq!(theme.defaults.font.family, "Inter");
723        assert_eq!(theme.window.radius, 4.0);
724        assert_eq!(theme.button.min_height, 28.0);
725        assert_eq!(theme.icon_set, crate::IconSet::Freedesktop);
726        assert_eq!(theme.icon_theme, "breeze");
727        assert_eq!(theme.text_scale.caption.size, 12.0);
728    }
729
730    #[test]
731    fn resolved_theme_derives_clone_debug_partialeq() {
732        let c = Rgba::rgb(100, 100, 100);
733        let f = sample_font();
734        let e = sample_text_scale_entry();
735
736        let theme = ResolvedThemeVariant {
737            defaults: sample_defaults(),
738            text_scale: ResolvedTextScale {
739                caption: e.clone(),
740                section_heading: e.clone(),
741                dialog_title: e.clone(),
742                display: e,
743            },
744            window: ResolvedWindowTheme {
745                background: c,
746                foreground: c,
747                border: c,
748                title_bar_background: c,
749                title_bar_foreground: c,
750                inactive_title_bar_background: c,
751                inactive_title_bar_foreground: c,
752                radius: 4.0,
753                shadow: true,
754                title_bar_font: f.clone(),
755            },
756            button: ResolvedButtonTheme {
757                background: c,
758                foreground: c,
759                border: c,
760                primary_bg: c,
761                primary_fg: c,
762                min_width: 64.0,
763                min_height: 28.0,
764                padding_horizontal: 12.0,
765                padding_vertical: 6.0,
766                radius: 4.0,
767                icon_spacing: 6.0,
768                disabled_opacity: 0.5,
769                shadow: false,
770                font: f.clone(),
771            },
772            input: ResolvedInputTheme {
773                background: c,
774                foreground: c,
775                border: c,
776                placeholder: c,
777                caret: c,
778                selection: c,
779                selection_foreground: c,
780                min_height: 28.0,
781                padding_horizontal: 8.0,
782                padding_vertical: 4.0,
783                radius: 4.0,
784                border_width: 1.0,
785                font: f.clone(),
786            },
787            checkbox: ResolvedCheckboxTheme {
788                checked_bg: c,
789                indicator_size: 18.0,
790                spacing: 6.0,
791                radius: 2.0,
792                border_width: 1.0,
793            },
794            menu: ResolvedMenuTheme {
795                background: c,
796                foreground: c,
797                separator: c,
798                item_height: 28.0,
799                padding_horizontal: 8.0,
800                padding_vertical: 4.0,
801                icon_spacing: 6.0,
802                font: f.clone(),
803            },
804            tooltip: ResolvedTooltipTheme {
805                background: c,
806                foreground: c,
807                padding_horizontal: 6.0,
808                padding_vertical: 4.0,
809                max_width: 300.0,
810                radius: 4.0,
811                font: f.clone(),
812            },
813            scrollbar: ResolvedScrollbarTheme {
814                track: c,
815                thumb: c,
816                thumb_hover: c,
817                width: 14.0,
818                min_thumb_height: 20.0,
819                slider_width: 8.0,
820                overlay_mode: false,
821            },
822            slider: ResolvedSliderTheme {
823                fill: c,
824                track: c,
825                thumb: c,
826                track_height: 4.0,
827                thumb_size: 16.0,
828                tick_length: 6.0,
829            },
830            progress_bar: ResolvedProgressBarTheme {
831                fill: c,
832                track: c,
833                height: 6.0,
834                min_width: 100.0,
835                radius: 3.0,
836            },
837            tab: ResolvedTabTheme {
838                background: c,
839                foreground: c,
840                active_background: c,
841                active_foreground: c,
842                bar_background: c,
843                min_width: 60.0,
844                min_height: 32.0,
845                padding_horizontal: 12.0,
846                padding_vertical: 6.0,
847            },
848            sidebar: ResolvedSidebarTheme {
849                background: c,
850                foreground: c,
851            },
852            toolbar: ResolvedToolbarTheme {
853                height: 40.0,
854                item_spacing: 4.0,
855                padding: 4.0,
856                font: f.clone(),
857            },
858            status_bar: ResolvedStatusBarTheme { font: f.clone() },
859            list: ResolvedListTheme {
860                background: c,
861                foreground: c,
862                alternate_row: c,
863                selection: c,
864                selection_foreground: c,
865                header_background: c,
866                header_foreground: c,
867                grid_color: c,
868                item_height: 28.0,
869                padding_horizontal: 8.0,
870                padding_vertical: 4.0,
871            },
872            popover: ResolvedPopoverTheme {
873                background: c,
874                foreground: c,
875                border: c,
876                radius: 6.0,
877            },
878            splitter: ResolvedSplitterTheme { width: 4.0 },
879            separator: ResolvedSeparatorTheme { color: c },
880            switch: ResolvedSwitchTheme {
881                checked_bg: c,
882                unchecked_bg: c,
883                thumb_bg: c,
884                track_width: 40.0,
885                track_height: 20.0,
886                thumb_size: 14.0,
887                track_radius: 10.0,
888            },
889            dialog: ResolvedDialogTheme {
890                min_width: 320.0,
891                max_width: 600.0,
892                min_height: 200.0,
893                max_height: 800.0,
894                content_padding: 16.0,
895                button_spacing: 8.0,
896                radius: 8.0,
897                icon_size: 22.0,
898                button_order: DialogButtonOrder::TrailingAffirmative,
899                title_font: f.clone(),
900            },
901            spinner: ResolvedSpinnerTheme {
902                fill: c,
903                diameter: 24.0,
904                min_size: 16.0,
905                stroke_width: 2.0,
906            },
907            combo_box: ResolvedComboBoxTheme {
908                min_height: 28.0,
909                min_width: 80.0,
910                padding_horizontal: 8.0,
911                arrow_size: 12.0,
912                arrow_area_width: 20.0,
913                radius: 4.0,
914            },
915            segmented_control: ResolvedSegmentedControlTheme {
916                segment_height: 28.0,
917                separator_width: 1.0,
918                padding_horizontal: 12.0,
919                radius: 4.0,
920            },
921            card: ResolvedCardTheme {
922                background: c,
923                border: c,
924                radius: 8.0,
925                padding: 12.0,
926                shadow: true,
927            },
928            expander: ResolvedExpanderTheme {
929                header_height: 32.0,
930                arrow_size: 12.0,
931                content_padding: 8.0,
932                radius: 4.0,
933            },
934            link: ResolvedLinkTheme {
935                color: c,
936                visited: c,
937                background: c,
938                hover_bg: c,
939                underline: true,
940            },
941            icon_set: crate::IconSet::Freedesktop,
942            icon_theme: "breeze".into(),
943        };
944
945        let theme2 = theme.clone();
946        assert_eq!(theme, theme2);
947        let dbg = format!("{theme:?}");
948        assert!(dbg.contains("ResolvedThemeVariant"));
949    }
950}