use frus_core::{BorderRadius, Color, Insets, ShapeBorder, TextAlign, TextOverflow, TextStyle};
use crate::card::CardVariant;
pub fn resolve_shape(
own: Option<ShapeBorder>,
themed: Option<ShapeBorder>,
radius: Option<BorderRadius>,
fallback: ShapeBorder,
) -> ShapeBorder {
own.or(themed)
.or_else(|| radius.map(ShapeBorder::rounded))
.unwrap_or(fallback)
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct WidgetThemes {
pub alert: AlertTheme,
pub dialog: DialogTheme,
pub action_icons: ActionIconTheme,
pub app_bar: AppBarTheme,
pub autocomplete: AutocompleteTheme,
pub badge: BadgeTheme,
pub banner: BannerTheme,
pub bottom_app_bar: BottomAppBarTheme,
pub bottom_sheet: BottomSheetTheme,
pub breadcrumb: BreadcrumbTheme,
pub button: ButtonTheme,
pub card: CardTheme,
pub checkbox: CheckboxTheme,
pub chip: ChipTheme,
pub date_picker: DatePickerTheme,
pub divider: DividerTheme,
pub drawer: DrawerTheme,
pub list_tile: ListTileTheme,
pub dropdown: DropdownTheme,
pub expansion_tile: ExpansionTileTheme,
pub expansion_panel_list: ExpansionPanelListTheme,
pub fab: FabTheme,
pub form: FormTheme,
pub grid_tile_bar: GridTileBarTheme,
pub icon: IconTheme,
pub icon_button: IconButtonTheme,
pub ink: InkTheme,
pub kanban: KanbanTheme,
pub reorderable: ReorderableTheme,
pub kbd: KbdTheme,
pub menu: MenuTheme,
pub nav_bar: NavBarTheme,
pub nav_rail: NavRailTheme,
pub nav_drawer: NavDrawerTheme,
pub scrollbar: ScrollbarTheme,
pub search_bar: SearchBarTheme,
pub search_view: SearchViewTheme,
pub progress: ProgressTheme,
pub radio: RadioTheme,
pub segmented: SegmentedTheme,
pub slider: SliderTheme,
pub snack_bar: SnackBarTheme,
pub steps: StepsTheme,
pub switch: SwitchTheme,
pub page_selector: PageSelectorTheme,
pub tab_bar: TabBarTheme,
pub table: TableTheme,
pub text: DefaultTextStyle,
pub menu_bar: MenuBarTheme,
pub caption_buttons: CaptionButtonsTheme,
pub title_bar: TitleBarTheme,
pub text_selection: TextSelectionTheme,
pub text_field: TextFieldTheme,
pub time_picker: TimePickerTheme,
pub timeline: TimelineTheme,
pub toggle_buttons: ToggleButtonsTheme,
pub tooltip: TooltipTheme,
pub tree: TreeTheme,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TextSelectionTheme {
pub selection_color: Option<Color>,
pub handle_color: Option<Color>,
pub cursor_color: Option<Color>,
}
impl TextSelectionTheme {
pub fn selection_color_in(&self, theme: &crate::Theme) -> Color {
self.selection_color.unwrap_or(theme.selection)
}
pub fn handle_color_in(&self, theme: &crate::Theme) -> Color {
self.handle_color.unwrap_or(theme.scheme.primary)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BadgeTheme {
pub background_color: Option<Color>,
pub text_color: Option<Color>,
pub text_style: Option<TextStyle>,
pub small_size: Option<f32>,
pub large_size: Option<f32>,
pub padding: Option<f32>,
pub alignment: Option<frus_core::AlignmentGeometry>,
pub offset: Option<(f32, f32)>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CheckboxTheme {
pub fill_color: Option<Color>,
pub check_color: Option<Color>,
pub border_color: Option<Color>,
pub active_border_color: Option<Color>,
pub radius: Option<f32>,
pub label_color: Option<Color>,
pub tap_target: Option<crate::theme::TapTarget>,
pub overlay_color: Option<crate::WidgetStateProperty<Color>>,
pub splash_radius: Option<f32>,
pub visual_density: Option<crate::VisualDensity>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct RadioTheme {
pub selected_color: Option<Color>,
pub border_color: Option<Color>,
pub active_border_color: Option<Color>,
pub label_color: Option<Color>,
pub tap_target: Option<crate::theme::TapTarget>,
pub overlay_color: Option<crate::WidgetStateProperty<Color>>,
pub splash_radius: Option<f32>,
pub visual_density: Option<crate::VisualDensity>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SliderTheme {
pub value_indicator_text_style: Option<TextStyle>,
pub active_track_color: Option<Color>,
pub inactive_track_color: Option<Color>,
pub thumb_color: Option<Color>,
pub thumb_border_color: Option<Color>,
pub thumb_elevation: Option<f32>,
pub pressed_thumb_elevation: Option<f32>,
pub track_height: Option<f32>,
pub secondary_active_track_color: Option<Color>,
pub active_tick_mark_color: Option<Color>,
pub inactive_tick_mark_color: Option<Color>,
pub disabled_active_track_color: Option<Color>,
pub disabled_inactive_track_color: Option<Color>,
pub disabled_thumb_color: Option<Color>,
pub overlay_color: Option<crate::WidgetStateProperty<Color>>,
pub value_indicator_color: Option<Color>,
pub show_value_indicator: Option<crate::ShowValueIndicator>,
pub allowed_interaction: Option<crate::SliderInteraction>,
pub track_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderTrackShape>>,
pub range_track_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderTrackShape>>,
pub tick_mark_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderTickMarkShape>>,
pub range_tick_mark_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderTickMarkShape>>,
pub thumb_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderThumbShape>>,
pub range_thumb_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderThumbShape>>,
pub overlay_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderOverlayShape>>,
pub value_indicator_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderValueIndicatorShape>>,
pub range_value_indicator_shape:
Option<crate::slidershape::SliderShape<dyn crate::slidershape::SliderValueIndicatorShape>>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SwitchTheme {
pub track_color: Option<Color>,
pub inactive_track_color: Option<Color>,
pub thumb_color: Option<Color>,
pub inactive_thumb_color: Option<Color>,
pub thumb_colors: Option<crate::WidgetStateProperty<Color>>,
pub track_colors: Option<crate::WidgetStateProperty<Color>>,
pub track_outline_color: Option<crate::WidgetStateProperty<Color>>,
pub track_outline_width: Option<f32>,
pub overlay_color: Option<crate::WidgetStateProperty<Color>>,
pub splash_radius: Option<f32>,
pub padding: Option<Insets>,
pub icon_color: Option<Color>,
pub inactive_icon_color: Option<Color>,
pub tap_target: Option<crate::theme::TapTarget>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DefaultTextStyle {
pub style: TextStyle,
pub align: Option<TextAlign>,
pub soft_wrap: Option<bool>,
pub overflow: Option<TextOverflow>,
pub max_lines: Option<usize>,
pub selectable: bool,
}
impl DefaultTextStyle {
pub const NONE: Self = Self {
style: TextStyle::NONE,
align: None,
soft_wrap: None,
overflow: None,
max_lines: None,
selectable: false,
};
pub const fn from_text_style(style: TextStyle) -> Self {
Self {
style,
..Self::NONE
}
}
#[must_use]
pub fn merge(self, over: Self) -> Self {
Self {
style: self.style.merge(over.style),
align: over.align.or(self.align),
soft_wrap: over.soft_wrap.or(self.soft_wrap),
overflow: over.overflow.or(self.overflow),
max_lines: over.max_lines.or(self.max_lines),
selectable: self.selectable || over.selectable,
}
}
pub fn around<Msg: 'static>(
self,
child: impl crate::Widget<Msg> + 'static,
) -> crate::Themed<Msg> {
crate::Themed::tweak(move |t| t.widgets.text = t.widgets.text.merge(self), child)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct IconTheme {
pub color: Option<Color>,
pub size: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AppBarTheme {
pub center_title: Option<bool>,
pub title_style: Option<TextStyle>,
pub background: Option<Color>,
pub foreground: Option<Color>,
pub elevation: Option<f32>,
pub height: Option<f32>,
pub shadow_color: Option<Color>,
pub surface_tint: Option<Color>,
pub shape: Option<ShapeBorder>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TextFieldTheme {
pub fill: Option<Color>,
pub border_color: Option<Color>,
pub focused_border_color: Option<Color>,
pub error_color: Option<Color>,
pub error_hover_color: Option<Color>,
pub text_color: Option<Color>,
pub label_color: Option<Color>,
pub focused_label_color: Option<Color>,
pub helper_color: Option<Color>,
pub icon_color: Option<Color>,
pub handle_color: Option<Color>,
pub radius: Option<f32>,
pub border_width: Option<f32>,
pub focused_border_width: Option<f32>,
pub floating_label_behavior: Option<crate::FloatingLabelBehavior>,
pub floating_label_alignment: Option<crate::FloatingLabelAlignment>,
pub cursor_color: Option<Color>,
pub cursor_error_color: Option<Color>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ButtonTheme {
pub elevated_style: Option<crate::ButtonStyle>,
pub filled_style: Option<crate::ButtonStyle>,
pub outlined_style: Option<crate::ButtonStyle>,
pub text_button_style: Option<crate::ButtonStyle>,
pub shape: Option<ShapeBorder>,
pub color: Option<Color>,
pub label_color: Option<Color>,
pub label_style: Option<TextStyle>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub radius: Option<BorderRadius>,
pub padding: Option<f32>,
pub height: Option<f32>,
pub min_width: Option<f32>,
pub elevation: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CardTheme {
pub shape: Option<ShapeBorder>,
pub variant: Option<CardVariant>,
pub elevation: Option<f32>,
pub color: Option<Color>,
pub radius: Option<BorderRadius>,
pub margin: Option<f32>,
pub padding: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ChipTheme {
pub shape: Option<ShapeBorder>,
pub color: Option<Color>,
pub selected_color: Option<Color>,
pub label_color: Option<Color>,
pub selected_label_color: Option<Color>,
pub label_style: Option<TextStyle>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub radius: Option<BorderRadius>,
pub padding: Option<f32>,
pub label_padding: Option<f32>,
pub height: Option<f32>,
pub icon_size: Option<f32>,
pub show_checkmark: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ListTileTheme {
pub tile_color: Option<Color>,
pub selected_tile_color: Option<Color>,
pub selected_color: Option<Color>,
pub icon_color: Option<Color>,
pub text_color: Option<Color>,
pub shape: Option<ShapeBorder>,
pub content_padding: Option<Insets>,
pub title_style: Option<TextStyle>,
pub subtitle_style: Option<TextStyle>,
pub title_gap: Option<f32>,
pub min_leading_width: Option<f32>,
pub min_height: Option<f32>,
pub dense: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ProgressTheme {
pub color: Option<Color>,
pub linear_track_color: Option<Color>,
pub linear_min_height: Option<f32>,
pub circular_track_color: Option<Color>,
pub border_radius: Option<BorderRadius>,
pub stop_indicator_color: Option<Color>,
pub stop_indicator_radius: Option<f32>,
pub track_gap: Option<f32>,
pub year2023: Option<bool>,
pub stroke_width: Option<f32>,
pub stroke_align: Option<f32>,
pub stroke_cap: Option<frus_core::StrokeCap>,
pub circular_size: Option<f32>,
pub circular_track_padding: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TooltipTheme {
pub background: Option<Color>,
pub text_style: Option<TextStyle>,
pub text_color: Option<Color>,
pub text_align: Option<TextAlign>,
pub padding: Option<Insets>,
pub shape: Option<ShapeBorder>,
pub radius: Option<f32>,
pub max_width: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ExpansionPanelListTheme {
pub background: Option<Color>,
pub radius: Option<f32>,
pub gap: Option<f32>,
pub divider_color: Option<Color>,
pub elevation: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ExpansionTileTheme {
pub background: Option<Color>,
pub collapsed_background: Option<Color>,
pub text_color: Option<Color>,
pub collapsed_text_color: Option<Color>,
pub icon_color: Option<Color>,
pub collapsed_icon_color: Option<Color>,
pub tile_padding: Option<Insets>,
pub children_padding: Option<Insets>,
pub shape: Option<ShapeBorder>,
pub collapsed_shape: Option<ShapeBorder>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FabTheme {
pub background: Option<Color>,
pub foreground: Option<Color>,
pub elevation: Option<f32>,
pub hover_elevation: Option<f32>,
pub shape: Option<ShapeBorder>,
pub radius: Option<f32>,
pub icon_size: Option<f32>,
pub label_style: Option<TextStyle>,
pub extended_padding: Option<f32>,
pub extended_gap: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ActionIconTheme {
pub back: Option<crate::IconData>,
pub close: Option<crate::IconData>,
pub drawer: Option<crate::IconData>,
pub end_drawer: Option<crate::IconData>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DividerTheme {
pub height: Option<f32>,
pub thickness: Option<f32>,
pub color: Option<Color>,
pub indent: Option<f32>,
pub end_indent: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DrawerTheme {
pub shape: Option<ShapeBorder>,
pub end_shape: Option<ShapeBorder>,
pub width: Option<f32>,
pub background_color: Option<Color>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub radius: Option<f32>,
pub elevation: Option<f32>,
pub shadow_color: Option<Color>,
pub scrim_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SegmentedTheme {
pub selected_color: Option<Color>,
pub label_color: Option<Color>,
pub selected_label_color: Option<Color>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub radius: Option<f32>,
pub height: Option<f32>,
pub padding: Option<f32>,
pub label_style: Option<TextStyle>,
pub icon_size: Option<f32>,
pub show_selected_icon: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TabBarTheme {
pub variant: Option<crate::tabs::TabBarVariant>,
pub indicator_color: Option<Color>,
pub indicator_weight: Option<f32>,
pub label_color: Option<Color>,
pub unselected_label_color: Option<Color>,
pub label_style: Option<TextStyle>,
pub divider_color: Option<Color>,
pub divider_height: Option<f32>,
pub label_padding: Option<f32>,
pub tab_height: Option<f32>,
pub alignment: Option<crate::tabs::TabAlignment>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct PageSelectorTheme {
pub size: Option<f32>,
pub gap: Option<f32>,
pub color: Option<Color>,
pub border_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct IconButtonTheme {
pub size: Option<f32>,
pub icon_size: Option<f32>,
pub color: Option<Color>,
pub icon_color: Option<Color>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub radius: Option<BorderRadius>,
pub tap_target: Option<crate::theme::TapTarget>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct InkTheme {
pub color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SnackBarTheme {
pub shape: Option<ShapeBorder>,
pub behavior: Option<crate::toast::SnackBarBehavior>,
pub inset_padding: Option<Insets>,
pub width: Option<f32>,
pub content_text_style: Option<TextStyle>,
pub action_text_style: Option<TextStyle>,
pub background_color: Option<Color>,
pub text_color: Option<Color>,
pub action_text_color: Option<Color>,
pub close_icon_color: Option<Color>,
pub accent_color: Option<Color>,
pub success_color: Option<Color>,
pub radius: Option<f32>,
pub elevation: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct MenuTheme {
pub text_style: Option<TextStyle>,
pub background: Option<Color>,
pub shape: Option<ShapeBorder>,
pub radius: Option<f32>,
pub elevation: Option<f32>,
pub shadow_color: Option<Color>,
pub padding: Option<Insets>,
pub item_padding: Option<Insets>,
pub item_height: Option<f32>,
pub border: Option<frus_core::BorderSide>,
pub divider_height: Option<f32>,
}
impl MenuTheme {
pub fn or(self, other: MenuTheme) -> MenuTheme {
MenuTheme {
text_style: self.text_style.or(other.text_style),
background: self.background.or(other.background),
shape: self.shape.or(other.shape),
radius: self.radius.or(other.radius),
elevation: self.elevation.or(other.elevation),
shadow_color: self.shadow_color.or(other.shadow_color),
padding: self.padding.or(other.padding),
item_padding: self.item_padding.or(other.item_padding),
item_height: self.item_height.or(other.item_height),
border: self.border.or(other.border),
divider_height: self.divider_height.or(other.divider_height),
}
}
pub fn desktop(theme: &crate::Theme) -> MenuTheme {
MenuTheme {
text_style: Some(TextStyle::new(13.0)),
background: None,
shape: None,
radius: Some(6.0),
elevation: Some(2.0),
shadow_color: None,
padding: Some(Insets::new(4.0, 0.0, 4.0, 0.0)),
item_padding: Some(Insets::new(0.0, 12.0, 0.0, 12.0)),
item_height: Some(26.0),
border: Some(frus_core::BorderSide::new(
theme.scheme.outline_variant,
1.0,
)),
divider_height: Some(9.0),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CaptionButtonsTheme {
pub glyph_color: Option<Color>,
pub inactive_glyph_color: Option<Color>,
pub hover_color: Option<Color>,
pub pressed_color: Option<Color>,
pub close_hover_color: Option<Color>,
pub close_pressed_color: Option<Color>,
pub close_glyph_hover_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TitleBarTheme {
pub background: Option<Color>,
pub foreground: Option<Color>,
pub inactive_background: Option<Color>,
pub inactive_foreground: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct MenuBarTheme {
pub height: Option<f32>,
pub background: Option<Color>,
pub padding: Option<Insets>,
pub item_padding: Option<Insets>,
pub text_style: Option<TextStyle>,
pub foreground: Option<Color>,
pub highlight: Option<Color>,
pub item_radius: Option<f32>,
pub item_inset: Option<f32>,
pub menus: MenuTheme,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DropdownTheme {
pub text_style: Option<TextStyle>,
pub menu_background: Option<Color>,
pub menu_shape: Option<ShapeBorder>,
pub menu_radius: Option<f32>,
pub menu_elevation: Option<f32>,
pub menu_shadow_color: Option<Color>,
pub menu_padding: Option<Insets>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AutocompleteTheme {
pub text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TableTheme {
pub heading_text_style: Option<TextStyle>,
pub data_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DatePickerTheme {
pub day_text_style: Option<TextStyle>,
pub weekday_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ScrollbarTheme {
pub thickness: Option<f32>,
pub margin: Option<f32>,
pub min_thumb_length: Option<f32>,
pub radius: Option<f32>,
pub thumb_color: Option<Color>,
pub opacity: Option<f32>,
pub hover_opacity: Option<f32>,
pub drag_opacity: Option<f32>,
pub interactive: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct NavBarTheme {
pub height: Option<f32>,
pub padding: Option<Insets>,
pub background: Option<Color>,
pub title_style: Option<TextStyle>,
pub divider_color: Option<Color>,
pub divider_thickness: Option<f32>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NavRailTheme {
pub indicator_shape: Option<frus_core::ShapeBorder>,
pub overlay_color: Option<crate::widgetstate::WidgetStateProperty<frus_core::Color>>,
pub label_text_style: Option<TextStyle>,
pub badge_text_style: Option<TextStyle>,
pub background_color: Option<Color>,
pub indicator_color: Option<Color>,
pub selected_icon_color: Option<Color>,
pub unselected_icon_color: Option<Color>,
pub selected_label_color: Option<Color>,
pub unselected_label_color: Option<Color>,
pub icon_size: Option<f32>,
pub use_indicator: Option<bool>,
pub elevation: Option<f32>,
pub bar_background_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SearchBarTheme {
pub background_color: Option<Color>,
pub shadow_color: Option<Color>,
pub elevation: Option<f32>,
pub shape: Option<frus_core::ShapeBorder>,
pub radius: Option<BorderRadius>,
pub side: Option<frus_core::BorderSide>,
pub padding: Option<frus_core::Insets>,
pub min_width: Option<f32>,
pub max_width: Option<f32>,
pub height: Option<f32>,
pub text_style: Option<TextStyle>,
pub hint_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SearchViewTheme {
pub background_color: Option<Color>,
pub elevation: Option<f32>,
pub shape: Option<frus_core::ShapeBorder>,
pub radius: Option<BorderRadius>,
pub side: Option<frus_core::BorderSide>,
pub divider_color: Option<Color>,
pub header_height: Option<f32>,
pub header_text_style: Option<TextStyle>,
pub header_hint_style: Option<TextStyle>,
pub min_width: Option<f32>,
pub min_height: Option<f32>,
pub bar_padding: Option<frus_core::Insets>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NavDrawerTheme {
pub background_color: Option<Color>,
pub elevation: Option<f32>,
pub shadow_color: Option<Color>,
pub indicator_color: Option<Color>,
pub indicator_shape: Option<frus_core::ShapeBorder>,
pub indicator_size: Option<(f32, f32)>,
pub overlay_color: Option<crate::widgetstate::WidgetStateProperty<frus_core::Color>>,
pub tile_height: Option<f32>,
pub tile_padding: Option<frus_core::Insets>,
pub icon_size: Option<f32>,
pub label_text_style: Option<TextStyle>,
pub badge_text_style: Option<TextStyle>,
pub selected_color: Option<Color>,
pub unselected_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AlertTheme {
pub title_text_style: Option<TextStyle>,
pub content_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BottomAppBarTheme {
pub color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BottomSheetTheme {
pub shape: Option<ShapeBorder>,
pub radius: Option<f32>,
pub background_color: Option<Color>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BannerTheme {
pub color: Option<Color>,
pub surface_tint: Option<Color>,
pub shadow_color: Option<Color>,
pub divider_color: Option<Color>,
pub elevation: Option<f32>,
pub padding: Option<Insets>,
pub margin: Option<Insets>,
pub leading_padding: Option<Insets>,
pub content_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DialogTheme {
pub color: Option<Color>,
pub elevation: Option<f32>,
pub shadow_color: Option<Color>,
pub surface_tint: Option<Color>,
pub shape: Option<ShapeBorder>,
pub inset_padding: Option<Insets>,
pub icon_color: Option<Color>,
pub title_text_style: Option<TextStyle>,
pub content_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct StepsTheme {
pub label_text_style: Option<TextStyle>,
pub index_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TreeTheme {
pub text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TimelineTheme {
pub title_text_style: Option<TextStyle>,
pub detail_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BreadcrumbTheme {
pub text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ReorderableTheme {
pub handle_icon: Option<crate::IconData>,
pub handle_color: Option<Color>,
pub handle_icon_size: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct KanbanTheme {
pub card_text_style: Option<TextStyle>,
pub column_title_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FormTheme {
pub bullet_text_style: Option<TextStyle>,
pub summary_title_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TimePickerTheme {
pub dial_text_style: Option<TextStyle>,
pub preview_text_style: Option<TextStyle>,
pub help_text_style: Option<TextStyle>,
pub range_label_text_style: Option<TextStyle>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct KbdTheme {
pub text_style: Option<TextStyle>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ToggleButtonsTheme {
pub color: Option<Color>,
pub selected_color: Option<Color>,
pub disabled_color: Option<Color>,
pub fill_color: Option<crate::WidgetStateProperty<Color>>,
pub border_color: Option<Color>,
pub selected_border_color: Option<Color>,
pub disabled_border_color: Option<Color>,
pub border_width: Option<f32>,
pub border_radius: Option<BorderRadius>,
pub text_style: Option<TextStyle>,
pub min_width: Option<f32>,
pub min_height: Option<f32>,
pub max_width: Option<f32>,
pub max_height: Option<f32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GridTileBarTheme {
pub background_color: Option<Color>,
pub foreground_color: Option<Color>,
pub title_style: Option<TextStyle>,
pub subtitle_style: Option<TextStyle>,
pub height: Option<f32>,
pub two_line_height: Option<f32>,
}
#[cfg(test)]
mod tests {
#[test]
fn a_shape_outranks_a_radius_and_a_caller_outranks_both() {
use super::{resolve_shape, BorderRadius, ShapeBorder};
let fallback = ShapeBorder::stadium();
let told = ShapeBorder::circle();
let themed = ShapeBorder::beveled(3.0);
assert_eq!(resolve_shape(None, None, None, fallback), fallback);
assert_eq!(
resolve_shape(None, None, Some(BorderRadius::uniform(4.0)), fallback),
ShapeBorder::rounded(4.0),
"a plain radius still speaks"
);
assert_eq!(
resolve_shape(
None,
Some(themed),
Some(BorderRadius::uniform(4.0)),
fallback
),
themed,
"and a shape beside it outranks it"
);
assert_eq!(
resolve_shape(
Some(told),
Some(themed),
Some(BorderRadius::uniform(4.0)),
fallback
),
told,
"and the caller outranks the theme"
);
}
use super::*;
use crate::card::{Card, CardVariant, CARD_MARGIN};
use crate::divider::{Divider, DIVIDER_SPACE};
use crate::flex::Flex;
use crate::runtime::Runtime;
use crate::theme::{TextTheme, Theme};
use crate::ui::build_ui;
use crate::widget::Widget;
use frus_core::{Color, Primitive, Rect, Size};
#[derive(Clone, Debug, PartialEq)]
enum Msg {}
fn framed(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Vec<Primitive> {
let tree = Flex::column().width(200.0).height(100.0).child(widget);
build_ui(&tree, Size::new(200.0, 100.0), &Runtime::default(), theme)
.scene()
.primitives()
.to_vec()
}
fn painted_rect(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Rect {
framed(widget, theme)
.iter()
.find_map(|p| match p {
Primitive::Rect { rect, blur, .. } if *blur == 0.0 => Some(*rect),
_ => None,
})
.expect("the widget paints a box")
}
#[test]
fn the_theme_reaches_layout_and_not_only_paint() {
let plain = Theme::default();
let mut thin = Theme::default();
thin.widgets.divider.height = Some(1.0);
let tall = painted_rect(Divider::new(), &plain);
let thin_line = painted_rect(Divider::new(), &thin);
assert!(
tall.y > thin_line.y,
"the themed divider's box is shorter: {tall:?} against {thin_line:?}"
);
assert_eq!(tall.height, 1.0, "the line itself is unchanged");
assert_eq!(thin_line.height, 1.0);
}
#[test]
fn the_caller_outranks_the_theme_and_the_theme_outranks_the_framework() {
let plain = Theme::default();
let mut themed = Theme::default();
themed.widgets.card.margin = Some(20.0);
let x = |card: Card<Msg>, theme: &Theme| painted_rect(card, theme).x;
assert_eq!(x(Card::new(), &plain), CARD_MARGIN, "the framework's");
assert_eq!(x(Card::new(), &themed), 20.0, "the theme's");
assert_eq!(
x(Card::new().margin(2.0), &themed),
2.0,
"the caller's, over the theme's"
);
}
#[test]
fn a_theme_can_change_what_an_untold_widget_is() {
let mut flat = Theme::default();
flat.widgets.card.variant = Some(CardVariant::Filled);
let shadows = |theme: &Theme| crate::shadowprobe::casts(&framed(Card::<Msg>::new(), theme));
assert_eq!(shadows(&Theme::default()), 1, "elevated by default");
assert_eq!(shadows(&flat), 0, "flat because the theme says so");
}
#[test]
fn the_ink_colour_is_themeable_even_where_a_widget_computes_its_own() {
let mine = Color::rgb8(255, 0, 128);
let mut theme = Theme::default();
theme.widgets.ink.color = Some(mine);
let button = crate::button::Button::<()>::new("Go").on_press(());
assert_eq!(
Widget::<()>::ink(&button, &theme).map(|i| i.color),
Some(mine)
);
assert_eq!(crate::ink::default_splash(&theme), mine);
}
#[test]
fn changing_a_theme_invalidates_the_layout_cache() {
let runtime = Runtime::default();
let tree = Flex::<Msg>::column()
.width(200.0)
.height(100.0)
.child(Divider::new());
let plain = Theme::default();
let mut thin = Theme::default();
thin.widgets.divider.height = Some(1.0);
let height = |theme: &Theme| {
build_ui(&tree, Size::new(200.0, 100.0), &runtime, theme)
.scene()
.primitives()
.iter()
.find_map(|p| match p {
Primitive::Rect { rect, .. } => Some(rect.y),
_ => None,
})
.expect("a line")
};
let first = height(&plain);
let second = height(&thin);
assert_ne!(
first, second,
"a themed size must not be served from the cache of an unthemed one"
);
assert_eq!(height(&plain), first, "and back again");
}
#[test]
fn the_defaults_are_still_reachable_through_an_empty_theme() {
let plain = Theme::default();
assert_eq!(
Widget::<Msg>::style_themed(&Divider::new(), &plain).height,
frus_layout::Dimension::Length(DIVIDER_SPACE)
);
}
fn text_sizes(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Vec<f32> {
framed(widget, theme)
.iter()
.filter_map(|p| match p {
Primitive::Text { size, .. } => Some(*size),
_ => None,
})
.collect()
}
fn first_weight(widget: impl Widget<Msg> + 'static, theme: &Theme) -> Option<u16> {
framed(widget, theme).iter().find_map(|p| match p {
Primitive::Text { weight, .. } => Some(weight.to_u16()),
_ => None,
})
}
#[test]
fn a_widget_no_longer_decides_its_own_type() {
let plain = Theme::default();
assert_eq!(
text_sizes(crate::toast::SnackBar::<Msg>::new("Saved"), &plain),
vec![plain.text.body_medium.size.unwrap()]
);
let mut loud = Theme::default();
loud.widgets.snack_bar.content_text_style = Some(TextStyle::new(30.0));
assert_eq!(
text_sizes(crate::toast::SnackBar::<Msg>::new("Saved"), &loud),
vec![30.0],
"the theme has a say"
);
assert_eq!(
text_sizes(
crate::toast::SnackBar::<Msg>::new("Saved").content_text_style(TextStyle::new(9.0)),
&loud
),
vec![9.0],
"and the caller has the last word over it"
);
let kbd = || crate::kbd::Kbd::new("Ctrl");
assert_eq!(
text_sizes(kbd(), &plain),
vec![plain.text.label_medium.size.unwrap()]
);
assert_eq!(
framed(kbd(), &plain).iter().find_map(|p| match p {
Primitive::Text { family, .. } => Some(*family),
_ => None,
}),
Some(Some(frus_core::FontFamily::Monospace)),
"a cap stands for what is printed on a keyboard"
);
let tree = || {
crate::tree::Tree::<Msg>::new(|_| -> Msg { unreachable!() })
.node(1, 0, "root", false, false)
};
assert_eq!(
text_sizes(tree(), &plain),
vec![plain.text.body_large.size.unwrap()]
);
assert_eq!(
text_sizes(tree().text_style(TextStyle::new(7.0)), &plain),
vec![7.0],
"said on the widget, whatever order the builders came in"
);
}
#[test]
fn the_reference_names_a_table_s_two_rows_apart() {
let theme = Theme::default();
let table = crate::table::Table::<Msg>::new(1)
.header(&["Name"])
.row(&["Ada"]);
assert_eq!(
text_sizes(table, &theme),
vec![
theme.text.title_small.size.unwrap(),
theme.text.body_medium.size.unwrap(),
]
);
let heading = crate::table::Table::<Msg>::new(1).header(&["Name"]);
assert_eq!(
first_weight(heading, &theme),
Some(frus_core::FontWeight::Medium.to_u16())
);
}
#[test]
fn a_table_carries_its_own_word_down_to_cells_it_did_not_build_directly() {
let theme = Theme::default();
let table = crate::table::Table::<Msg>::new(1)
.header(&["Name"])
.row(&["Ada"])
.heading_text_style(TextStyle::new(21.0))
.data_text_style(TextStyle::new(8.0));
assert_eq!(text_sizes(table, &theme), vec![21.0, 8.0]);
assert!(Widget::<Msg>::theme_override(
&crate::table::Table::<Msg>::new(1).header(&["Name"]),
&theme
)
.is_none());
}
#[test]
fn the_un_themed_path_reads_the_same_scale_as_the_themed_one() {
assert_eq!(TextTheme::M3, Theme::default().text);
let kbd = crate::kbd::Kbd::new("Ctrl");
assert_eq!(
Widget::<Msg>::style(&kbd),
Widget::<Msg>::style_themed(&kbd, &Theme::default()),
"the same widget, sized the same way, with and without a theme in hand"
);
}
#[test]
fn the_last_seven_widgets_stop_deciding_their_own_type() {
let plain = Theme::default();
let trail = || {
crate::breadcrumb::Breadcrumb::<Msg>::new(|_| -> Msg { unreachable!() }).crumb("Home")
};
assert_eq!(
text_sizes(trail(), &plain),
vec![plain.text.body_medium.size.unwrap()]
);
assert_eq!(
text_sizes(trail().text_style(TextStyle::new(9.0)), &plain),
vec![9.0],
"and a caller can still say otherwise"
);
let mut themed = Theme::default();
themed.widgets.breadcrumb.text_style = Some(TextStyle::new(31.0));
assert_eq!(text_sizes(trail(), &themed), vec![31.0]);
}
#[test]
fn a_bar_titles_at_the_reference_s_weight() {
let plain = Theme::default();
assert_eq!(
first_weight(crate::navbar::NavigationBar::<Msg>::new("Inbox"), &plain),
Some(frus_core::FontWeight::Regular.to_u16())
);
assert_eq!(
text_sizes(crate::navbar::NavigationBar::<Msg>::new("Inbox"), &plain),
vec![plain.text.title_large.size.unwrap()],
"and a navigation bar titles at 22, not at a private 20"
);
}
#[test]
fn a_theme_that_says_nothing_is_the_absence_of_a_theme() {
assert_eq!(Theme::default().widgets, WidgetThemes::default());
assert_eq!(WidgetThemes::default().card.elevation, None);
assert_eq!(WidgetThemes::default().divider.height, None);
assert_eq!(WidgetThemes::default().drawer.width, None);
assert_eq!(WidgetThemes::default().ink.color, None);
assert_eq!(WidgetThemes::default().tab_bar.indicator_color, None);
assert_eq!(WidgetThemes::default().chip.height, None);
assert_eq!(WidgetThemes::default().button.height, None);
assert_eq!(WidgetThemes::default().segmented.height, None);
assert_eq!(WidgetThemes::default().icon_button.size, None);
assert_eq!(WidgetThemes::default().snack_bar.content_text_style, None);
assert_eq!(WidgetThemes::default().table.heading_text_style, None);
assert_eq!(WidgetThemes::default().date_picker.day_text_style, None);
assert_eq!(WidgetThemes::default().kbd.text_style, None);
assert_eq!(WidgetThemes::default().breadcrumb.text_style, None);
assert_eq!(WidgetThemes::default().kanban.card_text_style, None);
assert_eq!(WidgetThemes::default().form.bullet_text_style, None);
assert_eq!(WidgetThemes::default().time_picker.dial_text_style, None);
assert_eq!(
WidgetThemes::default().slider.value_indicator_text_style,
None
);
}
}