Skip to main content

graphix_package_gui/
theme.rs

1use iced_core::Color;
2use iced_widget::{
3    button, checkbox, combo_box, container, markdown, overlay::menu, pick_list,
4    progress_bar, qr_code, radio, rule, scrollable, slider, svg, table, text_editor,
5    text_input, toggler,
6};
7use triomphe::Arc;
8
9/// Wrapper around `iced_core::Theme` that supports per-widget style overrides.
10///
11/// When `overrides` is `None`, all Catalog impls delegate directly to the
12/// inner theme — behavior is identical to using `iced_core::Theme` directly.
13/// When `overrides` is `Some`, each widget checks for a user-specified style
14/// before falling back to the inner theme's built-in Catalog.
15#[derive(Clone, Debug)]
16pub struct GraphixTheme {
17    pub inner: iced_core::Theme,
18    pub overrides: Option<Arc<StyleOverrides>>,
19}
20
21impl GraphixTheme {
22    pub fn palette(&self) -> iced_core::theme::palette::Palette {
23        self.inner.palette()
24    }
25}
26
27#[derive(Clone, Debug)]
28pub struct StyleOverrides {
29    pub button: Option<ButtonSpec>,
30    pub checkbox: Option<CheckboxSpec>,
31    pub container: Option<ContainerSpec>,
32    pub menu: Option<MenuSpec>,
33    pub pick_list: Option<PickListSpec>,
34    pub progress_bar: Option<ProgressBarSpec>,
35    pub radio: Option<RadioSpec>,
36    pub rule: Option<RuleSpec>,
37    pub scrollable: Option<ScrollableSpec>,
38    pub slider: Option<SliderSpec>,
39    pub text_editor: Option<TextEditorSpec>,
40    pub text_input: Option<TextInputSpec>,
41    pub toggler: Option<TogglerSpec>,
42}
43
44// --- Spec structs ---
45
46#[derive(Clone, Copy, Debug)]
47pub struct ButtonSpec {
48    pub background: Option<Color>,
49    pub border_color: Option<Color>,
50    pub border_radius: Option<f32>,
51    pub border_width: Option<f32>,
52    pub text_color: Option<Color>,
53}
54
55#[derive(Clone, Copy, Debug)]
56pub struct CheckboxSpec {
57    pub accent: Option<Color>,
58    pub background: Option<Color>,
59    pub border_color: Option<Color>,
60    pub border_radius: Option<f32>,
61    pub border_width: Option<f32>,
62    pub icon_color: Option<Color>,
63    pub text_color: Option<Color>,
64}
65
66#[derive(Clone, Copy, Debug)]
67pub struct TextInputSpec {
68    pub background: Option<Color>,
69    pub border_color: Option<Color>,
70    pub border_radius: Option<f32>,
71    pub border_width: Option<f32>,
72    pub icon_color: Option<Color>,
73    pub placeholder_color: Option<Color>,
74    pub selection_color: Option<Color>,
75    pub value_color: Option<Color>,
76}
77
78#[derive(Clone, Copy, Debug)]
79pub struct TogglerSpec {
80    pub background: Option<Color>,
81    pub background_border_color: Option<Color>,
82    pub border_radius: Option<f32>,
83    pub foreground: Option<Color>,
84    pub foreground_border_color: Option<Color>,
85    pub text_color: Option<Color>,
86}
87
88#[derive(Clone, Copy, Debug)]
89pub struct SliderSpec {
90    pub handle_border_color: Option<Color>,
91    pub handle_border_width: Option<f32>,
92    pub handle_color: Option<Color>,
93    pub handle_radius: Option<f32>,
94    pub rail_color: Option<Color>,
95    pub rail_fill_color: Option<Color>,
96    pub rail_width: Option<f32>,
97}
98
99#[derive(Clone, Copy, Debug)]
100pub struct RadioSpec {
101    pub background: Option<Color>,
102    pub border_color: Option<Color>,
103    pub border_width: Option<f32>,
104    pub dot_color: Option<Color>,
105    pub text_color: Option<Color>,
106}
107
108#[derive(Clone, Copy, Debug)]
109pub struct PickListSpec {
110    pub background: Option<Color>,
111    pub border_color: Option<Color>,
112    pub border_radius: Option<f32>,
113    pub border_width: Option<f32>,
114    pub handle_color: Option<Color>,
115    pub placeholder_color: Option<Color>,
116    pub text_color: Option<Color>,
117}
118
119#[derive(Clone, Copy, Debug)]
120pub struct TextEditorSpec {
121    pub background: Option<Color>,
122    pub border_color: Option<Color>,
123    pub border_radius: Option<f32>,
124    pub border_width: Option<f32>,
125    pub placeholder_color: Option<Color>,
126    pub selection_color: Option<Color>,
127    pub value_color: Option<Color>,
128}
129
130#[derive(Clone, Copy, Debug)]
131pub struct ContainerSpec {
132    pub background: Option<Color>,
133    pub border_color: Option<Color>,
134    pub border_radius: Option<f32>,
135    pub border_width: Option<f32>,
136    pub text_color: Option<Color>,
137}
138
139#[derive(Clone, Copy, Debug)]
140pub struct ScrollableSpec {
141    pub background: Option<Color>,
142    pub border_color: Option<Color>,
143    pub border_radius: Option<f32>,
144    pub border_width: Option<f32>,
145    pub scroller_color: Option<Color>,
146}
147
148#[derive(Clone, Copy, Debug)]
149pub struct ProgressBarSpec {
150    pub background: Option<Color>,
151    pub bar_color: Option<Color>,
152    pub border_radius: Option<f32>,
153}
154
155#[derive(Clone, Copy, Debug)]
156pub struct RuleSpec {
157    pub color: Option<Color>,
158    pub radius: Option<f32>,
159    pub width: Option<f32>,
160}
161
162#[derive(Clone, Copy, Debug)]
163pub struct MenuSpec {
164    pub background: Option<Color>,
165    pub border_color: Option<Color>,
166    pub border_radius: Option<f32>,
167    pub border_width: Option<f32>,
168    pub selected_background: Option<Color>,
169    pub selected_text_color: Option<Color>,
170    pub text_color: Option<Color>,
171}
172
173// --- Color adjustment helpers ---
174
175fn hover_adjust(color: Color, is_dark: bool) -> Color {
176    if is_dark {
177        Color::from_rgba(
178            (color.r + 0.15).min(1.0),
179            (color.g + 0.15).min(1.0),
180            (color.b + 0.15).min(1.0),
181            color.a,
182        )
183    } else {
184        Color::from_rgba(
185            (color.r - 0.10).max(0.0),
186            (color.g - 0.10).max(0.0),
187            (color.b - 0.10).max(0.0),
188            color.a,
189        )
190    }
191}
192
193fn dim(color: Color) -> Color {
194    Color::from_rgba(color.r, color.g, color.b, color.a * 0.5)
195}
196
197// --- Resolve methods (overlay pattern) ---
198//
199// Each resolve starts from iced's complete default style for the given
200// theme + status, then selectively overrides only user-specified fields.
201
202impl ButtonSpec {
203    fn resolve(&self, theme: &iced_core::Theme, status: button::Status) -> button::Style {
204        let is_dark = theme.extended_palette().is_dark;
205        let mut s = button::primary(theme, status);
206        if let Some(bg) = self.background {
207            let bg = match status {
208                button::Status::Hovered => hover_adjust(bg, is_dark),
209                button::Status::Disabled => dim(bg),
210                _ => bg,
211            };
212            s.background = Some(bg.into());
213        }
214        if let Some(tc) = self.text_color {
215            s.text_color =
216                if matches!(status, button::Status::Disabled) { dim(tc) } else { tc };
217        }
218        if let Some(bc) = self.border_color {
219            s.border.color = bc;
220        }
221        if let Some(bw) = self.border_width {
222            s.border.width = bw;
223        }
224        if let Some(br) = self.border_radius {
225            s.border.radius = br.into();
226        }
227        s
228    }
229}
230
231impl CheckboxSpec {
232    fn resolve(
233        &self,
234        theme: &iced_core::Theme,
235        status: checkbox::Status,
236    ) -> checkbox::Style {
237        let is_dark = theme.extended_palette().is_dark;
238        let mut s = checkbox::primary(theme, status);
239        let is_disabled = matches!(status, checkbox::Status::Disabled { .. });
240        let is_hovered = matches!(status, checkbox::Status::Hovered { .. });
241        let is_checked = match status {
242            checkbox::Status::Active { is_checked }
243            | checkbox::Status::Hovered { is_checked }
244            | checkbox::Status::Disabled { is_checked } => is_checked,
245        };
246        if let Some(accent) = self.accent {
247            if let Some(bg) = self.background {
248                let c = if is_checked { accent } else { bg };
249                let c = if is_disabled {
250                    dim(c)
251                } else if is_hovered {
252                    hover_adjust(c, is_dark)
253                } else {
254                    c
255                };
256                s.background = c.into();
257            } else {
258                // only accent specified
259                if is_checked {
260                    let c = if is_disabled {
261                        dim(accent)
262                    } else if is_hovered {
263                        hover_adjust(accent, is_dark)
264                    } else {
265                        accent
266                    };
267                    s.background = c.into();
268                }
269            }
270        } else if let Some(bg) = self.background {
271            if !is_checked {
272                let c = if is_disabled {
273                    dim(bg)
274                } else if is_hovered {
275                    hover_adjust(bg, is_dark)
276                } else {
277                    bg
278                };
279                s.background = c.into();
280            }
281        }
282        if let Some(ic) = self.icon_color {
283            s.icon_color = if is_disabled { dim(ic) } else { ic };
284        }
285        if let Some(tc) = self.text_color {
286            s.text_color = Some(if is_disabled { dim(tc) } else { tc });
287        }
288        if let Some(bc) = self.border_color {
289            s.border.color = if is_disabled { dim(bc) } else { bc };
290        }
291        if let Some(bw) = self.border_width {
292            s.border.width = bw;
293        }
294        if let Some(br) = self.border_radius {
295            s.border.radius = br.into();
296        }
297        s
298    }
299}
300
301impl TextInputSpec {
302    fn resolve(
303        &self,
304        theme: &iced_core::Theme,
305        status: text_input::Status,
306    ) -> text_input::Style {
307        let is_dark = theme.extended_palette().is_dark;
308        let mut s = text_input::default(theme, status);
309        if let Some(bg) = self.background {
310            s.background = match status {
311                text_input::Status::Active => bg,
312                text_input::Status::Hovered | text_input::Status::Focused { .. } => {
313                    hover_adjust(bg, is_dark)
314                }
315                text_input::Status::Disabled => dim(bg),
316            }
317            .into();
318        }
319        if let Some(bc) = self.border_color {
320            s.border.color = bc;
321        }
322        if let Some(bw) = self.border_width {
323            s.border.width = bw;
324        }
325        if let Some(br) = self.border_radius {
326            s.border.radius = br.into();
327        }
328        if let Some(ic) = self.icon_color {
329            s.icon = ic;
330        }
331        if let Some(pc) = self.placeholder_color {
332            s.placeholder = pc;
333        }
334        if let Some(vc) = self.value_color {
335            s.value = vc;
336        }
337        if let Some(sc) = self.selection_color {
338            s.selection = sc;
339        }
340        s
341    }
342}
343
344impl TogglerSpec {
345    fn resolve(
346        &self,
347        theme: &iced_core::Theme,
348        status: toggler::Status,
349    ) -> toggler::Style {
350        let is_dark = theme.extended_palette().is_dark;
351        let mut s = toggler::default(theme, status);
352        let is_hovered = matches!(status, toggler::Status::Hovered { .. });
353        let is_disabled = matches!(status, toggler::Status::Disabled { .. });
354        if let Some(bg) = self.background {
355            s.background = if is_disabled {
356                dim(bg)
357            } else if is_hovered {
358                hover_adjust(bg, is_dark)
359            } else {
360                bg
361            }
362            .into();
363        }
364        if let Some(bbc) = self.background_border_color {
365            s.background_border_color = if is_disabled { dim(bbc) } else { bbc };
366        }
367        if let Some(fg) = self.foreground {
368            s.foreground = if is_disabled { dim(fg) } else { fg }.into();
369        }
370        if let Some(fbc) = self.foreground_border_color {
371            s.foreground_border_color = if is_disabled { dim(fbc) } else { fbc };
372        }
373        if let Some(tc) = self.text_color {
374            s.text_color = Some(if is_disabled { dim(tc) } else { tc });
375        }
376        if let Some(br) = self.border_radius {
377            s.border_radius = Some(br.into());
378        }
379        s
380    }
381}
382
383impl SliderSpec {
384    fn resolve(&self, theme: &iced_core::Theme, status: slider::Status) -> slider::Style {
385        let is_dark = theme.extended_palette().is_dark;
386        let mut s = slider::default(theme, status);
387        let is_hovered = matches!(status, slider::Status::Hovered);
388        if let Some(rfc) = self.rail_fill_color {
389            s.rail.backgrounds.0 = rfc.into();
390        }
391        if let Some(rc) = self.rail_color {
392            s.rail.backgrounds.1 = rc.into();
393        }
394        if let Some(rw) = self.rail_width {
395            s.rail.width = rw;
396        }
397        if let Some(hc) = self.handle_color {
398            let hc = if is_hovered { hover_adjust(hc, is_dark) } else { hc };
399            s.handle.background = hc.into();
400        }
401        if let Some(hr) = self.handle_radius {
402            s.handle.shape = slider::HandleShape::Circle { radius: hr };
403        }
404        if let Some(hbw) = self.handle_border_width {
405            s.handle.border_width = hbw;
406        }
407        if let Some(hbc) = self.handle_border_color {
408            s.handle.border_color = hbc;
409        }
410        s
411    }
412}
413
414impl RadioSpec {
415    fn resolve(&self, theme: &iced_core::Theme, status: radio::Status) -> radio::Style {
416        let is_dark = theme.extended_palette().is_dark;
417        let mut s = radio::default(theme, status);
418        let is_hovered = matches!(status, radio::Status::Hovered { .. });
419        if let Some(bg) = self.background {
420            s.background = if is_hovered { hover_adjust(bg, is_dark) } else { bg }.into();
421        }
422        if let Some(dc) = self.dot_color {
423            s.dot_color = dc;
424        }
425        if let Some(bw) = self.border_width {
426            s.border_width = bw;
427        }
428        if let Some(bc) = self.border_color {
429            s.border_color = bc;
430        }
431        if let Some(tc) = self.text_color {
432            s.text_color = Some(tc);
433        }
434        s
435    }
436}
437
438impl PickListSpec {
439    fn resolve(
440        &self,
441        theme: &iced_core::Theme,
442        status: pick_list::Status,
443    ) -> pick_list::Style {
444        let is_dark = theme.extended_palette().is_dark;
445        let mut s = pick_list::default(theme, status);
446        let is_hovered = matches!(
447            status,
448            pick_list::Status::Hovered | pick_list::Status::Opened { .. }
449        );
450        if let Some(bg) = self.background {
451            s.background = if is_hovered { hover_adjust(bg, is_dark) } else { bg }.into();
452        }
453        if let Some(tc) = self.text_color {
454            s.text_color = tc;
455        }
456        if let Some(pc) = self.placeholder_color {
457            s.placeholder_color = pc;
458        }
459        if let Some(hc) = self.handle_color {
460            s.handle_color = hc;
461        }
462        if let Some(bc) = self.border_color {
463            s.border.color = bc;
464        }
465        if let Some(bw) = self.border_width {
466            s.border.width = bw;
467        }
468        if let Some(br) = self.border_radius {
469            s.border.radius = br.into();
470        }
471        s
472    }
473}
474
475impl TextEditorSpec {
476    fn resolve(
477        &self,
478        theme: &iced_core::Theme,
479        status: text_editor::Status,
480    ) -> text_editor::Style {
481        let is_dark = theme.extended_palette().is_dark;
482        let mut s = text_editor::default(theme, status);
483        if let Some(bg) = self.background {
484            s.background = match status {
485                text_editor::Status::Active => bg,
486                text_editor::Status::Hovered | text_editor::Status::Focused { .. } => {
487                    hover_adjust(bg, is_dark)
488                }
489                text_editor::Status::Disabled => dim(bg),
490            }
491            .into();
492        }
493        if let Some(bc) = self.border_color {
494            s.border.color = bc;
495        }
496        if let Some(bw) = self.border_width {
497            s.border.width = bw;
498        }
499        if let Some(br) = self.border_radius {
500            s.border.radius = br.into();
501        }
502        if let Some(pc) = self.placeholder_color {
503            s.placeholder = pc;
504        }
505        if let Some(vc) = self.value_color {
506            s.value = vc;
507        }
508        if let Some(sc) = self.selection_color {
509            s.selection = sc;
510        }
511        s
512    }
513}
514
515impl ContainerSpec {
516    fn resolve(&self, theme: &iced_core::Theme) -> container::Style {
517        let mut s = container::transparent(theme);
518        if let Some(bg) = self.background {
519            s.background = Some(bg.into());
520        }
521        if let Some(tc) = self.text_color {
522            s.text_color = Some(tc);
523        }
524        if let Some(bc) = self.border_color {
525            s.border.color = bc;
526        }
527        if let Some(bw) = self.border_width {
528            s.border.width = bw;
529        }
530        if let Some(br) = self.border_radius {
531            s.border.radius = br.into();
532        }
533        s
534    }
535}
536
537impl ScrollableSpec {
538    fn resolve(
539        &self,
540        theme: &iced_core::Theme,
541        status: scrollable::Status,
542    ) -> scrollable::Style {
543        let is_dark = theme.extended_palette().is_dark;
544        let mut s = scrollable::default(theme, status);
545        // Apply to both rails symmetrically
546        for rail in [&mut s.vertical_rail, &mut s.horizontal_rail] {
547            if let Some(bg) = self.background {
548                rail.background = Some(bg.into());
549            }
550            if let Some(bc) = self.border_color {
551                rail.border.color = bc;
552            }
553            if let Some(bw) = self.border_width {
554                rail.border.width = bw;
555            }
556            if let Some(br) = self.border_radius {
557                rail.border.radius = br.into();
558            }
559            if let Some(sc) = self.scroller_color {
560                let is_hovered = matches!(status, scrollable::Status::Hovered { .. });
561                let sc = if is_hovered { hover_adjust(sc, is_dark) } else { sc };
562                rail.scroller.background = sc.into();
563            }
564        }
565        s
566    }
567}
568
569impl ProgressBarSpec {
570    fn resolve(&self, theme: &iced_core::Theme) -> progress_bar::Style {
571        let mut s = progress_bar::primary(theme);
572        if let Some(bg) = self.background {
573            s.background = bg.into();
574        }
575        if let Some(bar) = self.bar_color {
576            s.bar = bar.into();
577        }
578        if let Some(br) = self.border_radius {
579            s.border.radius = br.into();
580        }
581        s
582    }
583}
584
585impl RuleSpec {
586    fn resolve(&self, theme: &iced_core::Theme) -> rule::Style {
587        let mut s = rule::default(theme);
588        if let Some(c) = self.color {
589            s.color = c;
590        }
591        if let Some(r) = self.radius {
592            s.radius = r.into();
593        }
594        if let Some(w) = self.width {
595            s.fill_mode = rule::FillMode::Percent(w);
596        }
597        s
598    }
599}
600
601impl MenuSpec {
602    fn resolve(&self, theme: &iced_core::Theme) -> menu::Style {
603        let mut s = menu::default(theme);
604        if let Some(bg) = self.background {
605            s.background = bg.into();
606        }
607        if let Some(bc) = self.border_color {
608            s.border.color = bc;
609        }
610        if let Some(bw) = self.border_width {
611            s.border.width = bw;
612        }
613        if let Some(br) = self.border_radius {
614            s.border.radius = br.into();
615        }
616        if let Some(tc) = self.text_color {
617            s.text_color = tc;
618        }
619        if let Some(stc) = self.selected_text_color {
620            s.selected_text_color = stc;
621        }
622        if let Some(sb) = self.selected_background {
623            s.selected_background = sb.into();
624        }
625        s
626    }
627}
628
629// --- Catalog trait implementations ---
630
631// Macros to reduce boilerplate for the common Catalog patterns.
632
633macro_rules! impl_catalog_with_status {
634    ($module:ident, $field:ident, $fallback:expr) => {
635        impl $module::Catalog for GraphixTheme {
636            type Class<'a> = $module::StyleFn<'a, Self>;
637
638            fn default<'a>() -> Self::Class<'a> {
639                Box::new(|theme, status| {
640                    if let Some(spec) =
641                        theme.overrides.as_ref().and_then(|o| o.$field.as_ref())
642                    {
643                        spec.resolve(&theme.inner, status)
644                    } else {
645                        #[allow(clippy::redundant_closure_call)]
646                        ($fallback)(&theme.inner, status)
647                    }
648                })
649            }
650
651            fn style(
652                &self,
653                class: &Self::Class<'_>,
654                status: $module::Status,
655            ) -> $module::Style {
656                class(self, status)
657            }
658        }
659    };
660}
661
662macro_rules! impl_catalog_no_status {
663    ($module:ident, $field:ident, $fallback:expr) => {
664        impl $module::Catalog for GraphixTheme {
665            type Class<'a> = $module::StyleFn<'a, Self>;
666
667            fn default<'a>() -> Self::Class<'a> {
668                Box::new(|theme| {
669                    if let Some(spec) =
670                        theme.overrides.as_ref().and_then(|o| o.$field.as_ref())
671                    {
672                        spec.resolve(&theme.inner)
673                    } else {
674                        #[allow(clippy::redundant_closure_call)]
675                        ($fallback)(&theme.inner)
676                    }
677                })
678            }
679
680            fn style(&self, class: &Self::Class<'_>) -> $module::Style {
681                class(self)
682            }
683        }
684    };
685}
686
687impl_catalog_with_status!(button, button, button::primary);
688impl_catalog_with_status!(checkbox, checkbox, checkbox::primary);
689impl_catalog_with_status!(text_input, text_input, text_input::default);
690impl_catalog_with_status!(toggler, toggler, toggler::default);
691impl_catalog_with_status!(slider, slider, slider::default);
692impl_catalog_with_status!(radio, radio, radio::default);
693// pick_list: needs fully qualified paths due to supertrait ambiguity
694impl pick_list::Catalog for GraphixTheme {
695    type Class<'a> = pick_list::StyleFn<'a, Self>;
696
697    fn default<'a>() -> <Self as pick_list::Catalog>::Class<'a> {
698        Box::new(|theme, status| {
699            if let Some(spec) =
700                theme.overrides.as_ref().and_then(|o| o.pick_list.as_ref())
701            {
702                spec.resolve(&theme.inner, status)
703            } else {
704                pick_list::default(&theme.inner, status)
705            }
706        })
707    }
708
709    fn style(
710        &self,
711        class: &<Self as pick_list::Catalog>::Class<'_>,
712        status: pick_list::Status,
713    ) -> pick_list::Style {
714        class(self, status)
715    }
716}
717impl_catalog_with_status!(text_editor, text_editor, text_editor::default);
718
719impl scrollable::Catalog for GraphixTheme {
720    type Class<'a> = scrollable::StyleFn<'a, Self>;
721
722    fn default<'a>() -> Self::Class<'a> {
723        Box::new(|theme, status| {
724            if let Some(spec) =
725                theme.overrides.as_ref().and_then(|o| o.scrollable.as_ref())
726            {
727                spec.resolve(&theme.inner, status)
728            } else {
729                scrollable::default(&theme.inner, status)
730            }
731        })
732    }
733
734    fn style(
735        &self,
736        class: &Self::Class<'_>,
737        status: scrollable::Status,
738    ) -> scrollable::Style {
739        class(self, status)
740    }
741}
742
743impl_catalog_no_status!(container, container, container::transparent);
744impl_catalog_no_status!(progress_bar, progress_bar, progress_bar::primary);
745impl_catalog_no_status!(rule, rule, rule::default);
746
747// Menu: needs fully qualified paths due to supertrait ambiguity with scrollable
748impl menu::Catalog for GraphixTheme {
749    type Class<'a> = menu::StyleFn<'a, Self>;
750
751    fn default<'a>() -> <Self as menu::Catalog>::Class<'a> {
752        Box::new(|theme| {
753            if let Some(spec) = theme.overrides.as_ref().and_then(|o| o.menu.as_ref()) {
754                spec.resolve(&theme.inner)
755            } else {
756                menu::default(&theme.inner)
757            }
758        })
759    }
760
761    fn style(&self, class: &<Self as menu::Catalog>::Class<'_>) -> menu::Style {
762        class(self)
763    }
764}
765
766// ComboBox: supertrait of text_input + menu; empty impl uses defaults
767impl combo_box::Catalog for GraphixTheme {}
768
769// Delegate-only: text
770impl iced_core::widget::text::Catalog for GraphixTheme {
771    type Class<'a> = iced_core::widget::text::StyleFn<'a, Self>;
772
773    fn default<'a>() -> Self::Class<'a> {
774        Box::new(|_theme| iced_core::widget::text::Style::default())
775    }
776
777    fn style(&self, class: &Self::Class<'_>) -> iced_core::widget::text::Style {
778        class(self)
779    }
780}
781
782// Delegate-only: svg
783impl svg::Catalog for GraphixTheme {
784    type Class<'a> = svg::StyleFn<'a, Self>;
785
786    fn default<'a>() -> Self::Class<'a> {
787        Box::new(|_theme, _status| svg::Style::default())
788    }
789
790    fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style {
791        class(self, status)
792    }
793}
794
795// table: delegate to inner theme
796impl table::Catalog for GraphixTheme {
797    type Class<'a> = table::StyleFn<'a, Self>;
798
799    fn default<'a>() -> Self::Class<'a> {
800        Box::new(|theme| table::default(&theme.inner))
801    }
802
803    fn style(&self, class: &Self::Class<'_>) -> table::Style {
804        class(self)
805    }
806}
807
808// qr_code: delegate to inner theme
809impl qr_code::Catalog for GraphixTheme {
810    type Class<'a> = qr_code::StyleFn<'a, Self>;
811
812    fn default<'a>() -> Self::Class<'a> {
813        Box::new(|theme| qr_code::default(&theme.inner))
814    }
815
816    fn style(&self, class: &Self::Class<'_>) -> qr_code::Style {
817        class(self)
818    }
819}
820
821// markdown: supertrait of container + scrollable + text + rule + checkbox + table
822impl markdown::Catalog for GraphixTheme {
823    fn code_block<'a>() -> <Self as container::Catalog>::Class<'a> {
824        Box::new(|theme| container::dark(&theme.inner))
825    }
826}
827
828// theme::Base — required supertrait for text_editor::Catalog
829impl iced_core::theme::Base for GraphixTheme {
830    fn default(preference: iced_core::theme::Mode) -> Self {
831        GraphixTheme {
832            inner: iced_core::theme::Base::default(preference),
833            overrides: None,
834        }
835    }
836
837    fn mode(&self) -> iced_core::theme::Mode {
838        self.inner.mode()
839    }
840
841    fn base(&self) -> iced_core::theme::Style {
842        self.inner.base()
843    }
844
845    fn palette(&self) -> Option<iced_core::theme::palette::Palette> {
846        iced_core::theme::Base::palette(&self.inner)
847    }
848
849    fn name(&self) -> &str {
850        self.inner.name()
851    }
852}