Skip to main content

native_theme/resolve/
validate.rs

1// Theme validation: require fields, range-check values, produce ResolvedThemeVariant.
2
3use crate::error::ThemeResolutionError;
4use crate::model::border::{BorderSpec, ResolvedBorderSpec};
5
6/// Standard screen DPI (96 dots per inch). Used as the font_dpi fallback
7/// when no DPI was set on the unresolved variant (e.g. community presets
8/// loaded standalone without OS reader). This matches the CSS/Web reference
9/// pixel and the Windows default. Validation uses this to convert
10/// `FontSize::Pt` values to logical pixels via `FontSize::to_px(dpi)`.
11const DEFAULT_FONT_DPI: f32 = 96.0;
12use crate::model::resolved::{
13    ResolvedIconSizes, ResolvedTextScale, ResolvedTextScaleEntry, ResolvedThemeDefaults,
14    ResolvedThemeVariant,
15};
16use crate::model::{FontSpec, ResolvedFontSpec, TextScaleEntry, ThemeVariant};
17
18// --- validate() helpers ---
19
20/// Extract a required field, recording the path if missing.
21///
22/// Returns the value if present, or `T::default()` as a placeholder if missing.
23/// The placeholder is never used: `validate()` returns `Err` before constructing
24/// `ResolvedThemeVariant` when any field was recorded as missing.
25fn require<T: Clone + Default>(field: &Option<T>, path: &str, missing: &mut Vec<String>) -> T {
26    match field {
27        Some(val) => val.clone(),
28        None => {
29            missing.push(path.to_string());
30            T::default()
31        }
32    }
33}
34
35/// Validate a FontSpec that is stored directly (not wrapped in Option).
36/// Checks each sub-field individually. Converts `FontSize` to `f32` px via `to_px(dpi)`.
37fn require_font(
38    font: &FontSpec,
39    prefix: &str,
40    dpi: f32,
41    missing: &mut Vec<String>,
42) -> ResolvedFontSpec {
43    let family = require(&font.family, &format!("{prefix}.family"), missing);
44    let size = font.size.map(|fs| fs.to_px(dpi)).unwrap_or_else(|| {
45        missing.push(format!("{prefix}.size"));
46        0.0
47    });
48    let weight = require(&font.weight, &format!("{prefix}.weight"), missing);
49    let color = require(&font.color, &format!("{prefix}.color"), missing);
50    ResolvedFontSpec {
51        family,
52        size,
53        weight,
54        style: font.style.unwrap_or_default(),
55        color,
56    }
57}
58
59/// Validate an `Option<FontSpec>` (widget font fields).
60/// If None, records the path as missing. Converts `FontSize` to `f32` px via `to_px(dpi)`.
61fn require_font_opt(
62    font: &Option<FontSpec>,
63    prefix: &str,
64    dpi: f32,
65    missing: &mut Vec<String>,
66) -> ResolvedFontSpec {
67    match font {
68        None => {
69            missing.push(prefix.to_string());
70            ResolvedFontSpec::default()
71        }
72        Some(f) => {
73            let family = require(&f.family, &format!("{prefix}.family"), missing);
74            let size = f.size.map(|fs| fs.to_px(dpi)).unwrap_or_else(|| {
75                missing.push(format!("{prefix}.size"));
76                0.0
77            });
78            let weight = require(&f.weight, &format!("{prefix}.weight"), missing);
79            let color = require(&f.color, &format!("{prefix}.color"), missing);
80            ResolvedFontSpec {
81                family,
82                size,
83                weight,
84                style: f.style.unwrap_or_default(),
85                color,
86            }
87        }
88    }
89}
90
91/// Validate an `Option<TextScaleEntry>`.
92/// Converts `FontSize` to `f32` px via `to_px(dpi)`. Also converts `line_height`
93/// when the sibling `size` is in points (same unit, same DPI factor).
94fn require_text_scale_entry(
95    entry: &Option<TextScaleEntry>,
96    prefix: &str,
97    dpi: f32,
98    missing: &mut Vec<String>,
99) -> ResolvedTextScaleEntry {
100    match entry {
101        None => {
102            missing.push(prefix.to_string());
103            ResolvedTextScaleEntry::default()
104        }
105        Some(e) => {
106            let size = e.size.map(|fs| fs.to_px(dpi)).unwrap_or_else(|| {
107                missing.push(format!("{prefix}.size"));
108                0.0
109            });
110            let line_height = e.line_height.map(|fs| fs.to_px(dpi)).unwrap_or_else(|| {
111                missing.push(format!("{prefix}.line_height"));
112                0.0
113            });
114            let weight = require(&e.weight, &format!("{prefix}.weight"), missing);
115            ResolvedTextScaleEntry {
116                size,
117                weight,
118                line_height,
119            }
120        }
121    }
122}
123
124/// Validate an `Option<BorderSpec>` (widget border fields).
125/// If None, records the path as missing. Requires the 4 sub-fields filled by
126/// border_inheritance (color, corner_radius, line_width, shadow_enabled).
127/// Padding sub-fields are sizing fields with no inheritance -- they use
128/// the preset value if present, otherwise default to `T::default()`.
129fn require_border(
130    border: &Option<BorderSpec>,
131    prefix: &str,
132    missing: &mut Vec<String>,
133) -> ResolvedBorderSpec {
134    match border {
135        None => {
136            missing.push(prefix.to_string());
137            ResolvedBorderSpec::default()
138        }
139        Some(b) => {
140            let color = require(&b.color, &format!("{prefix}.color"), missing);
141            let corner_radius = require(
142                &b.corner_radius,
143                &format!("{prefix}.corner_radius"),
144                missing,
145            );
146            let line_width = require(&b.line_width, &format!("{prefix}.line_width"), missing);
147            let shadow_enabled = require(
148                &b.shadow_enabled,
149                &format!("{prefix}.shadow_enabled"),
150                missing,
151            );
152            ResolvedBorderSpec {
153                color,
154                corner_radius,
155                corner_radius_lg: b.corner_radius_lg.unwrap_or_default(),
156                line_width,
157                opacity: b.opacity.unwrap_or_default(),
158                shadow_enabled,
159                padding_horizontal: b.padding_horizontal.unwrap_or_default(),
160                padding_vertical: b.padding_vertical.unwrap_or_default(),
161            }
162        }
163    }
164}
165
166/// Resolve a border for widgets excluded from border_inheritance (menu, tab, card).
167/// These widgets have no inheritance for any border sub-field; all sub-fields
168/// use the preset value if present, otherwise `T::default()`. No validation
169/// errors are recorded -- the border is entirely optional.
170fn border_all_optional(border: &Option<BorderSpec>) -> ResolvedBorderSpec {
171    match border {
172        None => ResolvedBorderSpec::default(),
173        Some(b) => ResolvedBorderSpec {
174            color: b.color.unwrap_or_default(),
175            corner_radius: b.corner_radius.unwrap_or_default(),
176            corner_radius_lg: b.corner_radius_lg.unwrap_or_default(),
177            line_width: b.line_width.unwrap_or_default(),
178            opacity: b.opacity.unwrap_or_default(),
179            shadow_enabled: b.shadow_enabled.unwrap_or_default(),
180            padding_horizontal: b.padding_horizontal.unwrap_or_default(),
181            padding_vertical: b.padding_vertical.unwrap_or_default(),
182        },
183    }
184}
185
186/// Validate a border for widgets with partial border inheritance (sidebar, status_bar).
187/// Only color + line_width are inherited; other sub-fields use defaults if not in preset.
188fn require_border_partial(
189    border: &Option<BorderSpec>,
190    prefix: &str,
191    missing: &mut Vec<String>,
192) -> ResolvedBorderSpec {
193    match border {
194        None => {
195            missing.push(prefix.to_string());
196            ResolvedBorderSpec::default()
197        }
198        Some(b) => {
199            let color = require(&b.color, &format!("{prefix}.color"), missing);
200            let line_width = require(&b.line_width, &format!("{prefix}.line_width"), missing);
201            ResolvedBorderSpec {
202                color,
203                corner_radius: b.corner_radius.unwrap_or_default(),
204                corner_radius_lg: b.corner_radius_lg.unwrap_or_default(),
205                line_width,
206                opacity: b.opacity.unwrap_or_default(),
207                shadow_enabled: b.shadow_enabled.unwrap_or_default(),
208                padding_horizontal: b.padding_horizontal.unwrap_or_default(),
209                padding_vertical: b.padding_vertical.unwrap_or_default(),
210            }
211        }
212    }
213}
214
215// --- Range-check helpers for validate() ---
216//
217// These push a descriptive message to the `missing` vec (reusing the same
218// error-collection pattern as require()) so that all problems — missing
219// fields AND out-of-range values — are reported in a single pass.
220
221/// Check that an `f32` value is finite and non-negative (>= 0.0).
222fn check_non_negative(value: f32, path: &str, errors: &mut Vec<String>) {
223    if !value.is_finite() || value < 0.0 {
224        errors.push(format!(
225            "{path} must be a finite non-negative number, got {value}"
226        ));
227    }
228}
229
230/// Check that an `f32` value is finite and strictly positive (> 0.0).
231fn check_positive(value: f32, path: &str, errors: &mut Vec<String>) {
232    if !value.is_finite() || value <= 0.0 {
233        errors.push(format!(
234            "{path} must be a finite positive number, got {value}"
235        ));
236    }
237}
238
239/// Check that an `f32` value is finite and falls within an inclusive range.
240fn check_range_f32(value: f32, min: f32, max: f32, path: &str, errors: &mut Vec<String>) {
241    if !value.is_finite() || value < min || value > max {
242        errors.push(format!(
243            "{path} must be a finite number between {min} and {max}, got {value}"
244        ));
245    }
246}
247
248/// Check that a `u16` value falls within an inclusive range.
249fn check_range_u16(value: u16, min: u16, max: u16, path: &str, errors: &mut Vec<String>) {
250    if value < min || value > max {
251        errors.push(format!("{path} must be {min}..={max}, got {value}"));
252    }
253}
254
255/// Check that a min value does not exceed its corresponding max value.
256fn check_min_max(
257    min_val: f32,
258    max_val: f32,
259    min_name: &str,
260    max_name: &str,
261    errors: &mut Vec<String>,
262) {
263    if min_val > max_val {
264        errors.push(format!(
265            "{min_name} ({min_val}) must not exceed {max_name} ({max_val})"
266        ));
267    }
268}
269
270impl ThemeVariant {
271    // --- validate() ---
272
273    /// Convert this ThemeVariant into a [`ResolvedThemeVariant`] with all fields guaranteed.
274    ///
275    /// Should be called after [`resolve()`](ThemeVariant::resolve). Walks every field
276    /// and collects missing (None) field paths, then validates that numeric values
277    /// are within legal ranges (e.g., spacing >= 0, opacity 0..=1, font weight
278    /// 100..=900). Returns `Ok(ResolvedThemeVariant)` if all fields are populated
279    /// and in range.
280    ///
281    /// # Errors
282    ///
283    /// Returns [`crate::Error::Resolution`] containing a [`ThemeResolutionError`]
284    /// with all missing field paths and out-of-range diagnostics.
285    // Validation is kept in a single function for field-level traceability.
286    // Future extraction into per-widget validators is planned for v0.6.0.
287    #[must_use = "this returns the validation result; handle the Result or propagate with ?"]
288    pub fn validate(&self) -> crate::Result<ResolvedThemeVariant> {
289        let mut missing = Vec::new();
290
291        // ┌──────────────────────────────────────────────────────────────┐
292        // │ Adding a new widget or field? Follow these steps:           │
293        // │ 1. Add require() calls in the extraction section below      │
294        // │ 2. Add range checks in the "range validation" section       │
295        // │ 3. Add the field to the ResolvedThemeVariant construction   │
296        // │ Each section is marked with // --- widget_name ---          │
297        // └──────────────────────────────────────────────────────────────┘
298
299        // --- defaults ---
300
301        // Extract DPI for FontSize::to_px conversion throughout validate().
302        let dpi = self.defaults.font_dpi.unwrap_or(DEFAULT_FONT_DPI);
303
304        let defaults_font = require_font(&self.defaults.font, "defaults.font", dpi, &mut missing);
305        let defaults_line_height = require(
306            &self.defaults.line_height,
307            "defaults.line_height",
308            &mut missing,
309        );
310        let defaults_mono_font = require_font(
311            &self.defaults.mono_font,
312            "defaults.mono_font",
313            dpi,
314            &mut missing,
315        );
316
317        let defaults_background = require(
318            &self.defaults.background_color,
319            "defaults.background_color",
320            &mut missing,
321        );
322        let defaults_foreground = require(
323            &self.defaults.text_color,
324            "defaults.text_color",
325            &mut missing,
326        );
327        let defaults_accent = require(
328            &self.defaults.accent_color,
329            "defaults.accent_color",
330            &mut missing,
331        );
332        let defaults_accent_foreground = require(
333            &self.defaults.accent_text_color,
334            "defaults.accent_text_color",
335            &mut missing,
336        );
337        let defaults_surface = require(
338            &self.defaults.surface_color,
339            "defaults.surface_color",
340            &mut missing,
341        );
342        let defaults_border = require(
343            &self.defaults.border.color,
344            "defaults.border.color",
345            &mut missing,
346        );
347        let defaults_muted = require(
348            &self.defaults.muted_color,
349            "defaults.muted_color",
350            &mut missing,
351        );
352        let defaults_shadow = require(
353            &self.defaults.shadow_color,
354            "defaults.shadow_color",
355            &mut missing,
356        );
357        let defaults_link = require(
358            &self.defaults.link_color,
359            "defaults.link_color",
360            &mut missing,
361        );
362        let defaults_selection = require(
363            &self.defaults.selection_background,
364            "defaults.selection_background",
365            &mut missing,
366        );
367        let defaults_selection_foreground = require(
368            &self.defaults.selection_text_color,
369            "defaults.selection_text_color",
370            &mut missing,
371        );
372        let defaults_selection_inactive = require(
373            &self.defaults.selection_inactive_background,
374            "defaults.selection_inactive_background",
375            &mut missing,
376        );
377        let defaults_disabled_foreground = require(
378            &self.defaults.disabled_text_color,
379            "defaults.disabled_text_color",
380            &mut missing,
381        );
382
383        let defaults_danger = require(
384            &self.defaults.danger_color,
385            "defaults.danger_color",
386            &mut missing,
387        );
388        let defaults_danger_foreground = require(
389            &self.defaults.danger_text_color,
390            "defaults.danger_text_color",
391            &mut missing,
392        );
393        let defaults_warning = require(
394            &self.defaults.warning_color,
395            "defaults.warning_color",
396            &mut missing,
397        );
398        let defaults_warning_foreground = require(
399            &self.defaults.warning_text_color,
400            "defaults.warning_text_color",
401            &mut missing,
402        );
403        let defaults_success = require(
404            &self.defaults.success_color,
405            "defaults.success_color",
406            &mut missing,
407        );
408        let defaults_success_foreground = require(
409            &self.defaults.success_text_color,
410            "defaults.success_text_color",
411            &mut missing,
412        );
413        let defaults_info = require(
414            &self.defaults.info_color,
415            "defaults.info_color",
416            &mut missing,
417        );
418        let defaults_info_foreground = require(
419            &self.defaults.info_text_color,
420            "defaults.info_text_color",
421            &mut missing,
422        );
423
424        let defaults_radius = require(
425            &self.defaults.border.corner_radius,
426            "defaults.border.corner_radius",
427            &mut missing,
428        );
429        let defaults_radius_lg = require(
430            &self.defaults.border.corner_radius_lg,
431            "defaults.border.corner_radius_lg",
432            &mut missing,
433        );
434        let defaults_frame_width = require(
435            &self.defaults.border.line_width,
436            "defaults.border.line_width",
437            &mut missing,
438        );
439        let defaults_disabled_opacity = require(
440            &self.defaults.disabled_opacity,
441            "defaults.disabled_opacity",
442            &mut missing,
443        );
444        let defaults_border_opacity = require(
445            &self.defaults.border.opacity,
446            "defaults.border.opacity",
447            &mut missing,
448        );
449        let defaults_shadow_enabled = require(
450            &self.defaults.border.shadow_enabled,
451            "defaults.border.shadow_enabled",
452            &mut missing,
453        );
454
455        let defaults_focus_ring_color = require(
456            &self.defaults.focus_ring_color,
457            "defaults.focus_ring_color",
458            &mut missing,
459        );
460        let defaults_focus_ring_width = require(
461            &self.defaults.focus_ring_width,
462            "defaults.focus_ring_width",
463            &mut missing,
464        );
465        let defaults_focus_ring_offset = require(
466            &self.defaults.focus_ring_offset,
467            "defaults.focus_ring_offset",
468            &mut missing,
469        );
470
471        let defaults_border_padding_h = require(
472            &self.defaults.border.padding_horizontal,
473            "defaults.border.padding_horizontal",
474            &mut missing,
475        );
476        let defaults_border_padding_v = require(
477            &self.defaults.border.padding_vertical,
478            "defaults.border.padding_vertical",
479            &mut missing,
480        );
481        let defaults_text_selection_background = require(
482            &self.defaults.text_selection_background,
483            "defaults.text_selection_background",
484            &mut missing,
485        );
486        let defaults_text_selection_color = require(
487            &self.defaults.text_selection_color,
488            "defaults.text_selection_color",
489            &mut missing,
490        );
491
492        let defaults_icon_sizes_toolbar = require(
493            &self.defaults.icon_sizes.toolbar,
494            "defaults.icon_sizes.toolbar",
495            &mut missing,
496        );
497        let defaults_icon_sizes_small = require(
498            &self.defaults.icon_sizes.small,
499            "defaults.icon_sizes.small",
500            &mut missing,
501        );
502        let defaults_icon_sizes_large = require(
503            &self.defaults.icon_sizes.large,
504            "defaults.icon_sizes.large",
505            &mut missing,
506        );
507        let defaults_icon_sizes_dialog = require(
508            &self.defaults.icon_sizes.dialog,
509            "defaults.icon_sizes.dialog",
510            &mut missing,
511        );
512        let defaults_icon_sizes_panel = require(
513            &self.defaults.icon_sizes.panel,
514            "defaults.icon_sizes.panel",
515            &mut missing,
516        );
517
518        // font_dpi records the DPI used for pt-to-px conversion (or the default).
519        let defaults_font_dpi = dpi;
520
521        let defaults_text_scaling_factor = require(
522            &self.defaults.text_scaling_factor,
523            "defaults.text_scaling_factor",
524            &mut missing,
525        );
526        let defaults_reduce_motion = require(
527            &self.defaults.reduce_motion,
528            "defaults.reduce_motion",
529            &mut missing,
530        );
531        let defaults_high_contrast = require(
532            &self.defaults.high_contrast,
533            "defaults.high_contrast",
534            &mut missing,
535        );
536        let defaults_reduce_transparency = require(
537            &self.defaults.reduce_transparency,
538            "defaults.reduce_transparency",
539            &mut missing,
540        );
541
542        // --- text_scale ---
543
544        let ts_caption = require_text_scale_entry(
545            &self.text_scale.caption,
546            "text_scale.caption",
547            dpi,
548            &mut missing,
549        );
550        let ts_section_heading = require_text_scale_entry(
551            &self.text_scale.section_heading,
552            "text_scale.section_heading",
553            dpi,
554            &mut missing,
555        );
556        let ts_dialog_title = require_text_scale_entry(
557            &self.text_scale.dialog_title,
558            "text_scale.dialog_title",
559            dpi,
560            &mut missing,
561        );
562        let ts_display = require_text_scale_entry(
563            &self.text_scale.display,
564            "text_scale.display",
565            dpi,
566            &mut missing,
567        );
568
569        // --- window ---
570
571        let window_background = require(
572            &self.window.background_color,
573            "window.background_color",
574            &mut missing,
575        );
576        let window_title_bar_background = require(
577            &self.window.title_bar_background,
578            "window.title_bar_background",
579            &mut missing,
580        );
581        let window_inactive_title_bar_background = require(
582            &self.window.inactive_title_bar_background,
583            "window.inactive_title_bar_background",
584            &mut missing,
585        );
586        let window_inactive_title_bar_foreground = require(
587            &self.window.inactive_title_bar_text_color,
588            "window.inactive_title_bar_text_color",
589            &mut missing,
590        );
591        let window_title_bar_font = require_font_opt(
592            &self.window.title_bar_font,
593            "window.title_bar_font",
594            dpi,
595            &mut missing,
596        );
597
598        // --- button ---
599
600        let button_background = require(
601            &self.button.background_color,
602            "button.background_color",
603            &mut missing,
604        );
605        let button_primary_background = require(
606            &self.button.primary_background,
607            "button.primary_background",
608            &mut missing,
609        );
610        let button_primary_foreground = require(
611            &self.button.primary_text_color,
612            "button.primary_text_color",
613            &mut missing,
614        );
615        let button_min_width = require(&self.button.min_width, "button.min_width", &mut missing);
616        let button_min_height = require(&self.button.min_height, "button.min_height", &mut missing);
617        let button_icon_spacing = require(
618            &self.button.icon_text_gap,
619            "button.icon_text_gap",
620            &mut missing,
621        );
622        let button_disabled_opacity = require(
623            &self.button.disabled_opacity,
624            "button.disabled_opacity",
625            &mut missing,
626        );
627        let button_font = require_font_opt(&self.button.font, "button.font", dpi, &mut missing);
628
629        // --- input ---
630
631        let input_background = require(
632            &self.input.background_color,
633            "input.background_color",
634            &mut missing,
635        );
636        let input_placeholder = require(
637            &self.input.placeholder_color,
638            "input.placeholder_color",
639            &mut missing,
640        );
641        let input_caret = require(&self.input.caret_color, "input.caret_color", &mut missing);
642        let input_selection = require(
643            &self.input.selection_background,
644            "input.selection_background",
645            &mut missing,
646        );
647        let input_selection_foreground = require(
648            &self.input.selection_text_color,
649            "input.selection_text_color",
650            &mut missing,
651        );
652        let input_min_height = require(&self.input.min_height, "input.min_height", &mut missing);
653        let input_font = require_font_opt(&self.input.font, "input.font", dpi, &mut missing);
654
655        // --- checkbox ---
656
657        let checkbox_checked_background = require(
658            &self.checkbox.checked_background,
659            "checkbox.checked_background",
660            &mut missing,
661        );
662        let checkbox_indicator_size = require(
663            &self.checkbox.indicator_width,
664            "checkbox.indicator_width",
665            &mut missing,
666        );
667        let checkbox_spacing =
668            require(&self.checkbox.label_gap, "checkbox.label_gap", &mut missing);
669
670        // --- menu ---
671
672        let menu_background = require(
673            &self.menu.background_color,
674            "menu.background_color",
675            &mut missing,
676        );
677        let menu_separator = require(
678            &self.menu.separator_color,
679            "menu.separator_color",
680            &mut missing,
681        );
682        let menu_item_height = require(&self.menu.row_height, "menu.row_height", &mut missing);
683        let menu_icon_spacing =
684            require(&self.menu.icon_text_gap, "menu.icon_text_gap", &mut missing);
685        let menu_font = require_font_opt(&self.menu.font, "menu.font", dpi, &mut missing);
686
687        // --- tooltip ---
688
689        let tooltip_background = require(
690            &self.tooltip.background_color,
691            "tooltip.background_color",
692            &mut missing,
693        );
694        let tooltip_max_width = require(&self.tooltip.max_width, "tooltip.max_width", &mut missing);
695        let tooltip_font = require_font_opt(&self.tooltip.font, "tooltip.font", dpi, &mut missing);
696
697        // --- scrollbar ---
698
699        let scrollbar_track = require(
700            &self.scrollbar.track_color,
701            "scrollbar.track_color",
702            &mut missing,
703        );
704        let scrollbar_thumb = require(
705            &self.scrollbar.thumb_color,
706            "scrollbar.thumb_color",
707            &mut missing,
708        );
709        let scrollbar_thumb_hover = require(
710            &self.scrollbar.thumb_hover_color,
711            "scrollbar.thumb_hover_color",
712            &mut missing,
713        );
714        let scrollbar_width = require(
715            &self.scrollbar.groove_width,
716            "scrollbar.groove_width",
717            &mut missing,
718        );
719        let scrollbar_min_thumb_height = require(
720            &self.scrollbar.min_thumb_length,
721            "scrollbar.min_thumb_length",
722            &mut missing,
723        );
724        let scrollbar_slider_width = require(
725            &self.scrollbar.thumb_width,
726            "scrollbar.thumb_width",
727            &mut missing,
728        );
729        let scrollbar_overlay_mode = require(
730            &self.scrollbar.overlay_mode,
731            "scrollbar.overlay_mode",
732            &mut missing,
733        );
734
735        // --- slider ---
736
737        let slider_fill = require(&self.slider.fill_color, "slider.fill_color", &mut missing);
738        let slider_track = require(&self.slider.track_color, "slider.track_color", &mut missing);
739        let slider_thumb = require(&self.slider.thumb_color, "slider.thumb_color", &mut missing);
740        let slider_track_height = require(
741            &self.slider.track_height,
742            "slider.track_height",
743            &mut missing,
744        );
745        let slider_thumb_size = require(
746            &self.slider.thumb_diameter,
747            "slider.thumb_diameter",
748            &mut missing,
749        );
750        let slider_tick_length = require(
751            &self.slider.tick_mark_length,
752            "slider.tick_mark_length",
753            &mut missing,
754        );
755
756        // --- progress_bar ---
757
758        let progress_bar_fill = require(
759            &self.progress_bar.fill_color,
760            "progress_bar.fill_color",
761            &mut missing,
762        );
763        let progress_bar_track = require(
764            &self.progress_bar.track_color,
765            "progress_bar.track_color",
766            &mut missing,
767        );
768        let progress_bar_height = require(
769            &self.progress_bar.track_height,
770            "progress_bar.track_height",
771            &mut missing,
772        );
773        let progress_bar_min_width = require(
774            &self.progress_bar.min_width,
775            "progress_bar.min_width",
776            &mut missing,
777        );
778        // progress_bar.border.corner_radius -- handled by placeholder border spec
779
780        // --- tab ---
781
782        let tab_background = require(
783            &self.tab.background_color,
784            "tab.background_color",
785            &mut missing,
786        );
787        let tab_active_background = require(
788            &self.tab.active_background,
789            "tab.active_background",
790            &mut missing,
791        );
792        let tab_active_foreground = require(
793            &self.tab.active_text_color,
794            "tab.active_text_color",
795            &mut missing,
796        );
797        let tab_bar_background =
798            require(&self.tab.bar_background, "tab.bar_background", &mut missing);
799        let tab_min_width = require(&self.tab.min_width, "tab.min_width", &mut missing);
800        let tab_min_height = require(&self.tab.min_height, "tab.min_height", &mut missing);
801        let tab_hover_text_color = require(
802            &self.tab.hover_text_color,
803            "tab.hover_text_color",
804            &mut missing,
805        );
806
807        // --- sidebar ---
808
809        let sidebar_background = require(
810            &self.sidebar.background_color,
811            "sidebar.background_color",
812            &mut missing,
813        );
814
815        // --- toolbar ---
816
817        let toolbar_height = require(&self.toolbar.bar_height, "toolbar.bar_height", &mut missing);
818        let toolbar_item_spacing =
819            require(&self.toolbar.item_gap, "toolbar.item_gap", &mut missing);
820        let toolbar_font = require_font_opt(&self.toolbar.font, "toolbar.font", dpi, &mut missing);
821
822        // --- status_bar ---
823
824        let status_bar_font =
825            require_font_opt(&self.status_bar.font, "status_bar.font", dpi, &mut missing);
826
827        // --- list ---
828
829        let list_background = require(
830            &self.list.background_color,
831            "list.background_color",
832            &mut missing,
833        );
834        let list_alternate_row = require(
835            &self.list.alternate_row_background,
836            "list.alternate_row_background",
837            &mut missing,
838        );
839        let list_selection = require(
840            &self.list.selection_background,
841            "list.selection_background",
842            &mut missing,
843        );
844        let list_selection_foreground = require(
845            &self.list.selection_text_color,
846            "list.selection_text_color",
847            &mut missing,
848        );
849        let list_header_background = require(
850            &self.list.header_background,
851            "list.header_background",
852            &mut missing,
853        );
854        let list_grid_color = require(&self.list.grid_color, "list.grid_color", &mut missing);
855        let list_item_height = require(&self.list.row_height, "list.row_height", &mut missing);
856
857        // --- popover ---
858
859        let popover_background = require(
860            &self.popover.background_color,
861            "popover.background_color",
862            &mut missing,
863        );
864
865        // --- splitter ---
866
867        let splitter_width = require(
868            &self.splitter.divider_width,
869            "splitter.divider_width",
870            &mut missing,
871        );
872
873        // --- separator ---
874
875        let separator_color = require(
876            &self.separator.line_color,
877            "separator.line_color",
878            &mut missing,
879        );
880
881        // --- switch ---
882
883        let switch_checked_background = require(
884            &self.switch.checked_background,
885            "switch.checked_background",
886            &mut missing,
887        );
888        let switch_unchecked_background = require(
889            &self.switch.unchecked_background,
890            "switch.unchecked_background",
891            &mut missing,
892        );
893        let switch_thumb_background = require(
894            &self.switch.thumb_background,
895            "switch.thumb_background",
896            &mut missing,
897        );
898        let switch_track_width =
899            require(&self.switch.track_width, "switch.track_width", &mut missing);
900        let switch_track_height = require(
901            &self.switch.track_height,
902            "switch.track_height",
903            &mut missing,
904        );
905        let switch_thumb_size = require(
906            &self.switch.thumb_diameter,
907            "switch.thumb_diameter",
908            &mut missing,
909        );
910        let switch_track_radius = require(
911            &self.switch.track_radius,
912            "switch.track_radius",
913            &mut missing,
914        );
915
916        // --- dialog ---
917
918        let dialog_min_width = require(&self.dialog.min_width, "dialog.min_width", &mut missing);
919        let dialog_max_width = require(&self.dialog.max_width, "dialog.max_width", &mut missing);
920        let dialog_min_height = require(&self.dialog.min_height, "dialog.min_height", &mut missing);
921        let dialog_max_height = require(&self.dialog.max_height, "dialog.max_height", &mut missing);
922        let dialog_button_spacing =
923            require(&self.dialog.button_gap, "dialog.button_gap", &mut missing);
924        let dialog_icon_size = require(&self.dialog.icon_size, "dialog.icon_size", &mut missing);
925        let dialog_button_order = require(
926            &self.dialog.button_order,
927            "dialog.button_order",
928            &mut missing,
929        );
930        let dialog_title_font = require_font_opt(
931            &self.dialog.title_font,
932            "dialog.title_font",
933            dpi,
934            &mut missing,
935        );
936
937        // --- spinner ---
938
939        let spinner_fill = require(&self.spinner.fill_color, "spinner.fill_color", &mut missing);
940        let spinner_diameter = require(&self.spinner.diameter, "spinner.diameter", &mut missing);
941        let spinner_min_size = require(
942            &self.spinner.min_diameter,
943            "spinner.min_diameter",
944            &mut missing,
945        );
946        let spinner_stroke_width = require(
947            &self.spinner.stroke_width,
948            "spinner.stroke_width",
949            &mut missing,
950        );
951
952        // --- combo_box ---
953
954        let combo_box_min_height = require(
955            &self.combo_box.min_height,
956            "combo_box.min_height",
957            &mut missing,
958        );
959        let combo_box_min_width = require(
960            &self.combo_box.min_width,
961            "combo_box.min_width",
962            &mut missing,
963        );
964        let combo_box_arrow_size = require(
965            &self.combo_box.arrow_icon_size,
966            "combo_box.arrow_icon_size",
967            &mut missing,
968        );
969        let combo_box_arrow_area_width = require(
970            &self.combo_box.arrow_area_width,
971            "combo_box.arrow_area_width",
972            &mut missing,
973        );
974
975        // --- segmented_control ---
976
977        let segmented_control_segment_height = require(
978            &self.segmented_control.segment_height,
979            "segmented_control.segment_height",
980            &mut missing,
981        );
982        let segmented_control_separator_width = require(
983            &self.segmented_control.separator_width,
984            "segmented_control.separator_width",
985            &mut missing,
986        );
987
988        // --- card ---
989
990        let card_background = require(
991            &self.card.background_color,
992            "card.background_color",
993            &mut missing,
994        );
995
996        // --- expander ---
997
998        let expander_header_height = require(
999            &self.expander.header_height,
1000            "expander.header_height",
1001            &mut missing,
1002        );
1003        let expander_arrow_size = require(
1004            &self.expander.arrow_icon_size,
1005            "expander.arrow_icon_size",
1006            &mut missing,
1007        );
1008
1009        // --- link ---
1010
1011        let link_visited = require(
1012            &self.link.visited_text_color,
1013            "link.visited_text_color",
1014            &mut missing,
1015        );
1016        let link_background = require(
1017            &self.link.background_color,
1018            "link.background_color",
1019            &mut missing,
1020        );
1021        let link_hover_bg = require(
1022            &self.link.hover_background,
1023            "link.hover_background",
1024            &mut missing,
1025        );
1026        let link_underline = require(&self.link.underline_enabled, "link.underline", &mut missing);
1027
1028        // --- icon_set / icon_theme ---
1029
1030        let icon_set = require(&self.icon_set, "icon_set", &mut missing);
1031        let icon_theme = require(&self.icon_theme, "icon_theme", &mut missing);
1032
1033        // --- require() calls for fields previously using placeholder bindings ---
1034
1035        // window
1036        let window_border_spec = require_border(&self.window.border, "window.border", &mut missing);
1037
1038        // button
1039        let button_hover_background = require(
1040            &self.button.hover_background,
1041            "button.hover_background",
1042            &mut missing,
1043        );
1044        let button_hover_text_color = require(
1045            &self.button.hover_text_color,
1046            "button.hover_text_color",
1047            &mut missing,
1048        );
1049        let button_border_spec = require_border(&self.button.border, "button.border", &mut missing);
1050        let button_active_text_color = require(
1051            &self.button.active_text_color,
1052            "button.active_text_color",
1053            &mut missing,
1054        );
1055        let button_disabled_text_color = require(
1056            &self.button.disabled_text_color,
1057            "button.disabled_text_color",
1058            &mut missing,
1059        );
1060
1061        // input
1062        let input_disabled_opacity = require(
1063            &self.input.disabled_opacity,
1064            "input.disabled_opacity",
1065            &mut missing,
1066        );
1067        let input_border_spec = require_border(&self.input.border, "input.border", &mut missing);
1068        let input_disabled_text_color = require(
1069            &self.input.disabled_text_color,
1070            "input.disabled_text_color",
1071            &mut missing,
1072        );
1073
1074        // checkbox
1075        let checkbox_background_color = require(
1076            &self.checkbox.background_color,
1077            "checkbox.background_color",
1078            &mut missing,
1079        );
1080        let checkbox_indicator_color = require(
1081            &self.checkbox.indicator_color,
1082            "checkbox.indicator_color",
1083            &mut missing,
1084        );
1085        let checkbox_disabled_opacity = require(
1086            &self.checkbox.disabled_opacity,
1087            "checkbox.disabled_opacity",
1088            &mut missing,
1089        );
1090        let checkbox_font =
1091            require_font_opt(&self.checkbox.font, "checkbox.font", dpi, &mut missing);
1092        let checkbox_border_spec =
1093            require_border(&self.checkbox.border, "checkbox.border", &mut missing);
1094        let checkbox_disabled_text_color = require(
1095            &self.checkbox.disabled_text_color,
1096            "checkbox.disabled_text_color",
1097            &mut missing,
1098        );
1099
1100        // menu (excluded from border_inheritance -- border belongs to popup container)
1101        let menu_icon_size = require(&self.menu.icon_size, "menu.icon_size", &mut missing);
1102        let menu_hover_background = require(
1103            &self.menu.hover_background,
1104            "menu.hover_background",
1105            &mut missing,
1106        );
1107        let menu_hover_text_color = require(
1108            &self.menu.hover_text_color,
1109            "menu.hover_text_color",
1110            &mut missing,
1111        );
1112        let menu_disabled_text_color = require(
1113            &self.menu.disabled_text_color,
1114            "menu.disabled_text_color",
1115            &mut missing,
1116        );
1117        // menu border: no inheritance, all sub-fields optional (preset provides if available)
1118        let menu_border_spec = border_all_optional(&self.menu.border);
1119
1120        // tooltip
1121        let tooltip_border_spec =
1122            require_border(&self.tooltip.border, "tooltip.border", &mut missing);
1123
1124        // slider
1125        let slider_disabled_opacity = require(
1126            &self.slider.disabled_opacity,
1127            "slider.disabled_opacity",
1128            &mut missing,
1129        );
1130
1131        // progress_bar
1132        let progress_bar_border_spec = require_border(
1133            &self.progress_bar.border,
1134            "progress_bar.border",
1135            &mut missing,
1136        );
1137
1138        // tab (excluded from border_inheritance -- all border sub-fields are platform-specific)
1139        let tab_font = require_font_opt(&self.tab.font, "tab.font", dpi, &mut missing);
1140        // tab border: no inheritance, all sub-fields optional (preset provides if available)
1141        let tab_border_spec = border_all_optional(&self.tab.border);
1142
1143        // sidebar (partial border inheritance: color + line_width only)
1144        let sidebar_selection_background = require(
1145            &self.sidebar.selection_background,
1146            "sidebar.selection_background",
1147            &mut missing,
1148        );
1149        let sidebar_selection_text_color = require(
1150            &self.sidebar.selection_text_color,
1151            "sidebar.selection_text_color",
1152            &mut missing,
1153        );
1154        let sidebar_hover_background = require(
1155            &self.sidebar.hover_background,
1156            "sidebar.hover_background",
1157            &mut missing,
1158        );
1159        let sidebar_font = require_font_opt(&self.sidebar.font, "sidebar.font", dpi, &mut missing);
1160        let sidebar_border_spec =
1161            require_border_partial(&self.sidebar.border, "sidebar.border", &mut missing);
1162
1163        // toolbar
1164        let toolbar_background_color = require(
1165            &self.toolbar.background_color,
1166            "toolbar.background_color",
1167            &mut missing,
1168        );
1169        let toolbar_icon_size = require(&self.toolbar.icon_size, "toolbar.icon_size", &mut missing);
1170        let toolbar_border_spec =
1171            require_border(&self.toolbar.border, "toolbar.border", &mut missing);
1172
1173        // status_bar (partial border inheritance: color + line_width only)
1174        let status_bar_background_color = require(
1175            &self.status_bar.background_color,
1176            "status_bar.background_color",
1177            &mut missing,
1178        );
1179        let status_bar_border_spec =
1180            require_border_partial(&self.status_bar.border, "status_bar.border", &mut missing);
1181
1182        // list
1183        let list_item_font =
1184            require_font_opt(&self.list.item_font, "list.item_font", dpi, &mut missing);
1185        let list_header_font = require_font_opt(
1186            &self.list.header_font,
1187            "list.header_font",
1188            dpi,
1189            &mut missing,
1190        );
1191        let list_hover_background = require(
1192            &self.list.hover_background,
1193            "list.hover_background",
1194            &mut missing,
1195        );
1196        let list_hover_text_color = require(
1197            &self.list.hover_text_color,
1198            "list.hover_text_color",
1199            &mut missing,
1200        );
1201        let list_disabled_text_color = require(
1202            &self.list.disabled_text_color,
1203            "list.disabled_text_color",
1204            &mut missing,
1205        );
1206        let list_border_spec = require_border(&self.list.border, "list.border", &mut missing);
1207
1208        // popover
1209        let popover_font = require_font_opt(&self.popover.font, "popover.font", dpi, &mut missing);
1210        let popover_border_spec =
1211            require_border(&self.popover.border, "popover.border", &mut missing);
1212
1213        // splitter
1214        let splitter_divider_color = require(
1215            &self.splitter.divider_color,
1216            "splitter.divider_color",
1217            &mut missing,
1218        );
1219        let splitter_hover_color = require(
1220            &self.splitter.hover_color,
1221            "splitter.hover_color",
1222            &mut missing,
1223        );
1224
1225        // separator
1226        let separator_line_width = require(
1227            &self.separator.line_width,
1228            "separator.line_width",
1229            &mut missing,
1230        );
1231
1232        // switch
1233        let switch_disabled_opacity = require(
1234            &self.switch.disabled_opacity,
1235            "switch.disabled_opacity",
1236            &mut missing,
1237        );
1238
1239        // dialog
1240        let dialog_background_color = require(
1241            &self.dialog.background_color,
1242            "dialog.background_color",
1243            &mut missing,
1244        );
1245        let dialog_body_font = require_font_opt(
1246            &self.dialog.body_font,
1247            "dialog.body_font",
1248            dpi,
1249            &mut missing,
1250        );
1251        let dialog_border_spec = require_border(&self.dialog.border, "dialog.border", &mut missing);
1252
1253        // link
1254        let link_font = require_font_opt(&self.link.font, "link.font", dpi, &mut missing);
1255        let link_hover_text_color = require(
1256            &self.link.hover_text_color,
1257            "link.hover_text_color",
1258            &mut missing,
1259        );
1260        let link_active_text_color = require(
1261            &self.link.active_text_color,
1262            "link.active_text_color",
1263            &mut missing,
1264        );
1265        let link_disabled_text_color = require(
1266            &self.link.disabled_text_color,
1267            "link.disabled_text_color",
1268            &mut missing,
1269        );
1270
1271        // combo_box
1272        let combo_box_background_color = require(
1273            &self.combo_box.background_color,
1274            "combo_box.background_color",
1275            &mut missing,
1276        );
1277        let combo_box_disabled_opacity = require(
1278            &self.combo_box.disabled_opacity,
1279            "combo_box.disabled_opacity",
1280            &mut missing,
1281        );
1282        let combo_box_disabled_text_color = require(
1283            &self.combo_box.disabled_text_color,
1284            "combo_box.disabled_text_color",
1285            &mut missing,
1286        );
1287        let combo_box_font =
1288            require_font_opt(&self.combo_box.font, "combo_box.font", dpi, &mut missing);
1289        let combo_box_border_spec =
1290            require_border(&self.combo_box.border, "combo_box.border", &mut missing);
1291
1292        // segmented_control
1293        let segmented_control_background_color = require(
1294            &self.segmented_control.background_color,
1295            "segmented_control.background_color",
1296            &mut missing,
1297        );
1298        let segmented_control_active_background = require(
1299            &self.segmented_control.active_background,
1300            "segmented_control.active_background",
1301            &mut missing,
1302        );
1303        let segmented_control_active_text_color = require(
1304            &self.segmented_control.active_text_color,
1305            "segmented_control.active_text_color",
1306            &mut missing,
1307        );
1308        let segmented_control_disabled_opacity = require(
1309            &self.segmented_control.disabled_opacity,
1310            "segmented_control.disabled_opacity",
1311            &mut missing,
1312        );
1313        let segmented_control_font = require_font_opt(
1314            &self.segmented_control.font,
1315            "segmented_control.font",
1316            dpi,
1317            &mut missing,
1318        );
1319        let segmented_control_border_spec = require_border(
1320            &self.segmented_control.border,
1321            "segmented_control.border",
1322            &mut missing,
1323        );
1324
1325        // card (excluded from border_inheritance -- all sub-fields platform-specific or none)
1326        let card_border_spec = border_all_optional(&self.card.border);
1327
1328        // expander
1329        let expander_font =
1330            require_font_opt(&self.expander.font, "expander.font", dpi, &mut missing);
1331        let expander_border_spec =
1332            require_border(&self.expander.border, "expander.border", &mut missing);
1333
1334        // NEW WIDGET: add require() calls above this line, then add
1335        // range checks below and construction fields at the bottom.
1336
1337        // --- range validation ---
1338        //
1339        // Operate on the already-extracted values from require(). If a field was
1340        // missing, require() returned T::default() as placeholder — range-checking
1341        // that placeholder is harmless because the missing-field error already
1342        // captured the real problem.
1343
1344        // Fonts: size > 0, weight 100..=900
1345        check_positive(defaults_font.size, "defaults.font.size", &mut missing);
1346        check_range_u16(
1347            defaults_font.weight,
1348            100,
1349            900,
1350            "defaults.font.weight",
1351            &mut missing,
1352        );
1353        check_positive(
1354            defaults_mono_font.size,
1355            "defaults.mono_font.size",
1356            &mut missing,
1357        );
1358        check_range_u16(
1359            defaults_mono_font.weight,
1360            100,
1361            900,
1362            "defaults.mono_font.weight",
1363            &mut missing,
1364        );
1365
1366        // defaults: line_height > 0, text_scaling_factor > 0
1367        check_positive(defaults_line_height, "defaults.line_height", &mut missing);
1368        check_positive(
1369            defaults_text_scaling_factor,
1370            "defaults.text_scaling_factor",
1371            &mut missing,
1372        );
1373
1374        // defaults: radius, geometry >= 0
1375        check_non_negative(
1376            defaults_radius,
1377            "defaults.border.corner_radius",
1378            &mut missing,
1379        );
1380        check_non_negative(
1381            defaults_radius_lg,
1382            "defaults.border.corner_radius_lg",
1383            &mut missing,
1384        );
1385        check_non_negative(
1386            defaults_frame_width,
1387            "defaults.border.line_width",
1388            &mut missing,
1389        );
1390        check_non_negative(
1391            defaults_focus_ring_width,
1392            "defaults.focus_ring_width",
1393            &mut missing,
1394        );
1395        // Note: focus_ring_offset is intentionally NOT range-checked — negative values
1396        // mean an inset focus ring (e.g., adwaita uses -2.0, macOS uses -1.0).
1397
1398        // defaults: opacity 0..=1
1399        check_range_f32(
1400            defaults_disabled_opacity,
1401            0.0,
1402            1.0,
1403            "defaults.disabled_opacity",
1404            &mut missing,
1405        );
1406        check_range_f32(
1407            defaults_border_opacity,
1408            0.0,
1409            1.0,
1410            "defaults.border.opacity",
1411            &mut missing,
1412        );
1413
1414        // defaults: border padding >= 0
1415        check_non_negative(
1416            defaults_border_padding_h,
1417            "defaults.border.padding_horizontal",
1418            &mut missing,
1419        );
1420        check_non_negative(
1421            defaults_border_padding_v,
1422            "defaults.border.padding_vertical",
1423            &mut missing,
1424        );
1425
1426        // defaults: icon sizes >= 0
1427        check_non_negative(
1428            defaults_icon_sizes_toolbar,
1429            "defaults.icon_sizes.toolbar",
1430            &mut missing,
1431        );
1432        check_non_negative(
1433            defaults_icon_sizes_small,
1434            "defaults.icon_sizes.small",
1435            &mut missing,
1436        );
1437        check_non_negative(
1438            defaults_icon_sizes_large,
1439            "defaults.icon_sizes.large",
1440            &mut missing,
1441        );
1442        check_non_negative(
1443            defaults_icon_sizes_dialog,
1444            "defaults.icon_sizes.dialog",
1445            &mut missing,
1446        );
1447        check_non_negative(
1448            defaults_icon_sizes_panel,
1449            "defaults.icon_sizes.panel",
1450            &mut missing,
1451        );
1452
1453        // text_scale: entry sizes > 0, line_height > 0
1454        check_positive(ts_caption.size, "text_scale.caption.size", &mut missing);
1455        check_positive(
1456            ts_caption.line_height,
1457            "text_scale.caption.line_height",
1458            &mut missing,
1459        );
1460        check_range_u16(
1461            ts_caption.weight,
1462            100,
1463            900,
1464            "text_scale.caption.weight",
1465            &mut missing,
1466        );
1467        check_positive(
1468            ts_section_heading.size,
1469            "text_scale.section_heading.size",
1470            &mut missing,
1471        );
1472        check_positive(
1473            ts_section_heading.line_height,
1474            "text_scale.section_heading.line_height",
1475            &mut missing,
1476        );
1477        check_range_u16(
1478            ts_section_heading.weight,
1479            100,
1480            900,
1481            "text_scale.section_heading.weight",
1482            &mut missing,
1483        );
1484        check_positive(
1485            ts_dialog_title.size,
1486            "text_scale.dialog_title.size",
1487            &mut missing,
1488        );
1489        check_positive(
1490            ts_dialog_title.line_height,
1491            "text_scale.dialog_title.line_height",
1492            &mut missing,
1493        );
1494        check_range_u16(
1495            ts_dialog_title.weight,
1496            100,
1497            900,
1498            "text_scale.dialog_title.weight",
1499            &mut missing,
1500        );
1501        check_positive(ts_display.size, "text_scale.display.size", &mut missing);
1502        check_positive(
1503            ts_display.line_height,
1504            "text_scale.display.line_height",
1505            &mut missing,
1506        );
1507        check_range_u16(
1508            ts_display.weight,
1509            100,
1510            900,
1511            "text_scale.display.weight",
1512            &mut missing,
1513        );
1514
1515        // window font: size > 0, weight 100..=900
1516        check_positive(
1517            window_title_bar_font.size,
1518            "window.title_bar_font.size",
1519            &mut missing,
1520        );
1521        check_range_u16(
1522            window_title_bar_font.weight,
1523            100,
1524            900,
1525            "window.title_bar_font.weight",
1526            &mut missing,
1527        );
1528
1529        // button: geometry >= 0, opacity 0..=1, font size > 0, font weight 100..=900
1530        check_non_negative(button_min_width, "button.min_width", &mut missing);
1531        check_non_negative(button_min_height, "button.min_height", &mut missing);
1532        check_non_negative(button_icon_spacing, "button.icon_text_gap", &mut missing);
1533        check_range_f32(
1534            button_disabled_opacity,
1535            0.0,
1536            1.0,
1537            "button.disabled_opacity",
1538            &mut missing,
1539        );
1540        check_positive(button_font.size, "button.font.size", &mut missing);
1541        check_range_u16(
1542            button_font.weight,
1543            100,
1544            900,
1545            "button.font.weight",
1546            &mut missing,
1547        );
1548
1549        // input: geometry >= 0, opacity 0..=1, font size > 0, font weight 100..=900
1550        check_non_negative(input_min_height, "input.min_height", &mut missing);
1551        check_range_f32(
1552            input_disabled_opacity,
1553            0.0,
1554            1.0,
1555            "input.disabled_opacity",
1556            &mut missing,
1557        );
1558        check_positive(input_font.size, "input.font.size", &mut missing);
1559        check_range_u16(
1560            input_font.weight,
1561            100,
1562            900,
1563            "input.font.weight",
1564            &mut missing,
1565        );
1566
1567        // checkbox: geometry >= 0, opacity 0..=1, font size > 0, font weight 100..=900
1568        check_non_negative(
1569            checkbox_indicator_size,
1570            "checkbox.indicator_width",
1571            &mut missing,
1572        );
1573        check_non_negative(checkbox_spacing, "checkbox.label_gap", &mut missing);
1574        check_range_f32(
1575            checkbox_disabled_opacity,
1576            0.0,
1577            1.0,
1578            "checkbox.disabled_opacity",
1579            &mut missing,
1580        );
1581        check_positive(checkbox_font.size, "checkbox.font.size", &mut missing);
1582        check_range_u16(
1583            checkbox_font.weight,
1584            100,
1585            900,
1586            "checkbox.font.weight",
1587            &mut missing,
1588        );
1589
1590        // menu: geometry >= 0, font size > 0, font weight 100..=900
1591        check_non_negative(menu_item_height, "menu.row_height", &mut missing);
1592        check_non_negative(menu_icon_spacing, "menu.icon_text_gap", &mut missing);
1593        check_non_negative(menu_icon_size, "menu.icon_size", &mut missing);
1594        check_positive(menu_font.size, "menu.font.size", &mut missing);
1595        check_range_u16(menu_font.weight, 100, 900, "menu.font.weight", &mut missing);
1596
1597        // tooltip: geometry >= 0, font size > 0, font weight 100..=900
1598        check_non_negative(tooltip_max_width, "tooltip.max_width", &mut missing);
1599        check_positive(tooltip_font.size, "tooltip.font.size", &mut missing);
1600        check_range_u16(
1601            tooltip_font.weight,
1602            100,
1603            900,
1604            "tooltip.font.weight",
1605            &mut missing,
1606        );
1607
1608        // scrollbar: geometry >= 0
1609        check_non_negative(scrollbar_width, "scrollbar.groove_width", &mut missing);
1610        check_non_negative(
1611            scrollbar_min_thumb_height,
1612            "scrollbar.min_thumb_length",
1613            &mut missing,
1614        );
1615        check_non_negative(
1616            scrollbar_slider_width,
1617            "scrollbar.thumb_width",
1618            &mut missing,
1619        );
1620
1621        // slider: geometry >= 0, opacity 0..=1
1622        check_non_negative(slider_track_height, "slider.track_height", &mut missing);
1623        check_non_negative(slider_thumb_size, "slider.thumb_diameter", &mut missing);
1624        check_non_negative(slider_tick_length, "slider.tick_mark_length", &mut missing);
1625        check_range_f32(
1626            slider_disabled_opacity,
1627            0.0,
1628            1.0,
1629            "slider.disabled_opacity",
1630            &mut missing,
1631        );
1632
1633        // progress_bar: geometry >= 0
1634        check_non_negative(
1635            progress_bar_height,
1636            "progress_bar.track_height",
1637            &mut missing,
1638        );
1639        check_non_negative(
1640            progress_bar_min_width,
1641            "progress_bar.min_width",
1642            &mut missing,
1643        );
1644        // progress_bar.border.corner_radius check handled by border spec validation
1645
1646        // tab: geometry >= 0, font size > 0, font weight 100..=900
1647        check_non_negative(tab_min_width, "tab.min_width", &mut missing);
1648        check_non_negative(tab_min_height, "tab.min_height", &mut missing);
1649        check_positive(tab_font.size, "tab.font.size", &mut missing);
1650        check_range_u16(tab_font.weight, 100, 900, "tab.font.weight", &mut missing);
1651
1652        // sidebar: font size > 0, font weight 100..=900
1653        check_positive(sidebar_font.size, "sidebar.font.size", &mut missing);
1654        check_range_u16(
1655            sidebar_font.weight,
1656            100,
1657            900,
1658            "sidebar.font.weight",
1659            &mut missing,
1660        );
1661
1662        // toolbar: geometry >= 0, font size > 0, font weight 100..=900
1663        check_non_negative(toolbar_height, "toolbar.bar_height", &mut missing);
1664        check_non_negative(toolbar_item_spacing, "toolbar.item_gap", &mut missing);
1665        check_non_negative(toolbar_icon_size, "toolbar.icon_size", &mut missing);
1666        check_positive(toolbar_font.size, "toolbar.font.size", &mut missing);
1667        check_range_u16(
1668            toolbar_font.weight,
1669            100,
1670            900,
1671            "toolbar.font.weight",
1672            &mut missing,
1673        );
1674
1675        // status_bar: font size > 0, font weight 100..=900
1676        check_positive(status_bar_font.size, "status_bar.font.size", &mut missing);
1677        check_range_u16(
1678            status_bar_font.weight,
1679            100,
1680            900,
1681            "status_bar.font.weight",
1682            &mut missing,
1683        );
1684
1685        // list: geometry >= 0, font size > 0, font weight 100..=900
1686        check_non_negative(list_item_height, "list.row_height", &mut missing);
1687        check_positive(list_item_font.size, "list.item_font.size", &mut missing);
1688        check_range_u16(
1689            list_item_font.weight,
1690            100,
1691            900,
1692            "list.item_font.weight",
1693            &mut missing,
1694        );
1695        check_positive(list_header_font.size, "list.header_font.size", &mut missing);
1696        check_range_u16(
1697            list_header_font.weight,
1698            100,
1699            900,
1700            "list.header_font.weight",
1701            &mut missing,
1702        );
1703
1704        // popover: font size > 0, font weight 100..=900
1705        check_positive(popover_font.size, "popover.font.size", &mut missing);
1706        check_range_u16(
1707            popover_font.weight,
1708            100,
1709            900,
1710            "popover.font.weight",
1711            &mut missing,
1712        );
1713
1714        // splitter: width >= 0
1715        check_non_negative(splitter_width, "splitter.divider_width", &mut missing);
1716
1717        // separator: line_width >= 0
1718        check_non_negative(separator_line_width, "separator.line_width", &mut missing);
1719
1720        // switch: geometry >= 0, opacity 0..=1
1721        check_non_negative(switch_track_width, "switch.track_width", &mut missing);
1722        check_non_negative(switch_track_height, "switch.track_height", &mut missing);
1723        check_non_negative(switch_thumb_size, "switch.thumb_diameter", &mut missing);
1724        check_non_negative(switch_track_radius, "switch.track_radius", &mut missing);
1725        check_range_f32(
1726            switch_disabled_opacity,
1727            0.0,
1728            1.0,
1729            "switch.disabled_opacity",
1730            &mut missing,
1731        );
1732
1733        // dialog: geometry >= 0, font size > 0, font weight 100..=900
1734        check_non_negative(dialog_min_width, "dialog.min_width", &mut missing);
1735        check_non_negative(dialog_max_width, "dialog.max_width", &mut missing);
1736        check_non_negative(dialog_min_height, "dialog.min_height", &mut missing);
1737        check_non_negative(dialog_max_height, "dialog.max_height", &mut missing);
1738        check_non_negative(dialog_button_spacing, "dialog.button_gap", &mut missing);
1739        check_non_negative(dialog_icon_size, "dialog.icon_size", &mut missing);
1740        check_positive(
1741            dialog_title_font.size,
1742            "dialog.title_font.size",
1743            &mut missing,
1744        );
1745        check_range_u16(
1746            dialog_title_font.weight,
1747            100,
1748            900,
1749            "dialog.title_font.weight",
1750            &mut missing,
1751        );
1752
1753        // dialog: body_font size > 0, weight 100..=900
1754        check_positive(dialog_body_font.size, "dialog.body_font.size", &mut missing);
1755        check_range_u16(
1756            dialog_body_font.weight,
1757            100,
1758            900,
1759            "dialog.body_font.weight",
1760            &mut missing,
1761        );
1762
1763        // dialog: cross-field min/max constraints
1764        check_min_max(
1765            dialog_min_width,
1766            dialog_max_width,
1767            "dialog.min_width",
1768            "dialog.max_width",
1769            &mut missing,
1770        );
1771        check_min_max(
1772            dialog_min_height,
1773            dialog_max_height,
1774            "dialog.min_height",
1775            "dialog.max_height",
1776            &mut missing,
1777        );
1778
1779        // spinner: geometry >= 0
1780        check_non_negative(spinner_diameter, "spinner.diameter", &mut missing);
1781        check_non_negative(spinner_min_size, "spinner.min_diameter", &mut missing);
1782        check_non_negative(spinner_stroke_width, "spinner.stroke_width", &mut missing);
1783
1784        // link: font size > 0, font weight 100..=900
1785        check_positive(link_font.size, "link.font.size", &mut missing);
1786        check_range_u16(link_font.weight, 100, 900, "link.font.weight", &mut missing);
1787
1788        // combo_box: geometry >= 0, opacity 0..=1, font size > 0, font weight 100..=900
1789        check_non_negative(combo_box_min_height, "combo_box.min_height", &mut missing);
1790        check_non_negative(combo_box_min_width, "combo_box.min_width", &mut missing);
1791        check_non_negative(
1792            combo_box_arrow_size,
1793            "combo_box.arrow_icon_size",
1794            &mut missing,
1795        );
1796        check_non_negative(
1797            combo_box_arrow_area_width,
1798            "combo_box.arrow_area_width",
1799            &mut missing,
1800        );
1801        check_range_f32(
1802            combo_box_disabled_opacity,
1803            0.0,
1804            1.0,
1805            "combo_box.disabled_opacity",
1806            &mut missing,
1807        );
1808        check_positive(combo_box_font.size, "combo_box.font.size", &mut missing);
1809        check_range_u16(
1810            combo_box_font.weight,
1811            100,
1812            900,
1813            "combo_box.font.weight",
1814            &mut missing,
1815        );
1816
1817        // segmented_control: geometry >= 0, opacity 0..=1, font size > 0, font weight 100..=900
1818        check_non_negative(
1819            segmented_control_segment_height,
1820            "segmented_control.segment_height",
1821            &mut missing,
1822        );
1823        check_non_negative(
1824            segmented_control_separator_width,
1825            "segmented_control.separator_width",
1826            &mut missing,
1827        );
1828        check_range_f32(
1829            segmented_control_disabled_opacity,
1830            0.0,
1831            1.0,
1832            "segmented_control.disabled_opacity",
1833            &mut missing,
1834        );
1835        check_positive(
1836            segmented_control_font.size,
1837            "segmented_control.font.size",
1838            &mut missing,
1839        );
1840        check_range_u16(
1841            segmented_control_font.weight,
1842            100,
1843            900,
1844            "segmented_control.font.weight",
1845            &mut missing,
1846        );
1847
1848        // expander: geometry >= 0, font size > 0, font weight 100..=900
1849        check_non_negative(
1850            expander_header_height,
1851            "expander.header_height",
1852            &mut missing,
1853        );
1854        check_non_negative(
1855            expander_arrow_size,
1856            "expander.arrow_icon_size",
1857            &mut missing,
1858        );
1859        check_positive(expander_font.size, "expander.font.size", &mut missing);
1860        check_range_u16(
1861            expander_font.weight,
1862            100,
1863            900,
1864            "expander.font.weight",
1865            &mut missing,
1866        );
1867
1868        // NEW WIDGET: add range checks above this line.
1869
1870        // --- check for missing fields and range errors ---
1871
1872        if !missing.is_empty() {
1873            return Err(crate::Error::Resolution(ThemeResolutionError {
1874                missing_fields: missing,
1875            }));
1876        }
1877
1878        // All fields present -- construct ResolvedThemeVariant.
1879        // require() returns T directly (using T::default() as placeholder for missing),
1880        // so no unwrap() is needed. The defaults are never used: we returned Err above.
1881
1882        Ok(ResolvedThemeVariant {
1883            defaults: ResolvedThemeDefaults {
1884                font: defaults_font,
1885                line_height: defaults_line_height,
1886                mono_font: defaults_mono_font,
1887                background_color: defaults_background,
1888                text_color: defaults_foreground,
1889                accent_color: defaults_accent,
1890                accent_text_color: defaults_accent_foreground,
1891                surface_color: defaults_surface,
1892                border: ResolvedBorderSpec {
1893                    color: defaults_border,
1894                    corner_radius: defaults_radius,
1895                    corner_radius_lg: defaults_radius_lg,
1896                    line_width: defaults_frame_width,
1897                    opacity: defaults_border_opacity,
1898                    shadow_enabled: defaults_shadow_enabled,
1899                    padding_horizontal: defaults_border_padding_h,
1900                    padding_vertical: defaults_border_padding_v,
1901                },
1902                muted_color: defaults_muted,
1903                shadow_color: defaults_shadow,
1904                link_color: defaults_link,
1905                selection_background: defaults_selection,
1906                selection_text_color: defaults_selection_foreground,
1907                selection_inactive_background: defaults_selection_inactive,
1908                text_selection_background: defaults_text_selection_background,
1909                text_selection_color: defaults_text_selection_color,
1910                disabled_text_color: defaults_disabled_foreground,
1911                danger_color: defaults_danger,
1912                danger_text_color: defaults_danger_foreground,
1913                warning_color: defaults_warning,
1914                warning_text_color: defaults_warning_foreground,
1915                success_color: defaults_success,
1916                success_text_color: defaults_success_foreground,
1917                info_color: defaults_info,
1918                info_text_color: defaults_info_foreground,
1919                disabled_opacity: defaults_disabled_opacity,
1920                focus_ring_color: defaults_focus_ring_color,
1921                focus_ring_width: defaults_focus_ring_width,
1922                focus_ring_offset: defaults_focus_ring_offset,
1923                icon_sizes: ResolvedIconSizes {
1924                    toolbar: defaults_icon_sizes_toolbar,
1925                    small: defaults_icon_sizes_small,
1926                    large: defaults_icon_sizes_large,
1927                    dialog: defaults_icon_sizes_dialog,
1928                    panel: defaults_icon_sizes_panel,
1929                },
1930                font_dpi: defaults_font_dpi,
1931                text_scaling_factor: defaults_text_scaling_factor,
1932                reduce_motion: defaults_reduce_motion,
1933                high_contrast: defaults_high_contrast,
1934                reduce_transparency: defaults_reduce_transparency,
1935            },
1936            text_scale: ResolvedTextScale {
1937                caption: ts_caption,
1938                section_heading: ts_section_heading,
1939                dialog_title: ts_dialog_title,
1940                display: ts_display,
1941            },
1942            window: crate::model::widgets::ResolvedWindowTheme {
1943                background_color: window_background,
1944                title_bar_background: window_title_bar_background,
1945                inactive_title_bar_background: window_inactive_title_bar_background,
1946                inactive_title_bar_text_color: window_inactive_title_bar_foreground,
1947                title_bar_font: window_title_bar_font,
1948                border: window_border_spec,
1949            },
1950            button: crate::model::widgets::ResolvedButtonTheme {
1951                background_color: button_background,
1952                primary_background: button_primary_background,
1953                primary_text_color: button_primary_foreground,
1954                min_width: button_min_width,
1955                min_height: button_min_height,
1956                icon_text_gap: button_icon_spacing,
1957                disabled_opacity: button_disabled_opacity,
1958                hover_background: button_hover_background,
1959                hover_text_color: button_hover_text_color,
1960                active_text_color: button_active_text_color,
1961                disabled_text_color: button_disabled_text_color,
1962                // soft_option fields (Option in Resolved, no require):
1963                active_background: self.button.active_background,
1964                disabled_background: self.button.disabled_background,
1965                font: button_font,
1966                border: button_border_spec,
1967            },
1968            input: crate::model::widgets::ResolvedInputTheme {
1969                background_color: input_background,
1970                placeholder_color: input_placeholder,
1971                caret_color: input_caret,
1972                selection_background: input_selection,
1973                selection_text_color: input_selection_foreground,
1974                min_height: input_min_height,
1975                disabled_opacity: input_disabled_opacity,
1976                disabled_text_color: input_disabled_text_color,
1977                // soft_option fields (Option in Resolved, no require):
1978                hover_border_color: self.input.hover_border_color,
1979                focus_border_color: self.input.focus_border_color,
1980                disabled_background: self.input.disabled_background,
1981                font: input_font,
1982                border: input_border_spec,
1983            },
1984            checkbox: crate::model::widgets::ResolvedCheckboxTheme {
1985                background_color: checkbox_background_color,
1986                checked_background: checkbox_checked_background,
1987                indicator_color: checkbox_indicator_color,
1988                indicator_width: checkbox_indicator_size,
1989                label_gap: checkbox_spacing,
1990                disabled_opacity: checkbox_disabled_opacity,
1991                disabled_text_color: checkbox_disabled_text_color,
1992                // soft_option fields (Option in Resolved, no require):
1993                hover_background: self.checkbox.hover_background,
1994                disabled_background: self.checkbox.disabled_background,
1995                unchecked_background: self.checkbox.unchecked_background,
1996                unchecked_border_color: self.checkbox.unchecked_border_color,
1997                font: checkbox_font,
1998                border: checkbox_border_spec,
1999            },
2000            menu: crate::model::widgets::ResolvedMenuTheme {
2001                background_color: menu_background,
2002                separator_color: menu_separator,
2003                row_height: menu_item_height,
2004                icon_text_gap: menu_icon_spacing,
2005                icon_size: menu_icon_size,
2006                hover_background: menu_hover_background,
2007                hover_text_color: menu_hover_text_color,
2008                disabled_text_color: menu_disabled_text_color,
2009                font: menu_font,
2010                border: menu_border_spec,
2011            },
2012            tooltip: crate::model::widgets::ResolvedTooltipTheme {
2013                background_color: tooltip_background,
2014                max_width: tooltip_max_width,
2015                font: tooltip_font,
2016                border: tooltip_border_spec,
2017            },
2018            scrollbar: crate::model::widgets::ResolvedScrollbarTheme {
2019                track_color: scrollbar_track,
2020                thumb_color: scrollbar_thumb,
2021                thumb_hover_color: scrollbar_thumb_hover,
2022                groove_width: scrollbar_width,
2023                min_thumb_length: scrollbar_min_thumb_height,
2024                thumb_width: scrollbar_slider_width,
2025                overlay_mode: scrollbar_overlay_mode,
2026                // soft_option field (Option in Resolved, no require):
2027                thumb_active_color: self.scrollbar.thumb_active_color,
2028            },
2029            slider: crate::model::widgets::ResolvedSliderTheme {
2030                fill_color: slider_fill,
2031                track_color: slider_track,
2032                thumb_color: slider_thumb,
2033                track_height: slider_track_height,
2034                thumb_diameter: slider_thumb_size,
2035                tick_mark_length: slider_tick_length,
2036                disabled_opacity: slider_disabled_opacity,
2037                // soft_option fields (Option in Resolved, no require):
2038                thumb_hover_color: self.slider.thumb_hover_color,
2039                disabled_fill_color: self.slider.disabled_fill_color,
2040                disabled_track_color: self.slider.disabled_track_color,
2041                disabled_thumb_color: self.slider.disabled_thumb_color,
2042            },
2043            progress_bar: crate::model::widgets::ResolvedProgressBarTheme {
2044                fill_color: progress_bar_fill,
2045                track_color: progress_bar_track,
2046                track_height: progress_bar_height,
2047                min_width: progress_bar_min_width,
2048                border: progress_bar_border_spec,
2049            },
2050            tab: crate::model::widgets::ResolvedTabTheme {
2051                background_color: tab_background,
2052                active_background: tab_active_background,
2053                active_text_color: tab_active_foreground,
2054                bar_background: tab_bar_background,
2055                min_width: tab_min_width,
2056                min_height: tab_min_height,
2057                hover_text_color: tab_hover_text_color,
2058                hover_background: self.tab.hover_background,
2059                font: tab_font,
2060                border: tab_border_spec,
2061            },
2062            sidebar: crate::model::widgets::ResolvedSidebarTheme {
2063                background_color: sidebar_background,
2064                selection_background: sidebar_selection_background,
2065                selection_text_color: sidebar_selection_text_color,
2066                hover_background: sidebar_hover_background,
2067                font: sidebar_font,
2068                border: sidebar_border_spec,
2069            },
2070            toolbar: crate::model::widgets::ResolvedToolbarTheme {
2071                background_color: toolbar_background_color,
2072                bar_height: toolbar_height,
2073                item_gap: toolbar_item_spacing,
2074                icon_size: toolbar_icon_size,
2075                font: toolbar_font,
2076                border: toolbar_border_spec,
2077            },
2078            status_bar: crate::model::widgets::ResolvedStatusBarTheme {
2079                background_color: status_bar_background_color,
2080                font: status_bar_font,
2081                border: status_bar_border_spec,
2082            },
2083            list: crate::model::widgets::ResolvedListTheme {
2084                background_color: list_background,
2085                alternate_row_background: list_alternate_row,
2086                selection_background: list_selection,
2087                selection_text_color: list_selection_foreground,
2088                header_background: list_header_background,
2089                grid_color: list_grid_color,
2090                row_height: list_item_height,
2091                hover_background: list_hover_background,
2092                hover_text_color: list_hover_text_color,
2093                disabled_text_color: list_disabled_text_color,
2094                item_font: list_item_font,
2095                header_font: list_header_font,
2096                border: list_border_spec,
2097            },
2098            popover: crate::model::widgets::ResolvedPopoverTheme {
2099                background_color: popover_background,
2100                font: popover_font,
2101                border: popover_border_spec,
2102            },
2103            splitter: crate::model::widgets::ResolvedSplitterTheme {
2104                divider_width: splitter_width,
2105                divider_color: splitter_divider_color,
2106                hover_color: splitter_hover_color,
2107            },
2108            separator: crate::model::widgets::ResolvedSeparatorTheme {
2109                line_color: separator_color,
2110                line_width: separator_line_width,
2111            },
2112            switch: crate::model::widgets::ResolvedSwitchTheme {
2113                checked_background: switch_checked_background,
2114                unchecked_background: switch_unchecked_background,
2115                thumb_background: switch_thumb_background,
2116                track_width: switch_track_width,
2117                track_height: switch_track_height,
2118                thumb_diameter: switch_thumb_size,
2119                track_radius: switch_track_radius,
2120                disabled_opacity: switch_disabled_opacity,
2121                hover_checked_background: self.switch.hover_checked_background,
2122                hover_unchecked_background: self.switch.hover_unchecked_background,
2123                disabled_checked_background: self.switch.disabled_checked_background,
2124                disabled_unchecked_background: self.switch.disabled_unchecked_background,
2125                disabled_thumb_color: self.switch.disabled_thumb_color,
2126            },
2127            dialog: crate::model::widgets::ResolvedDialogTheme {
2128                background_color: dialog_background_color,
2129                min_width: dialog_min_width,
2130                max_width: dialog_max_width,
2131                min_height: dialog_min_height,
2132                max_height: dialog_max_height,
2133                button_gap: dialog_button_spacing,
2134                icon_size: dialog_icon_size,
2135                button_order: dialog_button_order,
2136                title_font: dialog_title_font,
2137                body_font: dialog_body_font,
2138                border: dialog_border_spec,
2139            },
2140            spinner: crate::model::widgets::ResolvedSpinnerTheme {
2141                fill_color: spinner_fill,
2142                diameter: spinner_diameter,
2143                min_diameter: spinner_min_size,
2144                stroke_width: spinner_stroke_width,
2145            },
2146            combo_box: crate::model::widgets::ResolvedComboBoxTheme {
2147                background_color: combo_box_background_color,
2148                min_height: combo_box_min_height,
2149                min_width: combo_box_min_width,
2150                arrow_icon_size: combo_box_arrow_size,
2151                arrow_area_width: combo_box_arrow_area_width,
2152                disabled_opacity: combo_box_disabled_opacity,
2153                disabled_text_color: combo_box_disabled_text_color,
2154                hover_background: self.combo_box.hover_background,
2155                disabled_background: self.combo_box.disabled_background,
2156                font: combo_box_font,
2157                border: combo_box_border_spec,
2158            },
2159            segmented_control: crate::model::widgets::ResolvedSegmentedControlTheme {
2160                background_color: segmented_control_background_color,
2161                active_background: segmented_control_active_background,
2162                active_text_color: segmented_control_active_text_color,
2163                segment_height: segmented_control_segment_height,
2164                separator_width: segmented_control_separator_width,
2165                disabled_opacity: segmented_control_disabled_opacity,
2166                hover_background: self.segmented_control.hover_background,
2167                font: segmented_control_font,
2168                border: segmented_control_border_spec,
2169            },
2170            card: crate::model::widgets::ResolvedCardTheme {
2171                background_color: card_background,
2172                border: card_border_spec,
2173            },
2174            expander: crate::model::widgets::ResolvedExpanderTheme {
2175                header_height: expander_header_height,
2176                arrow_icon_size: expander_arrow_size,
2177                hover_background: self.expander.hover_background,
2178                arrow_color: self.expander.arrow_color,
2179                font: expander_font,
2180                border: expander_border_spec,
2181            },
2182            link: crate::model::widgets::ResolvedLinkTheme {
2183                visited_text_color: link_visited,
2184                underline_enabled: link_underline,
2185                background_color: link_background,
2186                hover_background: link_hover_bg,
2187                hover_text_color: link_hover_text_color,
2188                active_text_color: link_active_text_color,
2189                disabled_text_color: link_disabled_text_color,
2190                font: link_font,
2191            },
2192            icon_set,
2193            icon_theme,
2194        })
2195    }
2196}