Skip to main content

frus_widgets/
widgettheme.rs

1//! **Per-widget defaults**: the middle term of `what the caller said ?? what the theme
2//! says ?? what the framework ships`.
3//!
4//! Three milestones in a row ended on the same missing piece. An application could
5//! override one card's elevation, one divider's height, one splash's colour — and had
6//! no way to say *every card in this application*, short of writing its own wrapper
7//! around each widget. The reference resolves nearly every property through exactly
8//! this chain, and only the middle term was absent here.
9//!
10//! Every field is an `Option`, and `None` means "the framework's own default" — so a
11//! theme that sets nothing behaves exactly as no theme at all, and a theme that sets one
12//! field changes one thing.
13//!
14//! ```ignore
15//! let mut theme = Theme::dark();
16//! theme.widgets.card.elevation = Some(0.0);       // a flat application
17//! theme.widgets.divider.height = Some(1.0);       // hairlines, flush
18//! theme.widgets.ink.color = Some(accent.fade(0.2));
19//! ```
20//!
21//! **Layout properties are included** — a divider's height, a card's margin, a drawer's
22//! width — which is why [`Widget::style_themed`](crate::Widget::style_themed) exists
23//! beside `style`. A theme that could only reach paint would be able to recolour a
24//! divider but not make one thin, which is the setting an application actually wants.
25
26use frus_core::{BorderRadius, Color, Insets, ShapeBorder, TextAlign, TextOverflow, TextStyle};
27
28use crate::card::CardVariant;
29
30/// **The shape a box takes**, on the rungs everything here is resolved on
31/// (`card.dart`, `button_style.dart`, and every other `shape` in the reference).
32///
33/// The caller's word first, then the theme's shape, then the theme's plain **radius** read
34/// as a rounded rectangle, then the widget's own default.
35///
36/// The third rung is the one worth explaining. A radius was the only thing a theme could
37/// say until milestone 450, and applications have written them; a theme that names a
38/// `shape` outranks one that names only a `radius`, and naming both is naming the shape.
39pub fn resolve_shape(
40    own: Option<ShapeBorder>,
41    themed: Option<ShapeBorder>,
42    radius: Option<BorderRadius>,
43    fallback: ShapeBorder,
44) -> ShapeBorder {
45    own.or(themed)
46        .or_else(|| radius.map(ShapeBorder::rounded))
47        .unwrap_or(fallback)
48}
49
50/// The per-widget defaults carried by a [`Theme`](crate::Theme).
51///
52/// Adding a widget here is adding a field: the pattern is one `Option` per builder the
53/// widget already has, resolved in the same order everywhere.
54#[derive(Clone, Debug, Default, PartialEq)]
55pub struct WidgetThemes {
56    /// Defaults for [`Alert`](crate::Alert).
57    pub alert: AlertTheme,
58    /// Defaults for [`Dialog`](crate::Dialog) and [`AlertDialog`](crate::AlertDialog).
59    pub dialog: DialogTheme,
60    /// Which glyphs the four named buttons carry.
61    pub action_icons: ActionIconTheme,
62    /// Defaults for [`AppBar`](crate::AppBar).
63    pub app_bar: AppBarTheme,
64    /// Defaults for [`Autocomplete`](crate::Autocomplete).
65    pub autocomplete: AutocompleteTheme,
66    /// Defaults for [`Badge`](crate::Badge).
67    pub badge: BadgeTheme,
68    /// Defaults for [`MaterialBanner`](crate::MaterialBanner).
69    pub banner: BannerTheme,
70    /// Defaults for [`BottomAppBar`](crate::BottomAppBar).
71    pub bottom_app_bar: BottomAppBarTheme,
72    /// Defaults for [`BottomSheet`](crate::BottomSheet).
73    pub bottom_sheet: BottomSheetTheme,
74    /// Defaults for [`Breadcrumb`](crate::Breadcrumb).
75    pub breadcrumb: BreadcrumbTheme,
76    /// Defaults for [`Button`](crate::Button).
77    pub button: ButtonTheme,
78    /// Defaults for [`Card`](crate::Card).
79    pub card: CardTheme,
80    /// Defaults for [`Checkbox`](crate::Checkbox).
81    pub checkbox: CheckboxTheme,
82    /// Defaults for [`Chip`](crate::Chip).
83    pub chip: ChipTheme,
84    /// Defaults for [`DatePicker`](crate::DatePicker).
85    pub date_picker: DatePickerTheme,
86    /// Defaults for [`Divider`](crate::Divider).
87    pub divider: DividerTheme,
88    /// Defaults for [`Drawer`](crate::Drawer).
89    pub drawer: DrawerTheme,
90    /// Defaults for [`ListTile`](crate::ListTile).
91    pub list_tile: ListTileTheme,
92    /// Defaults for [`DropdownButton`](crate::DropdownButton) and its options.
93    pub dropdown: DropdownTheme,
94    /// Defaults for [`ExpansionTile`](crate::ExpansionTile).
95    pub expansion_tile: ExpansionTileTheme,
96    /// Defaults for [`ExpansionPanelList`](crate::ExpansionPanelList).
97    pub expansion_panel_list: ExpansionPanelListTheme,
98    /// Defaults for [`FloatingActionButton`](crate::FloatingActionButton).
99    pub fab: FabTheme,
100    /// Defaults for a form's error summary, [`ErrorSummary`](crate::ErrorSummary).
101    pub form: FormTheme,
102    /// Defaults for [`GridTileBar`](crate::GridTileBar).
103    pub grid_tile_bar: GridTileBarTheme,
104    /// Defaults for [`Icon`](crate::Icon).
105    pub icon: IconTheme,
106    /// Defaults for [`IconButton`](crate::IconButton).
107    pub icon_button: IconButtonTheme,
108    /// Defaults for the **ink ripple** — every surface that splashes, including [`InkWell`](crate::InkWell) and [`Button`](crate::Button).
109    pub ink: InkTheme,
110    /// Defaults for [`Kanban`](crate::Kanban).
111    pub kanban: KanbanTheme,
112    /// Defaults for [`ReorderableList`](crate::ReorderableList).
113    pub reorderable: ReorderableTheme,
114    /// Defaults for [`Kbd`](crate::Kbd).
115    pub kbd: KbdTheme,
116    /// Defaults for [`PopupMenuButton`](crate::PopupMenuButton) and its items.
117    pub menu: MenuTheme,
118    /// Defaults for [`NavigationBar`](crate::NavigationBar).
119    pub nav_bar: NavBarTheme,
120    /// Defaults for [`NavigationRail`](crate::NavigationRail) and [`BottomBar`](crate::BottomBar).
121    pub nav_rail: NavRailTheme,
122    /// Defaults for [`NavigationDrawer`](crate::NavigationDrawer).
123    pub nav_drawer: NavDrawerTheme,
124    /// Defaults for the **scrollbars** the framework draws over a scrollable.
125    pub scrollbar: ScrollbarTheme,
126    /// Defaults for [`SearchBar`](crate::SearchBar).
127    pub search_bar: SearchBarTheme,
128    /// Defaults for the view a [`SearchAnchor`](crate::SearchAnchor) opens.
129    pub search_view: SearchViewTheme,
130    /// Defaults for the two progress indicators.
131    pub progress: ProgressTheme,
132    /// Defaults for [`RadioGroup`](crate::RadioGroup).
133    pub radio: RadioTheme,
134    /// Defaults for [`SegmentedButton`](crate::SegmentedButton).
135    pub segmented: SegmentedTheme,
136    /// Defaults for [`Slider`](crate::Slider) and [`RangeSlider`](crate::RangeSlider).
137    pub slider: SliderTheme,
138    /// Defaults for [`SnackBar`](crate::SnackBar).
139    pub snack_bar: SnackBarTheme,
140    /// Defaults for [`Steps`](crate::Steps).
141    pub steps: StepsTheme,
142    /// Defaults for [`Switch`](crate::Switch).
143    pub switch: SwitchTheme,
144    /// Defaults for [`TabPageSelector`](crate::TabPageSelector).
145    pub page_selector: PageSelectorTheme,
146    /// Defaults for [`TabBar`](crate::TabBar).
147    pub tab_bar: TabBarTheme,
148    /// Defaults for [`Table`](crate::Table).
149    pub table: TableTheme,
150    /// The text style that text takes when it names none of its own.
151    pub text: DefaultTextStyle,
152    /// Defaults for [`TextField`](crate::TextField).
153    pub text_field: TextFieldTheme,
154    /// Defaults for [`TimePicker`](crate::TimePicker) and [`TimeRange`](crate::TimeRange).
155    pub time_picker: TimePickerTheme,
156    /// Defaults for [`Timeline`](crate::Timeline).
157    pub timeline: TimelineTheme,
158    /// Defaults for [`ToggleButtons`](crate::ToggleButtons).
159    pub toggle_buttons: ToggleButtonsTheme,
160    /// Defaults for [`Tooltip`](crate::Tooltip).
161    pub tooltip: TooltipTheme,
162    /// Defaults for [`Tree`](crate::Tree).
163    pub tree: TreeTheme,
164}
165
166/// Defaults for [`Badge`](crate::Badge).
167///
168/// A badge is an **alert** rather than an accent, which is why its untold fill is the
169/// scheme's `error` and not its `primary`. The two sizes are the reference's: `small_size`
170/// is the diameter of a badge with no label, `large_size` the height of one that carries
171/// a count.
172#[derive(Clone, Copy, Debug, Default, PartialEq)]
173pub struct BadgeTheme {
174    /// The pill's fill.
175    pub background_color: Option<Color>,
176    /// The label's colour on that fill.
177    pub text_color: Option<Color>,
178    /// The label's type.
179    pub text_style: Option<TextStyle>,
180    /// The diameter of a badge with no label.
181    pub small_size: Option<f32>,
182    /// The height of a badge that carries one.
183    pub large_size: Option<f32>,
184    /// The label's room either side.
185    pub padding: Option<f32>,
186}
187
188/// Defaults for [`Checkbox`](crate::Checkbox).
189///
190/// A checkbox is two controls wearing one name: ticked it is a **filled box** with a
191/// mark punched through it, unticked it is an **outline** and nothing else. The colours
192/// do not carry over between the two, which is why they are named apart rather than as
193/// one "active"/"inactive" pair.
194#[derive(Clone, Copy, Debug, Default, PartialEq)]
195pub struct CheckboxTheme {
196    /// The box's fill, ticked.
197    pub fill_color: Option<Color>,
198    /// The tick drawn on that fill.
199    pub check_color: Option<Color>,
200    /// The outline, unticked and at rest.
201    pub border_color: Option<Color>,
202    /// The outline under a pointer, a finger or focus. The reference resolves this side
203    /// per state, and an outline that did not answer would look inert.
204    pub active_border_color: Option<Color>,
205    /// The box's corner radius.
206    pub radius: Option<f32>,
207    /// The label beside it.
208    pub label_color: Option<Color>,
209    /// **How much room it reserves for a finger** — the reference's per-widget say over
210    /// the theme's (`checkbox.dart:512`). Unset, the theme's own answer.
211    pub tap_target: Option<crate::theme::TapTarget>,
212}
213
214/// Defaults for [`RadioGroup`](crate::RadioGroup).
215#[derive(Clone, Copy, Debug, Default, PartialEq)]
216pub struct RadioTheme {
217    /// The ring and the dot of the chosen option.
218    pub selected_color: Option<Color>,
219    /// The ring of an option that is not chosen, at rest.
220    pub border_color: Option<Color>,
221    /// That ring under a pointer, a finger or focus.
222    pub active_border_color: Option<Color>,
223    /// The labels.
224    pub label_color: Option<Color>,
225    /// **How much room it reserves for a finger** — the reference's per-widget say over
226    /// the theme's (`radio.dart:734`). Unset, the theme's own answer.
227    pub tap_target: Option<crate::theme::TapTarget>,
228}
229
230/// Defaults for [`Slider`](crate::Slider) and [`RangeSlider`](crate::RangeSlider).
231#[derive(Clone, Copy, Debug, Default, PartialEq)]
232pub struct SliderTheme {
233    /// The value bubble's type. The reference calls it the *value indicator* and sets it in
234    /// `labelLarge`.
235    pub value_indicator_text_style: Option<TextStyle>,
236    /// The travelled part of the track.
237    pub active_track_color: Option<Color>,
238    /// The part still to travel.
239    pub inactive_track_color: Option<Color>,
240    /// The thumb's fill.
241    pub thumb_color: Option<Color>,
242    /// The ring around the thumb.
243    pub thumb_border_color: Option<Color>,
244    /// How far a thumb sits off the track at rest — a slider's, and each of a range
245    /// slider's. Unset, the reference's 1.
246    pub thumb_elevation: Option<f32>,
247    /// And while it is held. Unset, the reference's 6.
248    pub pressed_thumb_elevation: Option<f32>,
249}
250
251/// Defaults for [`Switch`](crate::Switch).
252#[derive(Clone, Copy, Debug, Default, PartialEq)]
253pub struct SwitchTheme {
254    /// The track, on.
255    pub track_color: Option<Color>,
256    /// The track, off.
257    pub inactive_track_color: Option<Color>,
258    /// The thumb, on.
259    pub thumb_color: Option<Color>,
260    /// The thumb, off. Unset it follows the on colour, which is what the reference does
261    /// and what a switch looks like: one thumb sliding, not two.
262    pub inactive_thumb_color: Option<Color>,
263    /// The glyph inside the thumb, on. Unset, the scheme's `on_primary_container`
264    /// (`switch.dart:2338`).
265    pub icon_color: Option<Color>,
266    /// The glyph inside the thumb, off. Unset, the scheme's `surface_container_highest`
267    /// (`switch.dart:2349`) — the track's own colour, so the glyph reads as a hole in the
268    /// thumb rather than as a mark on it.
269    pub inactive_icon_color: Option<Color>,
270    /// **How much room it reserves for a finger** — the reference's per-widget say over
271    /// the theme's (`switch.dart:603`). Unset, the theme's own answer.
272    pub tap_target: Option<crate::theme::TapTarget>,
273}
274
275/// The text style a **subtree** hands down — the reference's `DefaultTextStyle`, and an
276/// inherited widget there for the reason it is a theme entry here: an app bar, a list
277/// tile or a dialog wants every run of words inside it to read the same way without
278/// reaching into each one, including the ones it never sees because a caller passed them
279/// in already assembled.
280///
281/// It is the reference's shape exactly: a [`TextStyle`] plus the four questions that are
282/// about the **box** rather than the type. It carried its own copy of every typographic
283/// field until `TextStyle` learned to say *unset*; now the style is just a style.
284///
285/// A [`Text`](crate::Text) resolves `what the caller said ?? what this says ?? what the
286/// framework ships`, field by field, and **a default the caller did not choose does not
287/// count as having said something** — which is what an `Option` per field means and what
288/// `TextStyle::new(16.0)` could not express.
289///
290/// [`DefaultTextStyle::around`] wraps a subtree with one, and that is how
291/// [`AppBar::toolbar_text_style`](crate::AppBar::toolbar_text_style) delivers its own.
292#[derive(Clone, Copy, Debug, Default, PartialEq)]
293pub struct DefaultTextStyle {
294    /// The type: size, weight, slant, colour, decoration — each answered or left open.
295    pub style: TextStyle,
296    /// Where the lines sit inside the box.
297    pub align: Option<TextAlign>,
298    /// Whether the text wraps at the width it is given.
299    pub soft_wrap: Option<bool>,
300    /// What becomes of text that does not fit.
301    pub overflow: Option<TextOverflow>,
302    /// At most this many lines.
303    pub max_lines: Option<usize>,
304}
305
306impl DefaultTextStyle {
307    /// Nothing said at all — what a theme carries until something sets a field.
308    pub const NONE: Self = Self {
309        style: TextStyle::NONE,
310        align: None,
311        soft_wrap: None,
312        overflow: None,
313        max_lines: None,
314    };
315
316    /// A handover of type and nothing else.
317    pub const fn from_text_style(style: TextStyle) -> Self {
318        Self {
319            style,
320            ..Self::NONE
321        }
322    }
323
324    /// This style with `over`'s answers laid on top, **field by field**: where `over` said
325    /// nothing, this one's answer survives.
326    ///
327    /// The operation an inherited style needs at every level it passes through — two
328    /// nested subtrees each setting one field must leave a text wearing both.
329    #[must_use]
330    pub fn merge(self, over: Self) -> Self {
331        Self {
332            style: self.style.merge(over.style),
333            align: over.align.or(self.align),
334            soft_wrap: over.soft_wrap.or(self.soft_wrap),
335            overflow: over.overflow.or(self.overflow),
336            max_lines: over.max_lines.or(self.max_lines),
337        }
338    }
339
340    /// Wraps `child` so that every [`Text`](crate::Text) in its subtree wears this style
341    /// where it has not answered for itself — the reference's `DefaultTextStyle` widget.
342    ///
343    /// **Merged onto** whatever an enclosing subtree already handed down rather than
344    /// replacing it, so two nested wrappers each setting one field leave a text wearing
345    /// both:
346    ///
347    /// ```ignore
348    /// DefaultTextStyle::from_text_style(TextStyle::NONE.color(muted))
349    ///     .around(a_column_of_labels)
350    /// ```
351    pub fn around<Msg: 'static>(
352        self,
353        child: impl crate::Widget<Msg> + 'static,
354    ) -> crate::Themed<Msg> {
355        crate::Themed::tweak(move |t| t.widgets.text = t.widgets.text.merge(self), child)
356    }
357}
358
359/// Defaults for [`Icon`](crate::Icon) — the reference's `IconTheme`, which is an
360/// **inherited** widget there for the same reason this is scoped here: an app bar, a
361/// list tile or a button bar wants every glyph inside it one colour without recolouring
362/// the words beside them.
363///
364/// `Themed::tweak` is how a subtree sets it, and that is exactly how
365/// [`AppBar::icon_theme`](crate::AppBar::icon_theme) delivers its own.
366#[derive(Clone, Copy, Debug, Default, PartialEq)]
367pub struct IconTheme {
368    /// The glyph's colour. Unset, the scheme's `on_surface` — an icon is text's peer,
369    /// not a decoration.
370    pub color: Option<Color>,
371    /// The square's side, in logical pixels. Unset, 24: the grid the icons are drawn on.
372    pub size: Option<f32>,
373}
374
375/// Defaults for [`AppBar`](crate::AppBar).
376#[derive(Clone, Copy, Debug, Default, PartialEq)]
377pub struct AppBarTheme {
378    /// Whether the title is centred. Unset, the **platform**'s convention decides, which
379    /// is the one place the framework's last word is a system convention rather than a
380    /// taste of its own.
381    pub center_title: Option<bool>,
382    /// The title's type.
383    pub title_style: Option<TextStyle>,
384    /// The bar's surface.
385    pub background: Option<Color>,
386    /// What is drawn on it — the title's colour.
387    pub foreground: Option<Color>,
388    /// How far off the page the bar sits. Unset, the bar is flat, as the reference's is.
389    pub elevation: Option<f32>,
390    /// The toolbar's height.
391    pub height: Option<f32>,
392    /// The colour of the shadow an elevated bar casts. Unset, **transparent**, as the
393    /// reference's app bar defaults are (line 2542): a shadow is cast only where a colour is
394    /// named.
395    pub shadow_color: Option<Color>,
396    /// The colour laid over the surface in proportion to the elevation — Material 3's
397    /// way of showing height, and the one that still reads on a dark background.
398    pub surface_tint: Option<Color>,
399    /// **What shape the bar is** — the reference's `AppBarTheme.shape`. Unset, square.
400    ///
401    /// It was an `Option<BorderRadius>` under this name until milestone 455: the last
402    /// theme field left carrying the reference's *word* with a corner radius behind it.
403    pub shape: Option<ShapeBorder>,
404}
405
406/// Defaults for [`TextField`](crate::TextField).
407#[derive(Clone, Copy, Debug, Default, PartialEq)]
408pub struct TextFieldTheme {
409    /// The container's fill. A filled field takes the theme's high surface container by
410    /// default; an outlined one has none.
411    pub fill: Option<Color>,
412    /// The border, or the underline, at rest.
413    pub border_color: Option<Color>,
414    /// The border once focused.
415    pub focused_border_color: Option<Color>,
416    /// Border, label and helper colour while an error is showing.
417    pub error_color: Option<Color>,
418    /// The same under the pointer: an errored field **deepens** on hover
419    /// (`input_decorator.dart:5981`). Unset, the scheme's `on_error_container`.
420    pub error_hover_color: Option<Color>,
421    /// The value's colour.
422    pub text_color: Option<Color>,
423    /// The label and the hint, at rest.
424    pub label_color: Option<Color>,
425    /// The label once focused.
426    pub focused_label_color: Option<Color>,
427    /// The helper line.
428    pub helper_color: Option<Color>,
429    /// The prefix and suffix icons.
430    pub icon_color: Option<Color>,
431    /// The handles under a touch selection. Unset, the scheme's `primary`.
432    pub handle_color: Option<Color>,
433    /// Corner radius.
434    pub radius: Option<f32>,
435    /// Border weight at rest.
436    pub border_width: Option<f32>,
437    /// Border weight once focused.
438    pub focused_border_width: Option<f32>,
439}
440
441/// Defaults for [`Button`](crate::Button).
442#[derive(Clone, Copy, Debug, Default, PartialEq)]
443pub struct ButtonTheme {
444    /// **What shape it is** (`shape_border.dart`), over the plain `radius` below:
445    /// a button's, whose default is a **pill** (`button_style.dart`). Unset, the widget decides.
446    pub shape: Option<ShapeBorder>,
447    /// The surface under the label, whatever the variant would have used.
448    pub color: Option<Color>,
449    /// The label's colour.
450    pub label_color: Option<Color>,
451    /// The label's type.
452    pub label_style: Option<TextStyle>,
453    /// The outline's colour.
454    pub border_color: Option<Color>,
455    /// Its thickness; `0.0` removes it.
456    pub border_width: Option<f32>,
457    /// The corner radii. Unset, a button is a stadium.
458    pub radius: Option<BorderRadius>,
459    /// The room either side of the label.
460    pub padding: Option<f32>,
461    /// The button's height.
462    pub height: Option<f32>,
463    /// The narrowest it will be, however short its label.
464    pub min_width: Option<f32>,
465    /// How far it sits off the surface, **in every state**. Unset, the variant's own
466    /// heights, which move with the state — see [`Button::elevation`](crate::Button::elevation).
467    pub elevation: Option<f32>,
468}
469
470/// Defaults for [`Card`](crate::Card).
471#[derive(Clone, Copy, Debug, Default, PartialEq)]
472pub struct CardTheme {
473    /// **What shape it is** (`shape_border.dart`), over the plain `radius` below:
474    /// a card's. Unset, the widget decides.
475    pub shape: Option<ShapeBorder>,
476    /// Which of the three cards an untold `Card::new()` is.
477    pub variant: Option<CardVariant>,
478    /// How far off the surface it sits.
479    pub elevation: Option<f32>,
480    /// Its background, overriding the variant's tone.
481    pub color: Option<Color>,
482    /// Its corner radii.
483    pub radius: Option<BorderRadius>,
484    /// The room it leaves around itself.
485    pub margin: Option<f32>,
486    /// The room it leaves inside itself.
487    pub padding: Option<f32>,
488}
489
490/// Defaults for [`Chip`](crate::Chip).
491#[derive(Clone, Copy, Debug, Default, PartialEq)]
492pub struct ChipTheme {
493    /// **What shape it is** (`shape_border.dart`), over the plain `radius` below:
494    /// a chip's. Unset, the widget decides.
495    pub shape: Option<ShapeBorder>,
496    /// The surface under an unselected chip.
497    pub color: Option<Color>,
498    /// The surface under a selected one.
499    pub selected_color: Option<Color>,
500    /// The label's colour when unselected.
501    pub label_color: Option<Color>,
502    /// The label's colour when selected.
503    pub selected_label_color: Option<Color>,
504    /// The label's type.
505    pub label_style: Option<TextStyle>,
506    /// The outline's colour.
507    pub border_color: Option<Color>,
508    /// Its thickness; `0.0` removes it.
509    pub border_width: Option<f32>,
510    /// The corner radii.
511    pub radius: Option<BorderRadius>,
512    /// The room inside the outline.
513    pub padding: Option<f32>,
514    /// The room on either side of the label.
515    pub label_padding: Option<f32>,
516    /// The chip's height.
517    pub height: Option<f32>,
518    /// The size of a leading icon, a checkmark or a delete cross.
519    pub icon_size: Option<f32>,
520    /// Whether a selected chip shows a checkmark.
521    pub show_checkmark: Option<bool>,
522}
523
524/// Defaults for [`ListTile`](crate::ListTile) — the reference's `ListTileThemeData`.
525///
526/// Every field is what the tile would otherwise decide for itself, on the usual rungs: the
527/// tile's own word, then this, then the framework's. An application that wants all its
528/// tiles rounded, or all its selected rows tinted, says it **once** here rather than on
529/// every tile it ever builds.
530#[derive(Clone, Copy, Debug, Default, PartialEq)]
531pub struct ListTileTheme {
532    /// The tile's surface. Unset, transparent — a tile takes the colour of what it sits on.
533    pub tile_color: Option<Color>,
534    /// The surface while the tile is the chosen one.
535    pub selected_tile_color: Option<Color>,
536    /// The colour its words and slots take while it is the chosen one.
537    pub selected_color: Option<Color>,
538    /// The two slots' icon colour.
539    pub icon_color: Option<Color>,
540    /// The title's and subtitle's colour.
541    pub text_color: Option<Color>,
542    /// What shape the tile is — taken by its surface and by its ink.
543    pub shape: Option<ShapeBorder>,
544    /// The room kept inside the tile, round its content.
545    pub content_padding: Option<Insets>,
546    /// The title's type.
547    pub title_style: Option<TextStyle>,
548    /// The subtitle's.
549    pub subtitle_style: Option<TextStyle>,
550    /// The gap between the slots and the text column.
551    pub title_gap: Option<f32>,
552    /// How much room the leading slot is guaranteed, so the text lines up down a list.
553    pub min_leading_width: Option<f32>,
554    /// The tile's minimum height, over the one its line count asks for.
555    pub min_height: Option<f32>,
556    /// Whether tiles are the tighter kind.
557    pub dense: Option<bool>,
558}
559
560/// Defaults for [`LinearProgressIndicator`](crate::LinearProgressIndicator) and
561/// [`CircularProgressIndicator`](crate::CircularProgressIndicator) — the reference's
562/// `ProgressIndicatorThemeData`.
563///
564/// Neither of them read a theme at all before this, and between them they had **two**
565/// builders: a width and a size. Everything else — the height of the bar, the colour of
566/// its track, the colour of the fill — was decided inside `paint` where nothing could
567/// reach it.
568#[derive(Clone, Copy, Debug, Default, PartialEq)]
569pub struct ProgressTheme {
570    /// The colour of the moving part: the bar's fill and the ring's dots. Unset,
571    /// `primary` (`progress_indicator.dart:1618`).
572    pub color: Option<Color>,
573    /// The colour of the bar's track — the part not yet filled. Unset,
574    /// `secondary_container` (`progress_indicator.dart:1621`).
575    pub linear_track_color: Option<Color>,
576    /// How tall the bar is. Unset, four (`progress_indicator.dart:1624`).
577    pub linear_min_height: Option<f32>,
578    /// A track **behind the ring**, for a determinate circular indicator
579    /// (`progress_indicator.dart:1590`). Unset, none is drawn.
580    pub circular_track_color: Option<Color>,
581    /// The corners of the bar and of its track. Unset, fully rounded.
582    pub border_radius: Option<BorderRadius>,
583    /// The dot at the far end of the track, which says where the bar is going. Unset,
584    /// the same colour as the fill (`progress_indicator.dart:1630`).
585    pub stop_indicator_color: Option<Color>,
586    /// That dot's radius. Unset, two (`progress_indicator.dart:1633`). Zero draws none.
587    pub stop_indicator_radius: Option<f32>,
588    /// The gap left between the end of the fill and the start of the track. Unset, four
589    /// (`progress_indicator.dart:1636`).
590    pub track_gap: Option<f32>,
591    /// How thick the ring's dots are. Unset, the framework's own proportional rule, so
592    /// that a large indicator does not draw a hairline.
593    pub stroke_width: Option<f32>,
594}
595
596/// Defaults for [`Tooltip`](crate::Tooltip) — the reference's `TooltipThemeData`.
597///
598/// A tooltip is the one widget an application is likely to use on *every* icon it draws,
599/// so saying what one looks like on each of them is saying it a hundred times.
600#[derive(Clone, Copy, Debug, Default, PartialEq)]
601pub struct TooltipTheme {
602    /// The bubble's surface. Unset, `inverse_surface` — a tooltip is a label **over**
603    /// the interface, not another panel of it.
604    pub background: Option<Color>,
605    /// The label's type. Unset, `body_small`.
606    pub text_style: Option<TextStyle>,
607    /// The label's colour. Unset, `on_inverse_surface`.
608    pub text_color: Option<Color>,
609    /// Where the lines sit once the label has wrapped. Unset, the start edge, as the
610    /// reference's `_defaultTextAlign` is (`tooltip.dart:398`).
611    pub text_align: Option<TextAlign>,
612    /// The room kept round the label.
613    pub padding: Option<Insets>,
614    /// What shape the bubble is.
615    pub shape: Option<ShapeBorder>,
616    /// The radius of its corners, for a theme that would rather give the number than the
617    /// shape. Outranked by [`shape`](Self::shape).
618    pub radius: Option<f32>,
619    /// How wide the bubble may get before the label wraps.
620    pub max_width: Option<f32>,
621}
622
623/// Defaults for [`ExpansionPanelList`](crate::ExpansionPanelList).
624///
625/// A list of panels is one **surface** that splits, so what it needs said once is the
626/// surface: its tone, its corners, the room an open panel is lifted by, and the hairline
627/// between two shut ones.
628#[derive(Clone, Copy, Debug, Default, PartialEq)]
629pub struct ExpansionPanelListTheme {
630    /// The surface the panels are drawn on. Unset, the scheme's low container — the tone
631    /// a card takes, because that is what a run of shut panels is.
632    pub background: Option<Color>,
633    /// The corner radius of a card: the outer corners of a run, and all four of an open
634    /// panel's.
635    pub radius: Option<f32>,
636    /// The room an open panel is given either side of it, which is what makes it read as
637    /// lifted out of the card rather than merely coloured differently.
638    pub gap: Option<f32>,
639    /// The hairline between two shut panels of one card.
640    pub divider_color: Option<Color>,
641    /// How far the cards sit off the page. Unset, the reference's 2; `0.0` is flat.
642    pub elevation: Option<f32>,
643}
644
645/// Defaults for [`ExpansionTile`](crate::ExpansionTile) — the reference's
646/// `ExpansionTileThemeData`.
647///
648/// The tile already had every one of these as a **builder**; what it had nowhere was a
649/// place to say them **once**. A settings screen is a column of these, and an
650/// application that wants its open sections tinted said so on each of them.
651///
652/// Every colour comes in a pair, because an expansion tile is two things: the row that
653/// is always there, and the row while what it hides is showing.
654#[derive(Clone, Copy, Debug, Default, PartialEq)]
655pub struct ExpansionTileTheme {
656    /// The row's surface while the tile is **open**.
657    pub background: Option<Color>,
658    /// And while it is shut.
659    pub collapsed_background: Option<Color>,
660    /// The title's colour while it is open.
661    pub text_color: Option<Color>,
662    /// And while it is shut.
663    pub collapsed_text_color: Option<Color>,
664    /// The chevron's colour while it is open.
665    pub icon_color: Option<Color>,
666    /// And while it is shut.
667    pub collapsed_icon_color: Option<Color>,
668    /// The room kept inside the row, round its content.
669    pub tile_padding: Option<Insets>,
670    /// The room kept round what the tile hides, while it is showing.
671    pub children_padding: Option<Insets>,
672    /// What shape the row is while the tile is open.
673    pub shape: Option<ShapeBorder>,
674    /// And while it is shut. Deliberately **not** falling back to
675    /// [`shape`](Self::shape): the reference keeps the two apart
676    /// (`expansion_tile_theme.dart:55`), and a tile that is square when shut and rounded
677    /// when open is a design, not a mistake.
678    pub collapsed_shape: Option<ShapeBorder>,
679}
680
681/// Defaults for [`FloatingActionButton`](crate::FloatingActionButton) — the
682/// reference's `FloatingActionButtonThemeData`.
683#[derive(Clone, Copy, Debug, Default, PartialEq)]
684pub struct FabTheme {
685    /// The surface. Unset, `primary_container`
686    /// (`floating_action_button.dart:810`) — **not** `primary`, which is what a filled
687    /// button takes and what this framework's `fab_button` helper was giving it.
688    pub background: Option<Color>,
689    /// What is drawn on it. Unset, `on_primary_container`.
690    pub foreground: Option<Color>,
691    /// How far off the page it sits at rest. Unset, six.
692    pub elevation: Option<f32>,
693    /// And under a pointer. Unset, eight — whatever the resting height is, as the reference's
694    /// build resolves it (line 517).
695    pub hover_elevation: Option<f32>,
696    /// What shape it is. Unset, the corner its size asks for — sixteen, twelve or
697    /// twenty-eight (`floating_action_button.dart:816`).
698    pub shape: Option<ShapeBorder>,
699    /// The radius of its corners, for a theme that would rather give the number than the
700    /// shape. Outranked by [`shape`](Self::shape).
701    pub radius: Option<f32>,
702    /// How big the glyph inside is. Unset, the size's own — twenty-four, or
703    /// thirty-six on a large one.
704    pub icon_size: Option<f32>,
705    /// An extended button's type. Unset, `label_large`.
706    pub label_style: Option<TextStyle>,
707    /// The room either side of an extended button's content.
708    pub extended_padding: Option<f32>,
709    /// The gap between an extended button's glyph and its words. Unset, eight.
710    pub extended_gap: Option<f32>,
711}
712
713/// Which glyphs the framework's four named buttons carry — the reference's
714/// `ActionIconTheme` (`action_icons_theme.dart`).
715///
716/// Unset, each takes the framework's own, and the back arrow takes the platform's: a
717/// chevron where the platform's own back control is a chevron, an arrow everywhere else.
718/// An application with its own icon set should not have to leave four of the framework's
719/// showing through.
720#[derive(Clone, Copy, Debug, Default, PartialEq)]
721pub struct ActionIconTheme {
722    /// The glyph on a [`BackButton`](crate::BackButton).
723    pub back: Option<crate::IconData>,
724    /// On a [`CloseButton`](crate::CloseButton).
725    pub close: Option<crate::IconData>,
726    /// On a [`DrawerButton`](crate::DrawerButton).
727    pub drawer: Option<crate::IconData>,
728    /// On an [`EndDrawerButton`](crate::EndDrawerButton).
729    pub end_drawer: Option<crate::IconData>,
730}
731
732/// Defaults for [`Divider`](crate::Divider).
733#[derive(Clone, Copy, Debug, Default, PartialEq)]
734pub struct DividerTheme {
735    /// The room the separator takes in the layout.
736    pub height: Option<f32>,
737    /// The thickness of the line drawn inside that room.
738    pub thickness: Option<f32>,
739    /// Its colour.
740    pub color: Option<Color>,
741    /// Its inset from the leading edge.
742    pub indent: Option<f32>,
743    /// Its inset from the trailing edge.
744    pub end_indent: Option<f32>,
745}
746
747/// Defaults for [`Drawer`](crate::Drawer).
748///
749/// The rounding is on the **inner** edge only — the one that meets the content — and
750/// the outer edge stays square against the window it is docked to, which is what the
751/// reference's own shape resolves to. Which edge that is follows the direction: the
752/// panel does not know which side of the screen it landed on until it paints.
753#[derive(Clone, Copy, Debug, Default, PartialEq)]
754pub struct DrawerTheme {
755    /// **What shape a leading panel is** — the reference's `DrawerThemeData.shape`
756    /// (`drawer.dart:268`). Unset, the framework rounds the panel's *inner* edge by
757    /// [`DRAWER_RADIUS`](crate::DRAWER_RADIUS).
758    pub shape: Option<ShapeBorder>,
759    /// **And a trailing one** — the reference's `endShape` (`drawer.dart:269`), which is a
760    /// separate field rather than a mirror of the first because a panel's rounded edge is
761    /// the one facing the page, and that is the opposite side.
762    ///
763    /// A trailing panel does **not** fall back to [`shape`](Self::shape): a theme that
764    /// named only the leading panel's shape has said nothing about the other one, and the
765    /// framework's own default is a better answer than a shape rounded on the wrong edge.
766    pub end_shape: Option<ShapeBorder>,
767    /// The panel's width.
768    pub width: Option<f32>,
769    /// The panel's fill.
770    pub background_color: Option<Color>,
771    /// The hairline on the panel's inner edge.
772    pub border_color: Option<Color>,
773    /// That hairline's thickness; `0.0` removes it.
774    pub border_width: Option<f32>,
775    /// The rounding of the **inner** edge's two corners — the edge that meets the
776    /// content. The outer one stays square against the window.
777    pub radius: Option<f32>,
778    /// How far off the surface the panel sits. `0.0` casts no shadow, and neither does a
779    /// height with no [`shadow_color`](Self::shadow_color).
780    pub elevation: Option<f32>,
781    /// The shadow's colour. Unset, **transparent**, as the reference's Material 3 drawer is
782    /// (its defaults, line 795): a panel is lifted by its tone, and shadowed only on request.
783    pub shadow_color: Option<Color>,
784    /// The scrim behind a modal panel, **alpha included**.
785    pub scrim_color: Option<Color>,
786}
787
788/// Defaults for [`SegmentedButton`](crate::SegmentedButton).
789#[derive(Clone, Copy, Debug, Default, PartialEq)]
790pub struct SegmentedTheme {
791    /// The fill under the chosen segment.
792    pub selected_color: Option<Color>,
793    /// The labels' colour.
794    pub label_color: Option<Color>,
795    /// The chosen segment's label colour, and its checkmark's.
796    pub selected_label_color: Option<Color>,
797    /// The outline's colour, and the hairlines' between segments.
798    pub border_color: Option<Color>,
799    /// Their thickness; `0.0` removes both.
800    pub border_width: Option<f32>,
801    /// The group's outer radius. Unset, the ends are stadium-rounded.
802    pub radius: Option<f32>,
803    /// The control's height.
804    pub height: Option<f32>,
805    /// The room either side of a label.
806    pub padding: Option<f32>,
807    /// The labels' type.
808    pub label_style: Option<TextStyle>,
809    /// The checkmark's size.
810    pub icon_size: Option<f32>,
811    /// Whether the chosen segment carries a checkmark.
812    pub show_selected_icon: Option<bool>,
813}
814
815/// Defaults for [`TabBar`](crate::TabBar) — the bar, its labels and its indicator.
816#[derive(Clone, Copy, Debug, Default, PartialEq)]
817pub struct TabBarTheme {
818    /// Which of the two bars an untold `TabBar::new()` is.
819    pub variant: Option<crate::tabs::TabBarVariant>,
820    /// The indicator's colour.
821    pub indicator_color: Option<Color>,
822    /// Its thickness — and, on a primary bar, the radius of its top corners.
823    pub indicator_weight: Option<f32>,
824    /// The **selected** label's colour.
825    pub label_color: Option<Color>,
826    /// Every other label's colour.
827    pub unselected_label_color: Option<Color>,
828    /// The labels' type.
829    pub label_style: Option<TextStyle>,
830    /// The colour of the hairline between the bar and what it labels.
831    pub divider_color: Option<Color>,
832    /// Its thickness; `0.0` removes it.
833    pub divider_height: Option<f32>,
834    /// The room on either side of a label.
835    pub label_padding: Option<f32>,
836    /// The tabs' height, the indicator excluded.
837    pub tab_height: Option<f32>,
838    /// Where the tabs sit when they do not fill the bar.
839    pub alignment: Option<crate::tabs::TabAlignment>,
840}
841
842/// Defaults for [`TabPageSelector`](crate::TabPageSelector) — the row of dots that says
843/// which page of several you are on.
844#[derive(Clone, Copy, Debug, Default, PartialEq)]
845pub struct PageSelectorTheme {
846    /// A dot's diameter.
847    pub size: Option<f32>,
848    /// The gap between two dots.
849    pub gap: Option<f32>,
850    /// What the dot you are on is filled with. Unset, the accent.
851    pub color: Option<Color>,
852    /// The ring every dot is drawn with.
853    pub border_color: Option<Color>,
854}
855
856/// Defaults for [`IconButton`](crate::IconButton).
857#[derive(Clone, Copy, Debug, Default, PartialEq)]
858pub struct IconButtonTheme {
859    /// The box's side.
860    pub size: Option<f32>,
861    /// The glyph's size inside it.
862    pub icon_size: Option<f32>,
863    /// The surface under the glyph.
864    pub color: Option<Color>,
865    /// The glyph's colour.
866    pub icon_color: Option<Color>,
867    /// The outline's colour.
868    pub border_color: Option<Color>,
869    /// Its thickness; `0.0` removes it.
870    pub border_width: Option<f32>,
871    /// The corner radii. Unset, an icon button is a circle.
872    pub radius: Option<BorderRadius>,
873    /// **How much room it reserves for a finger** — the reference's per-widget say over
874    /// the theme's (`icon_button.dart:708`). Unset, the theme's own answer.
875    pub tap_target: Option<crate::theme::TapTarget>,
876}
877
878/// Defaults for the **ink ripple** — every surface that splashes, including
879/// [`InkWell`](crate::InkWell) and [`Button`](crate::Button).
880#[derive(Clone, Copy, Debug, Default, PartialEq)]
881pub struct InkTheme {
882    /// The splash colour, **alpha included**. A widget that computes its own from its
883    /// surface (a button splashing in its `on` colour) still yields to this when it is
884    /// set: an application that has chosen an ink colour has chosen it everywhere.
885    pub color: Option<Color>,
886}
887
888/// Defaults for [`SnackBar`](crate::SnackBar).
889///
890/// The reference's Material 3 snackbar sets its content in `bodyMedium` and its action in
891/// `labelLarge`, and both are read from the type scale rather than written down here.
892#[derive(Clone, Copy, Debug, Default, PartialEq)]
893pub struct SnackBarTheme {
894    /// **What shape it is** (`shape_border.dart`), over the plain `radius` below:
895    /// a snack bar's, which its behaviour already decides — see [`crate::SnackBarBehavior`]. Unset, the widget decides.
896    pub shape: Option<ShapeBorder>,
897    /// **Where its bars sit**, and therefore what they look like. Unset, the reference's
898    /// `Fixed` (`snack_bar.dart:986`). See [`SnackBarBehavior`](crate::SnackBarBehavior).
899    pub behavior: Option<crate::toast::SnackBarBehavior>,
900    /// What a **floating** bar keeps clear of the page. Unset, the reference's
901    /// `insetPadding` (`snack_bar.dart:989`). Silent under `Fixed`.
902    pub inset_padding: Option<Insets>,
903    /// A **floating** bar's width, instead of the room it is given. Unset, the room it is
904    /// given. Silent under `Fixed`, where a width would contradict the behaviour.
905    pub width: Option<f32>,
906    /// The message's type.
907    pub content_text_style: Option<TextStyle>,
908    /// The action's type.
909    pub action_text_style: Option<TextStyle>,
910    /// The bar's surface. Unset, the scheme's `inverse_surface`.
911    pub background_color: Option<Color>,
912    /// The message's colour. Unset, the scheme's `on_inverse_surface`.
913    pub text_color: Option<Color>,
914    /// The action's colour. Unset, the scheme's `inverse_primary` — the one role in the
915    /// scheme whose whole reason for existing is being legible on an inverted surface.
916    pub action_text_color: Option<Color>,
917    /// The close cross's colour. Unset, the scheme's `on_inverse_surface`
918    /// (`snack_bar.dart:995`).
919    pub close_icon_color: Option<Color>,
920    /// The stripe down a notification's leading edge, whichever kind it is. Unset, the
921    /// kind decides.
922    pub accent_color: Option<Color>,
923    /// The stripe for a **success**, the one kind Material 3 has no role for. Unset, a
924    /// green of this crate's own choosing.
925    pub success_color: Option<Color>,
926    /// The corner. Unset, the reference's 4 for a **floating** bar and **nothing at all**
927    /// for a fixed one, which is flush against the edges and has nothing to round.
928    pub radius: Option<f32>,
929    /// How far off the page it sits. Unset, the reference's 6.
930    pub elevation: Option<f32>,
931}
932
933/// Defaults for [`PopupMenuButton`](crate::PopupMenuButton) and its items.
934#[derive(Clone, Copy, Debug, Default, PartialEq)]
935pub struct MenuTheme {
936    /// The items' type.
937    pub text_style: Option<TextStyle>,
938    /// **The panel's surface.** Unset, `surface_container` — a menu is a distinct area
939    /// within the surface, not a thing that floats above it in a different colour
940    /// (`popup_menu.dart:1858`).
941    pub background: Option<Color>,
942    /// What shape the panel is.
943    pub shape: Option<ShapeBorder>,
944    /// The radius of its corners, for a theme that would rather give the number than the
945    /// shape. Outranked by [`shape`](Self::shape).
946    pub radius: Option<f32>,
947    /// How far off the page the panel sits, in pixels. Unset, three
948    /// (`popup_menu.dart:1839`).
949    pub elevation: Option<f32>,
950    /// The colour of the panel's shadow. Unset, the scheme's shadow at 30 %, the
951    /// framework's colour for a height; [`Color::TRANSPARENT`] casts none.
952    pub shadow_color: Option<Color>,
953    /// The room kept **above and below** the rows, inside the panel
954    /// (`popup_menu.dart:1872`).
955    pub padding: Option<Insets>,
956    /// The room kept either side of a row's label (`popup_menu.dart:1876`).
957    pub item_padding: Option<Insets>,
958    /// How tall one row is. Unset, the smallest box a finger can be asked to hit.
959    pub item_height: Option<f32>,
960}
961
962/// Defaults for [`DropdownButton`](crate::DropdownButton) and its options.
963///
964/// The `menu_*` fields are the **panel** its options float on. They are this theme's and
965/// not [`MenuTheme`]'s, because the reference's dropdown button does not read the menu
966/// theme either: its list has its own height, eight, where a menu has three.
967#[derive(Clone, Copy, Debug, Default, PartialEq)]
968pub struct DropdownTheme {
969    /// The value's and the options' type.
970    pub text_style: Option<TextStyle>,
971    /// The panel's surface. Unset, `surface_container`, the popup menu's.
972    pub menu_background: Option<Color>,
973    /// What shape the panel is.
974    pub menu_shape: Option<ShapeBorder>,
975    /// The radius of the panel's corners, for a theme that would rather give the number
976    /// than the shape. Outranked by [`menu_shape`](Self::menu_shape).
977    pub menu_radius: Option<f32>,
978    /// How far off the page the panel sits, in pixels. Unset, eight, the reference's.
979    pub menu_elevation: Option<f32>,
980    /// The colour of the panel's shadow. Unset, the scheme's shadow at 30 %;
981    /// [`Color::TRANSPARENT`] casts none.
982    pub menu_shadow_color: Option<Color>,
983    /// The room kept **above and below** the options, inside the panel. Unset, eight.
984    pub menu_padding: Option<Insets>,
985}
986
987/// Defaults for [`Autocomplete`](crate::Autocomplete) and its suggestions.
988#[derive(Clone, Copy, Debug, Default, PartialEq)]
989pub struct AutocompleteTheme {
990    /// The suggestions' type.
991    pub text_style: Option<TextStyle>,
992}
993
994/// Defaults for [`Table`](crate::Table).
995///
996/// The reference names the two apart — a heading is `titleSmall`, a cell is `bodyMedium` —
997/// which is why one `text_style` would be the wrong shape here.
998#[derive(Clone, Copy, Debug, Default, PartialEq)]
999pub struct TableTheme {
1000    /// The column headings' type.
1001    pub heading_text_style: Option<TextStyle>,
1002    /// The cells' type.
1003    pub data_text_style: Option<TextStyle>,
1004}
1005
1006/// Defaults for [`DatePicker`](crate::DatePicker).
1007#[derive(Clone, Copy, Debug, Default, PartialEq)]
1008pub struct DatePickerTheme {
1009    /// The days' type.
1010    pub day_text_style: Option<TextStyle>,
1011    /// The weekday initials above them.
1012    pub weekday_text_style: Option<TextStyle>,
1013}
1014
1015/// Defaults for the **scrollbars** the framework draws over a scrollable.
1016///
1017/// The thumb is a **fade of one colour**, not three colours: at rest, warmed towards its
1018/// hovered value under a pointer, and at its held value the moment it is grabbed. A theme
1019/// substitutes the colour being faded and may move the three levels; it does not hand
1020/// over three finished colours, because interpolating between those means interpolating
1021/// colours and this framework has been bitten by doing that in the wrong space more than
1022/// once. Fading one colour is the arithmetic that was already right.
1023///
1024/// There is no field for a **track**. The framework paints none — the reference's is
1025/// transparent unless a caller asks for one — and a field that painted nothing whatever
1026/// it was set to would be worse than no field.
1027#[derive(Clone, Copy, Debug, Default, PartialEq)]
1028pub struct ScrollbarTheme {
1029    /// The thumb's thickness. Unset, 8 — and it is the **thumb's**, not a slot's: the
1030    /// margin holds it clear of the edge rather than shrinking it to fit.
1031    pub thickness: Option<f32>,
1032    /// How far the thumb is held clear of the edge it runs along. Unset, 2.
1033    pub margin: Option<f32>,
1034    /// How short the thumb is allowed to get on a very long page. Unset, 48 — below
1035    /// which it stops reading as a handle and stops being one to grab.
1036    pub min_thumb_length: Option<f32>,
1037    /// The thumb's corner radius. Unset, half its thickness, which is a pill.
1038    pub radius: Option<f32>,
1039    /// The colour the thumb is a fade of. Unset, the scheme's `on_surface`.
1040    pub thumb_color: Option<Color>,
1041    /// Its opacity at rest. Unset, 0.30 on a dark surface and 0.10 on a light one.
1042    pub opacity: Option<f32>,
1043    /// Its opacity with a pointer near it, which it warms towards. Unset, 0.65 and 0.50.
1044    pub hover_opacity: Option<f32>,
1045    /// Its opacity while it is held. Unset, 0.75 and 0.60. This one does not fade in —
1046    /// the hand is already on it, and a fade would only lag behind the grab.
1047    pub drag_opacity: Option<f32>,
1048}
1049
1050/// Defaults for [`NavigationBar`](crate::NavigationBar) — the bar at the **head** of a
1051/// screen, not the one along the bottom, which is `NavRailTheme`.
1052///
1053/// There is no field for the back button's size. The bar names one explicitly when it
1054/// builds the button, and an explicit argument outranks a theme — a field here would be
1055/// one that the widget never reads, which is worse than an absent one. A theme that wants
1056/// a different back button sets [`IconButtonTheme`] and drops the bar's own override.
1057#[derive(Clone, Copy, Debug, Default, PartialEq)]
1058pub struct NavBarTheme {
1059    /// The room the bar takes. Unset, 56 — the reference's toolbar height.
1060    pub height: Option<f32>,
1061    /// The bar's own padding. Unset, nothing above or below and a leading inset that
1062    /// clears the back-gesture zone, so the button stays clickable without starting a
1063    /// swipe.
1064    pub padding: Option<Insets>,
1065    /// What it is painted on. Unset, the theme's background.
1066    pub background: Option<Color>,
1067    /// The title's type. Unset, the theme's `title_large` — a bar title is a *title*, not
1068    /// body text. Its `color`, when set, is the title's colour.
1069    pub title_style: Option<TextStyle>,
1070    /// The hairline along the bottom edge. Unset, the scheme's `outline_variant`.
1071    pub divider_color: Option<Color>,
1072    /// How thick that hairline is. Unset, one pixel.
1073    pub divider_thickness: Option<f32>,
1074}
1075
1076/// Defaults for [`NavigationRail`](crate::NavigationRail) and [`BottomBar`](crate::BottomBar).
1077#[derive(Clone, Debug, Default, PartialEq)]
1078pub struct NavRailTheme {
1079    /// **The shape of a selected destination's indicator** (`navigation_rail.dart:1148`).
1080    /// Unset, the reference's pill.
1081    pub indicator_shape: Option<frus_core::ShapeBorder>,
1082    /// **The highlight over a destination, per state** — over the framework's own state
1083    /// layer, and under the destination's own word (`navigation_bar.dart:232`).
1084    ///
1085    /// The first field here that is not a plain value but a
1086    /// [`WidgetStateProperty`](crate::WidgetStateProperty). It owns a `Vec`, which is why
1087    /// this struct and [`WidgetThemes`] stopped being `Copy` in milestone 448 — a theme
1088    /// that weighed eight kilobytes had no business being copied implicitly anyway.
1089    pub overlay_color: Option<crate::widgetstate::WidgetStateProperty<frus_core::Color>>,
1090    /// The destinations' labels.
1091    pub label_text_style: Option<TextStyle>,
1092    /// The count carried by a destination's badge.
1093    pub badge_text_style: Option<TextStyle>,
1094    /// The **rail's** surface. Unset, the scheme's `surface`.
1095    pub background_color: Option<Color>,
1096    /// The pill behind the selected destination. Unset, the scheme's
1097    /// `secondary_container` — **opaque**, as the reference's is.
1098    pub indicator_color: Option<Color>,
1099    /// The selected destination's glyph. Unset, `on_secondary_container`: it is drawn on
1100    /// the indicator, so it takes the indicator's content colour.
1101    pub selected_icon_color: Option<Color>,
1102    /// An unselected destination's glyph. Unset, `on_surface_variant`.
1103    pub unselected_icon_color: Option<Color>,
1104    /// The selected destination's label. Unset, `on_surface` — the label sits *below* the
1105    /// indicator rather than on it, which is why it is not the indicator's content colour.
1106    pub selected_label_color: Option<Color>,
1107    /// An unselected destination's label. Unset, `on_surface` on a rail and
1108    /// `on_surface_variant` on a bar, which is the one place the reference gives the two
1109    /// different answers.
1110    pub unselected_label_color: Option<Color>,
1111    /// The destinations' glyphs. Unset, the reference's 24.
1112    pub icon_size: Option<f32>,
1113    /// Whether a **rail's** selected destination gets an indicator behind its glyph.
1114    /// Unset, `true` (`navigation_rail.dart:1233`). `false` is the arrangement that
1115    /// predates it, where the selected destination says so in the accent instead.
1116    pub use_indicator: Option<bool>,
1117    /// How far off the page a **rail** sits. Unset, `0` — flat, as the reference's is
1118    /// (`navigation_rail.dart:1236`), the rail being separated by a rule and not by a
1119    /// shadow.
1120    pub elevation: Option<f32>,
1121    /// The **bottom bar's** surface, a rung higher than the rail's. Unset, the scheme's
1122    /// `surface_container`.
1123    ///
1124    /// The reference keeps a theme object per navigation widget and gives them different
1125    /// defaults; this crate keeps one for the two, so the two surfaces are two fields
1126    /// rather than two structs.
1127    pub bar_background_color: Option<Color>,
1128}
1129
1130/// Defaults for [`SearchBar`](crate::SearchBar) — the reference's `SearchBarThemeData`.
1131///
1132/// Every field there is a `WidgetStateProperty`, resolved against the bar's own state; here
1133/// they are plain values, with one exception that matters and several that do not. The
1134/// exception is the highlight, and this framework already answers it: a state layer is one
1135/// rule applied from the ground toward the ink, so a bar does not need a colour per state
1136/// to light under a pointer. The rest — a fill, a shape or a padding that changes when the
1137/// bar is hovered — are things the reference *can* say and its own defaults never do.
1138#[derive(Clone, Copy, Debug, Default, PartialEq)]
1139pub struct SearchBarTheme {
1140    /// The bar's fill. Unset, the scheme's `surface_container_high`
1141    /// (`search_anchor.dart:1859`).
1142    pub background_color: Option<Color>,
1143    /// The colour its shadow is cast in, **alpha included**. Unset, the scheme's `shadow`
1144    /// at 30 %.
1145    pub shadow_color: Option<Color>,
1146    /// How far off the page it sits. Unset, `6` (`search_anchor.dart:1863`).
1147    pub elevation: Option<f32>,
1148    /// Its shape. Unset, a stadium (`search_anchor.dart:1892`).
1149    pub shape: Option<frus_core::ShapeBorder>,
1150    /// Its corners, read as a rounded rectangle when no shape was named.
1151    pub radius: Option<BorderRadius>,
1152    /// An outline around it. Unset, none — the reference has no default side
1153    /// (`search_anchor.dart:1888`), a raised surface being told apart by its shadow.
1154    pub side: Option<frus_core::BorderSide>,
1155    /// The room inside it. Unset, `8` either side, applied twice (once around the row and
1156    /// once around the field), which is where the sixteen between an icon and the first
1157    /// letter comes from.
1158    pub padding: Option<frus_core::Insets>,
1159    /// The narrowest it will go. Unset, `360`.
1160    pub min_width: Option<f32>,
1161    /// The widest. Unset, `800`.
1162    pub max_width: Option<f32>,
1163    /// Its height. Unset, `56`.
1164    pub height: Option<f32>,
1165    /// The value's type. Unset, `bodyLarge` in `on_surface`.
1166    pub text_style: Option<TextStyle>,
1167    /// The hint's. Unset, the value's, then `bodyLarge` in `on_surface_variant` — the
1168    /// reference falls back to the value's style **before** its own default, so a bar told
1169    /// to use one type does not say its hint in another.
1170    pub hint_style: Option<TextStyle>,
1171}
1172
1173/// Defaults for the view a [`SearchAnchor`](crate::SearchAnchor) opens — the reference's
1174/// `SearchViewThemeData`.
1175///
1176/// Kept apart from [`SearchBarTheme`] because the reference keeps them apart, and because
1177/// the two disagree where it matters: the view's shape depends on whether it is the screen,
1178/// its header is taller than a bar when it is, and it has a rule under that header that a
1179/// bar has nothing to do with.
1180#[derive(Clone, Copy, Debug, Default, PartialEq)]
1181pub struct SearchViewTheme {
1182    /// The view's fill. Unset, the scheme's `surface_container_high`
1183    /// (`search_anchor.dart:1936`) — the same rung the bar takes, so one grows out of the
1184    /// other rather than changing colour on the way.
1185    pub background_color: Option<Color>,
1186    /// How far off the page it sits. Unset, `6`.
1187    pub elevation: Option<f32>,
1188    /// Its shape. Unset, corners of `28` floating and square full-screen.
1189    pub shape: Option<frus_core::ShapeBorder>,
1190    /// Its corners, read as a rounded rectangle when no shape was named.
1191    pub radius: Option<BorderRadius>,
1192    /// An outline around it. Unset, none (`search_anchor.dart:1944`).
1193    pub side: Option<frus_core::BorderSide>,
1194    /// The rule under the header. Unset, the scheme's `outline`
1195    /// (`search_anchor.dart:1967`).
1196    pub divider_color: Option<Color>,
1197    /// The header's height. Unset, `72` full-screen and a bar's `56` floating.
1198    pub header_height: Option<f32>,
1199    /// The header's type. Unset, `bodyLarge` in `on_surface`.
1200    pub header_text_style: Option<TextStyle>,
1201    /// The header hint's. Unset, `bodyLarge` in `on_surface_variant`.
1202    pub header_hint_style: Option<TextStyle>,
1203    /// The narrowest the view goes. Unset, `360`.
1204    pub min_width: Option<f32>,
1205    /// The shortest. Unset, `240`.
1206    pub min_height: Option<f32>,
1207    /// The room either side of the header's contents. Unset, `8`.
1208    pub bar_padding: Option<frus_core::Insets>,
1209}
1210
1211/// Defaults for [`NavigationDrawer`](crate::NavigationDrawer).
1212///
1213/// The reference keeps `NavigationDrawerThemeData` apart from the rail's and the bar's
1214/// (`navigation_drawer_theme.dart`) and this follows it, where [`NavRailTheme`] holds two
1215/// widgets at once. The reason is the defaults: a drawer's surface, type step, indicator
1216/// size and tile height all differ from a rail's, and a shared struct would have had to
1217/// carry a second copy of every one of them.
1218#[derive(Clone, Debug, Default, PartialEq)]
1219pub struct NavDrawerTheme {
1220    /// The panel's surface. Unset, the scheme's `surface_container_low`
1221    /// (`navigation_drawer.dart:740`) — a rung above a rail's `surface`, the drawer being
1222    /// a sheet over the page rather than a strip beside it.
1223    pub background_color: Option<Color>,
1224    /// How far off the page the panel sits. Unset, `1` (`navigation_drawer.dart:729`).
1225    pub elevation: Option<f32>,
1226    /// The shadow's colour. Unset, **transparent**, as the reference's navigation drawer
1227    /// defaults are (line 746): the panel's height is its tone, and a shadow is cast only
1228    /// where a colour is named.
1229    pub shadow_color: Option<Color>,
1230    /// The pill behind the selected destination. Unset, the scheme's
1231    /// `secondary_container`.
1232    pub indicator_color: Option<Color>,
1233    /// That pill's shape. Unset, a stadium (`navigation_drawer.dart:731`).
1234    pub indicator_shape: Option<frus_core::ShapeBorder>,
1235    /// That pill's box, `(width, height)`. Unset, `336 x 56`
1236    /// (`navigation_drawer.dart:732`). The width is a **ceiling**: the pill never grows
1237    /// past the room its tile has, which is what keeps 336 inside a 304-wide panel.
1238    pub indicator_size: Option<(f32, f32)>,
1239    /// The highlight over a destination, per state — over the framework's state layer, and
1240    /// under the destination's own word.
1241    pub overlay_color: Option<crate::widgetstate::WidgetStateProperty<frus_core::Color>>,
1242    /// A tile's height. Unset, `56` (`navigation_drawer.dart:730`).
1243    pub tile_height: Option<f32>,
1244    /// The room either side of a tile. Unset, `12` left and right
1245    /// (`navigation_drawer.dart:69`).
1246    pub tile_padding: Option<frus_core::Insets>,
1247    /// The destinations' glyphs. Unset, `24`.
1248    pub icon_size: Option<f32>,
1249    /// The destinations' labels. Unset, `labelLarge` (`navigation_drawer.dart:768`) — a
1250    /// step above a rail's, a drawer's label having a line to itself.
1251    pub label_text_style: Option<TextStyle>,
1252    /// The count a destination carries. Unset, `labelSmall`.
1253    pub badge_text_style: Option<TextStyle>,
1254    /// A selected destination's glyph **and** label. Unset, `on_secondary_container`:
1255    /// here the indicator is the whole row, so both stand on it and both take its content
1256    /// colour — the one place this differs from a rail, where the label sits outside the
1257    /// pill.
1258    pub selected_color: Option<Color>,
1259    /// An unselected destination's glyph and label. Unset, `on_surface_variant`.
1260    pub unselected_color: Option<Color>,
1261}
1262
1263/// Defaults for [`Alert`](crate::Alert) — the message box, not the dialog. See
1264/// [`DialogTheme`] for [`AlertDialog`](crate::AlertDialog).
1265#[derive(Clone, Copy, Debug, Default, PartialEq)]
1266pub struct AlertTheme {
1267    /// The heading's type.
1268    pub title_text_style: Option<TextStyle>,
1269    /// The message's type.
1270    pub content_text_style: Option<TextStyle>,
1271}
1272
1273/// Defaults for [`BottomAppBar`](crate::BottomAppBar).
1274#[derive(Clone, Copy, Debug, Default, PartialEq)]
1275pub struct BottomAppBarTheme {
1276    /// The bar's surface. Unset, the scheme's `surface_container`.
1277    pub color: Option<Color>,
1278}
1279
1280/// Defaults for [`BottomSheet`](crate::BottomSheet).
1281#[derive(Clone, Copy, Debug, Default, PartialEq)]
1282pub struct BottomSheetTheme {
1283    /// **What shape the sheet is** — the reference's `BottomSheetThemeData.shape`. Unset,
1284    /// the framework rounds the **top** corners only: the bottom edge is flush against the
1285    /// window, and rounding it would cut two notches out of the screen.
1286    pub shape: Option<ShapeBorder>,
1287    /// The radius of those top two corners, for a theme that would rather give the number
1288    /// than the shape. Outranked by [`shape`](Self::shape).
1289    pub radius: Option<f32>,
1290    /// The sheet's surface. Unset, the scheme's `surface_container_low`.
1291    pub background_color: Option<Color>,
1292}
1293
1294/// Defaults for [`MaterialBanner`](crate::MaterialBanner).
1295#[derive(Clone, Copy, Debug, Default, PartialEq)]
1296pub struct BannerTheme {
1297    /// The banner's colour. Unset, a low container tone.
1298    pub color: Option<Color>,
1299    /// What the surface is tinted towards for its elevation. Unset, nothing is.
1300    pub surface_tint: Option<Color>,
1301    /// The shadow's colour. Unset, there is none.
1302    pub shadow_color: Option<Color>,
1303    /// The rule along the bottom, drawn only where the banner is flat. Unset, the
1304    /// scheme's `outlineVariant`.
1305    pub divider_color: Option<Color>,
1306    /// How far off the page it sits. Unset, `0`: flat, with a rule under it — see
1307    /// [`BANNER_ELEVATION`](crate::BANNER_ELEVATION).
1308    pub elevation: Option<f32>,
1309    /// The padding around the message row. Unset, it depends on where the actions went.
1310    pub padding: Option<Insets>,
1311    /// The margin around the banner. Unset, it depends on the elevation.
1312    pub margin: Option<Insets>,
1313    /// What the leading slot is held off the message by. Unset, 16 at its trailing edge.
1314    pub leading_padding: Option<Insets>,
1315    /// What the message is set in. Unset, `bodyMedium`.
1316    pub content_text_style: Option<TextStyle>,
1317}
1318
1319/// Defaults for [`Dialog`](crate::Dialog) and [`AlertDialog`](crate::AlertDialog).
1320#[derive(Clone, Copy, Debug, Default, PartialEq)]
1321pub struct DialogTheme {
1322    /// The surface's colour. Unset, the scheme's `surfaceContainerHigh`.
1323    pub color: Option<Color>,
1324    /// How far off the page it sits. Unset, 6.
1325    pub elevation: Option<f32>,
1326    /// The shadow's colour. Unset, **transparent**: the reference's Material 3 dialog
1327    /// shows its height by its container tone, not by a drop shadow.
1328    pub shadow_color: Option<Color>,
1329    /// What the surface is tinted towards for its elevation. Unset, nothing is — see
1330    /// [`Self::shadow_color`] for why.
1331    pub surface_tint: Option<Color>,
1332    /// **What shape it is** (`dialog.dart`). Unset, a 28-radius rounded rectangle.
1333    ///
1334    /// It carried the reference's *name* and a `BorderRadius` until milestone 451, which
1335    /// is the deviation this field is the clearest case of: `shape` in the reference is a
1336    /// `ShapeBorder`, and a corner radius is one of the things one can be.
1337    pub shape: Option<ShapeBorder>,
1338    /// How far the dialog is held off the window's edges. Unset, 40 across and 24 down.
1339    pub inset_padding: Option<Insets>,
1340    /// The glyph above the title. Unset, the scheme's `secondary`.
1341    pub icon_color: Option<Color>,
1342    /// The heading's type. Unset, `headlineSmall`.
1343    pub title_text_style: Option<TextStyle>,
1344    /// What the dialog says. Unset, `bodyMedium`.
1345    pub content_text_style: Option<TextStyle>,
1346}
1347
1348/// Defaults for [`Steps`](crate::Steps).
1349#[derive(Clone, Copy, Debug, Default, PartialEq)]
1350pub struct StepsTheme {
1351    /// The caption under each marker.
1352    pub label_text_style: Option<TextStyle>,
1353    /// The number inside the marker. It is a **glyph on a circle** rather than something
1354    /// read at length, so the framework's own default does not pass through the reader's
1355    /// font setting — a theme that sets this one takes that decision back.
1356    pub index_text_style: Option<TextStyle>,
1357}
1358
1359/// Defaults for [`Tree`](crate::Tree).
1360#[derive(Clone, Copy, Debug, Default, PartialEq)]
1361pub struct TreeTheme {
1362    /// The rows' type.
1363    pub text_style: Option<TextStyle>,
1364}
1365
1366/// Defaults for [`Timeline`](crate::Timeline).
1367#[derive(Clone, Copy, Debug, Default, PartialEq)]
1368pub struct TimelineTheme {
1369    /// An entry's heading.
1370    pub title_text_style: Option<TextStyle>,
1371    /// The line under it.
1372    pub detail_text_style: Option<TextStyle>,
1373}
1374
1375/// Defaults for [`Breadcrumb`](crate::Breadcrumb).
1376///
1377/// The reference has no breadcrumb, so its type is argued rather than read: a trail is a
1378/// **secondary** line of navigation above the page's own content, which is `bodyMedium`.
1379#[derive(Clone, Copy, Debug, Default, PartialEq)]
1380pub struct BreadcrumbTheme {
1381    /// The segments' type, the separators included.
1382    pub text_style: Option<TextStyle>,
1383}
1384
1385/// Defaults for [`ReorderableList`](crate::ReorderableList)'s grip.
1386///
1387/// The grip is the one part of a reorderable list the framework draws itself, so it is the
1388/// one part a theme has anything to say about — the rows are the application's.
1389#[derive(Clone, Copy, Debug, Default, PartialEq)]
1390pub struct ReorderableTheme {
1391    /// The glyph the grip carries.
1392    pub handle_icon: Option<crate::IconData>,
1393    /// Its colour, over the muted foreground.
1394    pub handle_color: Option<Color>,
1395    /// Its size.
1396    pub handle_icon_size: Option<f32>,
1397}
1398
1399/// Defaults for [`Kanban`](crate::Kanban).
1400///
1401/// The reference has no board, so its type is argued: a card is a small piece of content
1402/// read on its own, which is `bodyLarge` — the step the reference gives a list tile's title
1403/// and this framework already gives a `Tree` row.
1404#[derive(Clone, Copy, Debug, Default, PartialEq)]
1405pub struct KanbanTheme {
1406    /// A text card's label.
1407    pub card_text_style: Option<TextStyle>,
1408    /// A column's heading over its cards.
1409    pub column_title_text_style: Option<TextStyle>,
1410}
1411
1412/// Defaults for [`Form`](crate::Form)'s error summary.
1413///
1414/// The reference has no summary list, so its type is argued: a bullet restates an error
1415/// already shown under its field, which is `bodySmall` — the step the reference gives the
1416/// helper line those errors come from.
1417#[derive(Clone, Copy, Debug, Default, PartialEq)]
1418pub struct FormTheme {
1419    /// A summary bullet's type.
1420    pub bullet_text_style: Option<TextStyle>,
1421    /// The summary's heading over them.
1422    pub summary_title_text_style: Option<TextStyle>,
1423}
1424
1425/// Defaults for [`TimePicker`](crate::TimePicker) and [`TimeRange`](crate::TimeRange).
1426///
1427/// The reference names three of these four. Its dial is `bodyLarge`, its help line
1428/// `labelMedium`, and its day period `titleMedium` — which is the same step as the dial and
1429/// is why the AM/PM cells here are simply cells. The **preview** is the fourth: the
1430/// reference puts an editable pair of fields at `displayMedium` where this widget shows one
1431/// read-only line above two grids, so it takes the heading step that line is.
1432#[derive(Clone, Copy, Debug, Default, PartialEq)]
1433pub struct TimePickerTheme {
1434    /// The hour, minute and AM/PM cells.
1435    pub dial_text_style: Option<TextStyle>,
1436    /// The `HH:MM` line above them.
1437    pub preview_text_style: Option<TextStyle>,
1438    /// The "Hour" and "Minute" lines over each grid.
1439    pub help_text_style: Option<TextStyle>,
1440    /// The "Start" and "End" headings of a [`TimeRange`](crate::TimeRange).
1441    pub range_label_text_style: Option<TextStyle>,
1442}
1443
1444/// Defaults for [`Kbd`](crate::Kbd).
1445///
1446/// The reference has no key cap, so its type is argued rather than read: a shortcut hint is
1447/// a **label**, and a key cap is the one place in this framework where the glyphs stand for
1448/// what is printed on a keyboard, hence the monospaced default.
1449#[derive(Clone, Copy, Debug, Default, PartialEq)]
1450pub struct KbdTheme {
1451    /// The cap's label.
1452    pub text_style: Option<TextStyle>,
1453}
1454
1455/// Defaults for [`ToggleButtons`](crate::ToggleButtons).
1456///
1457/// The three border colours are named apart although the reference defaults all three to
1458/// the same value (`toggle_buttons.dart:601`, `:635`, `:651`): a bank is one object and
1459/// its outline does not break up because one of its buttons is on, but an application
1460/// that wants the chosen pair marked out needs somewhere to say so.
1461#[derive(Clone, Debug, Default, PartialEq)]
1462pub struct ToggleButtonsTheme {
1463    /// What is drawn in a button that is off — its text, its glyph, anything under it
1464    /// that reads the theme for a colour.
1465    pub color: Option<Color>,
1466    /// The same, for a button that is on.
1467    pub selected_color: Option<Color>,
1468    /// The same, when the bank cannot be pressed.
1469    pub disabled_color: Option<Color>,
1470    /// A button's ground, per state. A plain colour — `WidgetStateProperty::all` —
1471    /// fills every button rather than only the ones that are on, as the reference's does.
1472    pub fill_color: Option<crate::WidgetStateProperty<Color>>,
1473    /// The hairline around and beside a button that is off.
1474    pub border_color: Option<Color>,
1475    /// The hairline around and beside a button that is on. A seam between an on button and
1476    /// an off one takes this, from either side.
1477    pub selected_border_color: Option<Color>,
1478    /// The hairline when the bank cannot be pressed.
1479    pub disabled_border_color: Option<Color>,
1480    /// The hairline's width, around the bank and between its buttons.
1481    pub border_width: Option<f32>,
1482    /// The **bank's** corners: the buttons at its two ends take a pair each, and the ones
1483    /// between them are square.
1484    pub border_radius: Option<BorderRadius>,
1485    /// The type any text in the bank takes. Its colour is ignored — that is the three
1486    /// content colours' to answer, and it depends on the state.
1487    pub text_style: Option<TextStyle>,
1488    /// The smallest a button may be across.
1489    pub min_width: Option<f32>,
1490    /// And down. Untold, both are the tap target.
1491    pub min_height: Option<f32>,
1492    /// The largest. Untold, unbounded.
1493    pub max_width: Option<f32>,
1494    /// Likewise.
1495    pub max_height: Option<f32>,
1496}
1497
1498/// Defaults for [`GridTileBar`](crate::GridTileBar).
1499///
1500/// The two heights are two fields and not one, because it is the **content** that picks
1501/// between them: a bar with a title and a subtitle is 68 and any other is 48. An
1502/// application setting one has not thereby said anything about the other.
1503#[derive(Clone, Copy, Debug, Default, PartialEq)]
1504pub struct GridTileBarTheme {
1505    /// What is drawn behind the words. Untold, nothing at all: the picture is the
1506    /// background, which is the arrangement the widget is for.
1507    pub background_color: Option<Color>,
1508    /// The colour of the words and glyphs on it. Untold, white — the bar stands on a
1509    /// photograph, and a photograph has no brightness the theme knows about.
1510    pub foreground_color: Option<Color>,
1511    /// The title's type. Untold, the scale's `title_medium`.
1512    pub title_style: Option<TextStyle>,
1513    /// The subtitle's. Untold, `body_small`.
1514    pub subtitle_style: Option<TextStyle>,
1515    /// How tall a bar carrying one line is.
1516    pub height: Option<f32>,
1517    /// How tall one carrying a title **and** a subtitle is.
1518    pub two_line_height: Option<f32>,
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523    /// **The rungs a shape is resolved on**, and the third one in particular: a radius
1524    /// was all a theme could say until milestone 450, and applications have written them.
1525    /// A theme naming a `shape` outranks one naming only a `radius`; naming both is
1526    /// naming the shape.
1527    #[test]
1528    fn a_shape_outranks_a_radius_and_a_caller_outranks_both() {
1529        use super::{resolve_shape, BorderRadius, ShapeBorder};
1530        let fallback = ShapeBorder::stadium();
1531        let told = ShapeBorder::circle();
1532        let themed = ShapeBorder::beveled(3.0);
1533
1534        assert_eq!(resolve_shape(None, None, None, fallback), fallback);
1535        assert_eq!(
1536            resolve_shape(None, None, Some(BorderRadius::uniform(4.0)), fallback),
1537            ShapeBorder::rounded(4.0),
1538            "a plain radius still speaks"
1539        );
1540        assert_eq!(
1541            resolve_shape(
1542                None,
1543                Some(themed),
1544                Some(BorderRadius::uniform(4.0)),
1545                fallback
1546            ),
1547            themed,
1548            "and a shape beside it outranks it"
1549        );
1550        assert_eq!(
1551            resolve_shape(
1552                Some(told),
1553                Some(themed),
1554                Some(BorderRadius::uniform(4.0)),
1555                fallback
1556            ),
1557            told,
1558            "and the caller outranks the theme"
1559        );
1560    }
1561
1562    use super::*;
1563    use crate::card::{Card, CardVariant, CARD_MARGIN};
1564    use crate::divider::{Divider, DIVIDER_SPACE};
1565    use crate::flex::Flex;
1566    use crate::runtime::Runtime;
1567    use crate::theme::{TextTheme, Theme};
1568    use crate::ui::build_ui;
1569    use crate::widget::Widget;
1570    use frus_core::{Color, Primitive, Rect, Size};
1571
1572    #[derive(Clone, Debug, PartialEq)]
1573    enum Msg {}
1574
1575    /// Builds the widget **inside a sized parent**, which is where a margin and an
1576    /// `Auto` width mean anything, and returns the frame's primitives.
1577    fn framed(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Vec<Primitive> {
1578        let tree = Flex::column().width(200.0).height(100.0).child(widget);
1579        build_ui(&tree, Size::new(200.0, 100.0), &Runtime::default(), theme)
1580            .scene()
1581            .primitives()
1582            .to_vec()
1583    }
1584
1585    /// The first crisp box the tree paints — the widget's own surface, past any shadow.
1586    fn painted_rect(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Rect {
1587        framed(widget, theme)
1588            .iter()
1589            .find_map(|p| match p {
1590                Primitive::Rect { rect, blur, .. } if *blur == 0.0 => Some(*rect),
1591                _ => None,
1592            })
1593            .expect("the widget paints a box")
1594    }
1595
1596    #[test]
1597    fn the_theme_reaches_layout_and_not_only_paint() {
1598        // The point of `style_themed`. A divider's *height* is a layout property, and a
1599        // theme that could not set it would be able to recolour a separator but not make
1600        // one thin — which is the setting an application actually asks for.
1601        let plain = Theme::default();
1602        let mut thin = Theme::default();
1603        thin.widgets.divider.height = Some(1.0);
1604
1605        // The line is centred in its box, so a taller box puts it further down; the box
1606        // itself is what the theme moved.
1607        // The line is centred in its box: a 16 px box puts it at 7.5, a 1 px box at 0.
1608        let tall = painted_rect(Divider::new(), &plain);
1609        let thin_line = painted_rect(Divider::new(), &thin);
1610        assert!(
1611            tall.y > thin_line.y,
1612            "the themed divider's box is shorter: {tall:?} against {thin_line:?}"
1613        );
1614        assert_eq!(tall.height, 1.0, "the line itself is unchanged");
1615        assert_eq!(thin_line.height, 1.0);
1616    }
1617
1618    #[test]
1619    fn the_caller_outranks_the_theme_and_the_theme_outranks_the_framework() {
1620        // The whole chain in one assertion per link, on a layout property so that both
1621        // halves of the resolution are exercised.
1622        let plain = Theme::default();
1623        let mut themed = Theme::default();
1624        themed.widgets.card.margin = Some(20.0);
1625
1626        let x = |card: Card<Msg>, theme: &Theme| painted_rect(card, theme).x;
1627
1628        assert_eq!(x(Card::new(), &plain), CARD_MARGIN, "the framework's");
1629        assert_eq!(x(Card::new(), &themed), 20.0, "the theme's");
1630        assert_eq!(
1631            x(Card::new().margin(2.0), &themed),
1632            2.0,
1633            "the caller's, over the theme's"
1634        );
1635    }
1636
1637    #[test]
1638    fn a_theme_can_change_what_an_untold_widget_is() {
1639        // Not just a number: the *variant* is themeable, so an application can be flat
1640        // throughout without writing `.filled()` at every call site.
1641        let mut flat = Theme::default();
1642        flat.widgets.card.variant = Some(CardVariant::Filled);
1643        let shadows = |theme: &Theme| {
1644            framed(Card::<Msg>::new(), theme)
1645                .iter()
1646                .filter(|p| matches!(p, Primitive::Rect { blur, .. } if *blur > 0.0))
1647                .count()
1648        };
1649        assert_eq!(shadows(&Theme::default()), 1, "elevated by default");
1650        assert_eq!(shadows(&flat), 0, "flat because the theme says so");
1651    }
1652
1653    #[test]
1654    fn the_ink_colour_is_themeable_even_where_a_widget_computes_its_own() {
1655        // A `Button` derives its splash from its own `on` colour, which is the right
1656        // default and the wrong thing to insist on: an application that has chosen an
1657        // ink colour has chosen it for every surface.
1658        let mine = Color::rgb8(255, 0, 128);
1659        let mut theme = Theme::default();
1660        theme.widgets.ink.color = Some(mine);
1661        let button = crate::button::Button::<Msg>::new("Go");
1662        assert_eq!(
1663            Widget::<Msg>::ink(&button, &theme).map(|i| i.color),
1664            Some(mine)
1665        );
1666        // And a plain surface takes it too.
1667        assert_eq!(crate::ink::default_splash(&theme), mine);
1668    }
1669
1670    #[test]
1671    fn changing_a_theme_invalidates_the_layout_cache() {
1672        // The cache keys on a fingerprint of the *effective* style, which now includes
1673        // the theme's say. If it did not, a theme swap would keep the old geometry and
1674        // the change would appear only after something else moved.
1675        let runtime = Runtime::default();
1676        let tree = Flex::<Msg>::column()
1677            .width(200.0)
1678            .height(100.0)
1679            .child(Divider::new());
1680        let plain = Theme::default();
1681        let mut thin = Theme::default();
1682        thin.widgets.divider.height = Some(1.0);
1683
1684        let height = |theme: &Theme| {
1685            build_ui(&tree, Size::new(200.0, 100.0), &runtime, theme)
1686                .scene()
1687                .primitives()
1688                .iter()
1689                .find_map(|p| match p {
1690                    Primitive::Rect { rect, .. } => Some(rect.y),
1691                    _ => None,
1692                })
1693                .expect("a line")
1694        };
1695        // The same runtime, and therefore the same cache, across both builds.
1696        let first = height(&plain);
1697        let second = height(&thin);
1698        assert_ne!(
1699            first, second,
1700            "a themed size must not be served from the cache of an unthemed one"
1701        );
1702        assert_eq!(height(&plain), first, "and back again");
1703    }
1704
1705    #[test]
1706    fn the_defaults_are_still_reachable_through_an_empty_theme() {
1707        let plain = Theme::default();
1708        assert_eq!(
1709            Widget::<Msg>::style_themed(&Divider::new(), &plain).height,
1710            frus_layout::Dimension::Length(DIVIDER_SPACE)
1711        );
1712    }
1713
1714    /// The size every `Primitive::Text` in a frame was drawn at, in the order they were
1715    /// painted. A widget's type is not reachable any other way — these widgets paint their
1716    /// labels instead of laying them out.
1717    fn text_sizes(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Vec<f32> {
1718        framed(widget, theme)
1719            .iter()
1720            .filter_map(|p| match p {
1721                Primitive::Text { size, .. } => Some(*size),
1722                _ => None,
1723            })
1724            .collect()
1725    }
1726
1727    /// The weight of the first text in a frame.
1728    fn first_weight(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Option<u16> {
1729        framed(widget, theme).iter().find_map(|p| match p {
1730            Primitive::Text { weight, .. } => Some(weight.to_u16()),
1731            _ => None,
1732        })
1733    }
1734
1735    #[test]
1736    fn a_widget_no_longer_decides_its_own_type() {
1737        // Milestone 413. Twelve widgets set their text in a private constant that no theme
1738        // and no caller could reach, and one of them had **drifted two pixels from the
1739        // reference** without anybody being able to see it. Each is checked here at the
1740        // step the reference names, and then moved by a theme — which is the part that was
1741        // impossible before, and the reason the drift went unseen.
1742        let plain = Theme::default();
1743
1744        // A snackbar's content is `bodyMedium` — 14 px, not the 16 the constant said.
1745        assert_eq!(
1746            text_sizes(crate::toast::SnackBar::<Msg>::new("Saved"), &plain),
1747            vec![plain.text.body_medium.size.unwrap()]
1748        );
1749        let mut loud = Theme::default();
1750        loud.widgets.snack_bar.content_text_style = Some(TextStyle::new(30.0));
1751        assert_eq!(
1752            text_sizes(crate::toast::SnackBar::<Msg>::new("Saved"), &loud),
1753            vec![30.0],
1754            "the theme has a say"
1755        );
1756        assert_eq!(
1757            text_sizes(
1758                crate::toast::SnackBar::<Msg>::new("Saved").content_text_style(TextStyle::new(9.0)),
1759                &loud
1760            ),
1761            vec![9.0],
1762            "and the caller has the last word over it"
1763        );
1764
1765        // A key cap is a label, and monospaced.
1766        let kbd = || crate::kbd::Kbd::new("Ctrl");
1767        assert_eq!(
1768            text_sizes(kbd(), &plain),
1769            vec![plain.text.label_medium.size.unwrap()]
1770        );
1771        assert_eq!(
1772            framed(kbd(), &plain).iter().find_map(|p| match p {
1773                Primitive::Text { family, .. } => Some(*family),
1774                _ => None,
1775            }),
1776            Some(Some(frus_core::FontFamily::Monospace)),
1777            "a cap stands for what is printed on a keyboard"
1778        );
1779
1780        // A tree row is a list tile's title.
1781        let tree = || {
1782            crate::tree::Tree::<Msg>::new(|_| -> Msg { unreachable!() })
1783                .node(1, 0, "root", false, false)
1784        };
1785        assert_eq!(
1786            text_sizes(tree(), &plain),
1787            vec![plain.text.body_large.size.unwrap()]
1788        );
1789        assert_eq!(
1790            text_sizes(tree().text_style(TextStyle::new(7.0)), &plain),
1791            vec![7.0],
1792            "said on the widget, whatever order the builders came in"
1793        );
1794    }
1795
1796    #[test]
1797    fn the_reference_names_a_table_s_two_rows_apart() {
1798        // A data table's heading is `titleSmall` and its cells `bodyMedium` — **two
1799        // different steps**, which one `SIZE` constant for the whole widget could not say.
1800        let theme = Theme::default();
1801        let table = crate::table::Table::<Msg>::new(1)
1802            .header(&["Name"])
1803            .row(&["Ada"]);
1804        assert_eq!(
1805            text_sizes(table, &theme),
1806            vec![
1807                theme.text.title_small.size.unwrap(),
1808                theme.text.body_medium.size.unwrap(),
1809            ]
1810        );
1811        // And the heading carries the medium weight the step does.
1812        let heading = crate::table::Table::<Msg>::new(1).header(&["Name"]);
1813        assert_eq!(
1814            first_weight(heading, &theme),
1815            Some(frus_core::FontWeight::Medium.to_u16())
1816        );
1817    }
1818
1819    #[test]
1820    fn a_table_carries_its_own_word_down_to_cells_it_did_not_build_directly() {
1821        // `Table` and `DatePicker` hand their override down the **theme**
1822        // (`Widget::theme_override`) rather than through the cells' fields: a table builds
1823        // cells in half a dozen places and a calendar has five constructors, and a value
1824        // carried down the theme reaches all of them without any of them being taught to
1825        // pass it on.
1826        let theme = Theme::default();
1827        let table = crate::table::Table::<Msg>::new(1)
1828            .header(&["Name"])
1829            .row(&["Ada"])
1830            .heading_text_style(TextStyle::new(21.0))
1831            .data_text_style(TextStyle::new(8.0));
1832        assert_eq!(text_sizes(table, &theme), vec![21.0, 8.0]);
1833        // Saying nothing costs nothing: no override, no theme to allocate.
1834        assert!(Widget::<Msg>::theme_override(
1835            &crate::table::Table::<Msg>::new(1).header(&["Name"]),
1836            &theme
1837        )
1838        .is_none());
1839    }
1840
1841    #[test]
1842    fn the_un_themed_path_reads_the_same_scale_as_the_themed_one() {
1843        // `Widget::style` has no theme — it is what the transparent wrappers ask a child
1844        // when they want its size. It answers from `TextTheme::M3`, the same const
1845        // `Theme::default()` carries, rather than from a fallback constant beside it: two
1846        // numbers that have to agree is the shape the last five milestones were about.
1847        assert_eq!(TextTheme::M3, Theme::default().text);
1848        let kbd = crate::kbd::Kbd::new("Ctrl");
1849        assert_eq!(
1850            Widget::<Msg>::style(&kbd),
1851            Widget::<Msg>::style_themed(&kbd, &Theme::default()),
1852            "the same widget, sized the same way, with and without a theme in hand"
1853        );
1854    }
1855
1856    #[test]
1857    fn the_last_seven_widgets_stop_deciding_their_own_type() {
1858        // Milestone 414, the tail of 413. Five of these seven turned out to be **read**
1859        // from the reference after all, and the note that sent them here said only two
1860        // were — which is the same mistake as the constants themselves, an estimate
1861        // standing in for a look.
1862        let plain = Theme::default();
1863
1864        // Argued: a trail is a *secondary* line of navigation, `bodyMedium`.
1865        let trail = || {
1866            crate::breadcrumb::Breadcrumb::<Msg>::new(|_| -> Msg { unreachable!() }).crumb("Home")
1867        };
1868        assert_eq!(
1869            text_sizes(trail(), &plain),
1870            vec![plain.text.body_medium.size.unwrap()]
1871        );
1872        assert_eq!(
1873            text_sizes(trail().text_style(TextStyle::new(9.0)), &plain),
1874            vec![9.0],
1875            "and a caller can still say otherwise"
1876        );
1877        let mut themed = Theme::default();
1878        themed.widgets.breadcrumb.text_style = Some(TextStyle::new(31.0));
1879        assert_eq!(text_sizes(trail(), &themed), vec![31.0]);
1880    }
1881
1882    #[test]
1883    fn a_bar_titles_at_the_reference_s_weight() {
1884        // The app bar's title was a private `TextStyle::new(22.0).weight(Medium)`: the size
1885        // right and **the weight wrong**, because `titleLarge` is regular. Nothing tested it
1886        // — the one test that looked compared it against the constant it came from, which is
1887        // a tautology, not a check.
1888        let plain = Theme::default();
1889        assert_eq!(
1890            first_weight(crate::navbar::NavigationBar::<Msg>::new("Inbox"), &plain),
1891            Some(frus_core::FontWeight::Regular.to_u16())
1892        );
1893        assert_eq!(
1894            text_sizes(crate::navbar::NavigationBar::<Msg>::new("Inbox"), &plain),
1895            vec![plain.text.title_large.size.unwrap()],
1896            "and a navigation bar titles at 22, not at a private 20"
1897        );
1898    }
1899
1900    #[test]
1901    fn a_theme_that_says_nothing_is_the_absence_of_a_theme() {
1902        // The whole contract of `None`: a fresh theme must carry no opinions at all, or
1903        // every widget's built-in default silently becomes unreachable.
1904        assert_eq!(Theme::default().widgets, WidgetThemes::default());
1905        assert_eq!(WidgetThemes::default().card.elevation, None);
1906        assert_eq!(WidgetThemes::default().divider.height, None);
1907        assert_eq!(WidgetThemes::default().drawer.width, None);
1908        assert_eq!(WidgetThemes::default().ink.color, None);
1909        assert_eq!(WidgetThemes::default().tab_bar.indicator_color, None);
1910        assert_eq!(WidgetThemes::default().chip.height, None);
1911        assert_eq!(WidgetThemes::default().button.height, None);
1912        assert_eq!(WidgetThemes::default().segmented.height, None);
1913        assert_eq!(WidgetThemes::default().icon_button.size, None);
1914        assert_eq!(WidgetThemes::default().snack_bar.content_text_style, None);
1915        assert_eq!(WidgetThemes::default().table.heading_text_style, None);
1916        assert_eq!(WidgetThemes::default().date_picker.day_text_style, None);
1917        assert_eq!(WidgetThemes::default().kbd.text_style, None);
1918        assert_eq!(WidgetThemes::default().breadcrumb.text_style, None);
1919        assert_eq!(WidgetThemes::default().kanban.card_text_style, None);
1920        assert_eq!(WidgetThemes::default().form.bullet_text_style, None);
1921        assert_eq!(WidgetThemes::default().time_picker.dial_text_style, None);
1922        assert_eq!(
1923            WidgetThemes::default().slider.value_indicator_text_style,
1924            None
1925        );
1926    }
1927}