Skip to main content

teksilo_core/styles/
component_style_slots.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Typed `Rc<dyn FooStyle>` slot bag — the theme-wide override channel
5//! for the four-tier styling system.
6//!
7//! Each themable widget reads its slot like:
8//!
9//! ```ignore
10//! let style = self.style_override
11//!     .clone()
12//!     .or_else(|| ctx.theme().style_slots.button.clone())
13//!     .unwrap_or_else(|| Rc::new(RecipeButtonStyle::default()));
14//! ```
15//!
16//! Per-call `.style(...)` overrides win first; theme-wide
17//! `style_slots.button = Some(...)` wins second; the widget's local
18//! `Recipe*Style` default is the fallback.
19//!
20//! `Option` per slot rather than a populated default because the
21//! `Recipe*Style` types live in `teksilo-widgets` and can't be imported by
22//! `teksilo-core` (cycle). Apps that want theme-wide custom styles
23//! install them explicitly:
24//!
25//! ```ignore
26//! let mut theme = intui::light();
27//! theme.style_slots.button = Some(Rc::new(MyGlassButton));
28//! ```
29
30use crate::styles::{
31    SharedAvatarStyle, SharedBadgeStyle, SharedBannerStyle, SharedButtonStyle, SharedCalendarStyle,
32    SharedCardStyle, SharedChartStyle, SharedCheckboxStyle, SharedColorPickerStyle,
33    SharedComboBoxStyle, SharedDateEditStyle, SharedDialogStyle, SharedDropTargetStyle,
34    SharedDropZoneStyle, SharedGridViewStyle, SharedIconButtonStyle, SharedLinkStyle,
35    SharedListContainerStyle, SharedMenuItemStyle, SharedPanelStyle, SharedPopoverStyle,
36    SharedProgressBarStyle, SharedRadioStyle, SharedRadioTileStyle, SharedRichTextEditorStyle,
37    SharedScrollBarStyle, SharedSearchFieldStyle, SharedSegmentedControlStyle, SharedSliderStyle,
38    SharedSnackbarStyle, SharedSpinBoxStyle, SharedSplitButtonStyle, SharedSplitterStyle,
39    SharedStandardItemStyle, SharedTabStyle, SharedTableStyle, SharedTextInputStyle,
40    SharedToastStyle, SharedToggleStyle, SharedTooltipStyle, SharedWebViewStyle,
41};
42
43/// Typed slot bag living on [`crate::styles::Theme`]. One slot per
44/// themable widget. `None` means "use the widget's local default
45/// `Recipe*Style`"; `Some(rc)` installs the override theme-wide.
46#[derive(Default, Clone)]
47pub struct ComponentStyleSlots {
48    pub button: Option<SharedButtonStyle>,
49    pub split_button: Option<SharedSplitButtonStyle>,
50    pub splitter: Option<SharedSplitterStyle>,
51    pub icon_button: Option<SharedIconButtonStyle>,
52    pub toggle: Option<SharedToggleStyle>,
53    pub checkbox: Option<SharedCheckboxStyle>,
54    pub radio: Option<SharedRadioStyle>,
55    pub radio_tile: Option<SharedRadioTileStyle>,
56    pub slider: Option<SharedSliderStyle>,
57    pub text_input: Option<SharedTextInputStyle>,
58    pub combo_box: Option<SharedComboBoxStyle>,
59    pub menu_item: Option<SharedMenuItemStyle>,
60    pub panel: Option<SharedPanelStyle>,
61    pub card: Option<SharedCardStyle>,
62    pub chart: Option<SharedChartStyle>,
63    pub popover: Option<SharedPopoverStyle>,
64    pub tooltip: Option<SharedTooltipStyle>,
65    pub scroll_bar: Option<SharedScrollBarStyle>,
66    pub standard_item: Option<SharedStandardItemStyle>,
67    pub tab: Option<SharedTabStyle>,
68    pub dialog: Option<SharedDialogStyle>,
69    pub snackbar: Option<SharedSnackbarStyle>,
70    pub toast: Option<SharedToastStyle>,
71    pub banner: Option<SharedBannerStyle>,
72    pub badge: Option<SharedBadgeStyle>,
73    pub progress_bar: Option<SharedProgressBarStyle>,
74    pub link: Option<SharedLinkStyle>,
75    pub segmented_control: Option<SharedSegmentedControlStyle>,
76    pub avatar: Option<SharedAvatarStyle>,
77    pub calendar: Option<SharedCalendarStyle>,
78    pub color_picker: Option<SharedColorPickerStyle>,
79    pub spin_box: Option<SharedSpinBoxStyle>,
80    pub date_edit: Option<SharedDateEditStyle>,
81    pub search_field: Option<SharedSearchFieldStyle>,
82    pub rich_text_editor: Option<SharedRichTextEditorStyle>,
83    pub table: Option<SharedTableStyle>,
84    pub list_container: Option<SharedListContainerStyle>,
85    pub drop_zone: Option<SharedDropZoneStyle>,
86    pub drop_target: Option<SharedDropTargetStyle>,
87    pub grid_view: Option<SharedGridViewStyle>,
88    pub web_view: Option<SharedWebViewStyle>,
89}
90
91impl std::fmt::Debug for ComponentStyleSlots {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        // Hand-rolled because `Rc<dyn FooStyle>` doesn't impl Debug.
94        // Show which slots are populated (Some/None) — the actual
95        // chrome behaviour isn't introspectable.
96        f.debug_struct("ComponentStyleSlots")
97            .field("button", &self.button.is_some())
98            .field("split_button", &self.split_button.is_some())
99            .field("splitter", &self.splitter.is_some())
100            .field("icon_button", &self.icon_button.is_some())
101            .field("toggle", &self.toggle.is_some())
102            .field("checkbox", &self.checkbox.is_some())
103            .field("radio", &self.radio.is_some())
104            .field("radio_tile", &self.radio_tile.is_some())
105            .field("slider", &self.slider.is_some())
106            .field("text_input", &self.text_input.is_some())
107            .field("combo_box", &self.combo_box.is_some())
108            .field("menu_item", &self.menu_item.is_some())
109            .field("panel", &self.panel.is_some())
110            .field("card", &self.card.is_some())
111            .field("chart", &self.chart.is_some())
112            .field("popover", &self.popover.is_some())
113            .field("tooltip", &self.tooltip.is_some())
114            .field("scroll_bar", &self.scroll_bar.is_some())
115            .field("standard_item", &self.standard_item.is_some())
116            .field("tab", &self.tab.is_some())
117            .field("dialog", &self.dialog.is_some())
118            .field("snackbar", &self.snackbar.is_some())
119            .field("toast", &self.toast.is_some())
120            .field("banner", &self.banner.is_some())
121            .field("badge", &self.badge.is_some())
122            .field("progress_bar", &self.progress_bar.is_some())
123            .field("link", &self.link.is_some())
124            .field("segmented_control", &self.segmented_control.is_some())
125            .field("avatar", &self.avatar.is_some())
126            .field("calendar", &self.calendar.is_some())
127            .field("color_picker", &self.color_picker.is_some())
128            .field("spin_box", &self.spin_box.is_some())
129            .field("date_edit", &self.date_edit.is_some())
130            .field("search_field", &self.search_field.is_some())
131            .field("rich_text_editor", &self.rich_text_editor.is_some())
132            .field("table", &self.table.is_some())
133            .field("list_container", &self.list_container.is_some())
134            .field("drop_zone", &self.drop_zone.is_some())
135            .field("drop_target", &self.drop_target.is_some())
136            .field("grid_view", &self.grid_view.is_some())
137            .field("web_view", &self.web_view.is_some())
138            .finish()
139    }
140}
141
142impl PartialEq for ComponentStyleSlots {
143    fn eq(&self, other: &Self) -> bool {
144        // Rc trait-object pointer-equality is the only meaningful "are
145        // these the same style" check. Used for theme-equality (mostly
146        // tests + cache keys).
147        fn rc_eq<T: ?Sized>(a: &Option<std::rc::Rc<T>>, b: &Option<std::rc::Rc<T>>) -> bool {
148            match (a, b) {
149                (None, None) => true,
150                (Some(x), Some(y)) => std::rc::Rc::ptr_eq(x, y),
151                _ => false,
152            }
153        }
154        rc_eq(&self.button, &other.button)
155            && rc_eq(&self.radio_tile, &other.radio_tile)
156            && rc_eq(&self.split_button, &other.split_button)
157            && rc_eq(&self.splitter, &other.splitter)
158            && rc_eq(&self.icon_button, &other.icon_button)
159            && rc_eq(&self.toggle, &other.toggle)
160            && rc_eq(&self.checkbox, &other.checkbox)
161            && rc_eq(&self.radio, &other.radio)
162            && rc_eq(&self.slider, &other.slider)
163            && rc_eq(&self.text_input, &other.text_input)
164            && rc_eq(&self.combo_box, &other.combo_box)
165            && rc_eq(&self.menu_item, &other.menu_item)
166            && rc_eq(&self.panel, &other.panel)
167            && rc_eq(&self.card, &other.card)
168            && rc_eq(&self.chart, &other.chart)
169            && rc_eq(&self.popover, &other.popover)
170            && rc_eq(&self.tooltip, &other.tooltip)
171            && rc_eq(&self.scroll_bar, &other.scroll_bar)
172            && rc_eq(&self.standard_item, &other.standard_item)
173            && rc_eq(&self.tab, &other.tab)
174            && rc_eq(&self.dialog, &other.dialog)
175            && rc_eq(&self.snackbar, &other.snackbar)
176            && rc_eq(&self.toast, &other.toast)
177            && rc_eq(&self.banner, &other.banner)
178            && rc_eq(&self.badge, &other.badge)
179            && rc_eq(&self.progress_bar, &other.progress_bar)
180            && rc_eq(&self.link, &other.link)
181            && rc_eq(&self.segmented_control, &other.segmented_control)
182            && rc_eq(&self.avatar, &other.avatar)
183            && rc_eq(&self.calendar, &other.calendar)
184            && rc_eq(&self.color_picker, &other.color_picker)
185            && rc_eq(&self.spin_box, &other.spin_box)
186            && rc_eq(&self.date_edit, &other.date_edit)
187            && rc_eq(&self.search_field, &other.search_field)
188            && rc_eq(&self.rich_text_editor, &other.rich_text_editor)
189            && rc_eq(&self.table, &other.table)
190            && rc_eq(&self.list_container, &other.list_container)
191            && rc_eq(&self.drop_zone, &other.drop_zone)
192            && rc_eq(&self.drop_target, &other.drop_target)
193            && rc_eq(&self.grid_view, &other.grid_view)
194            && rc_eq(&self.web_view, &other.web_view)
195    }
196}