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