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