1pub mod appbar;
71pub mod badge;
72pub mod button;
73pub mod button_group;
74pub mod card;
75pub mod card_list;
76pub mod carousel;
77pub mod checkbox;
78pub mod chips;
79pub mod date_picker;
80pub mod dialog;
81pub mod dismissible;
82pub mod divider;
83pub mod dropdown;
84pub mod expandable_list;
85pub mod fab;
86pub mod fab_menu;
87pub mod icon_button;
88pub mod icons;
89pub mod interaction;
90pub mod list_item;
91pub mod list_view;
92pub mod loading_indicator;
93pub mod menu;
94pub mod navbar;
95pub mod navigation_drawer;
96pub mod navigation_rail;
97pub mod overlay;
98mod press;
99pub mod progress;
100pub mod radio;
101pub mod refresh_indicator;
102pub mod search;
103pub mod segmented_button;
104pub mod selection;
105pub mod shapes;
106pub mod sheet;
107pub mod side_sheet;
108pub mod slider;
109pub mod snackbar;
110pub mod split_button;
111pub mod state_layer;
112pub mod switch;
113pub mod tabs;
114pub mod text_field;
115pub mod time_picker;
116pub mod toggle_button;
117mod tokens;
118pub mod toolbar;
119pub mod tooltip;
120
121pub use appbar::{
122 AppBar, AppBarCollapse, AppBarDensity, AppBarMetrics, AppBarShapeFamily, AppBarVariant,
123 AppBarView, AppBarWidget, BottomAppBar, BottomAppBarView, BottomAppBarWidget, SearchAppBar,
124 SliverAppBar, SliverAppBarView, SliverAppBarWidget, app_bar, bottom_app_bar, search_app_bar,
125 sliver_app_bar,
126};
127pub use badge::{BadgeAlignment, BadgeView, BadgeWidget, badge};
128pub use button::{
129 Button, ButtonDecoration, ButtonShape, ButtonSize, ButtonSurface, ButtonVariant, ButtonView,
130 ButtonWidget, ContentMetrics, DecorationOutcome, GradientAlignment, GradientButtonDecoration,
131 GradientProperty, GradientSpec, GradientStates, IconAlignment, LinearGradientSpec,
132 OverflowObserver, OverflowStrategy, RadialGradientSpec, SweepGradientSpec, button,
133 button_with_icon, constant_gradient, elevated_button, filled_button, implied_stops,
134 outlined_button, text_button, tonal_button,
135};
136pub use button_group::{
137 ButtonGroup, ButtonGroupAction, ButtonGroupDensity, ButtonGroupDirection, ButtonGroupOverflow,
138 ButtonGroupType, ButtonGroupView, ButtonGroupWidget, button_group, button_group_action,
139 button_group_actions, button_group_icon_action,
140};
141pub use card::{
142 CardVariant, CardView, CardWidget, card, elevated_card, filled_card, outlined_card,
143};
144pub use card_list::{
145 CARD_LIST_GAP, CARD_LIST_INNER_RADIUS, CARD_LIST_ITEM_PADDING, CARD_LIST_OUTER_RADIUS,
146 CardListView, CardListWidget, CardPosition, card_list, card_list_items, card_position,
147 card_radii,
148};
149pub use carousel::{
150 CAROUSEL_ITEM_PADDING, CAROUSEL_ITEM_RADIUS, CarouselAxis, CarouselChange, CarouselLayout,
151 CarouselView, CarouselWidget, HeroAlignment, UNCONTAINED_ITEM_EXTENT,
152 UNCONTAINED_SHRINK_EXTENT, carousel, contained_carousel, hero_carousel, uncontained_carousel,
153};
154pub use checkbox::{Checkbox, CheckboxView, CheckboxWidget, checkbox, tristate_checkbox};
155pub use chips::{
156 AssistChip, AssistChipView, AssistChipWidget, FilterChip, FilterChipView, FilterChipWidget,
157 InputChip, InputChipView, InputChipWidget, SuggestionChip, SuggestionChipView,
158 SuggestionChipWidget, assist_chip, filter_chip, input_chip, suggestion_chip,
159};
160pub use date_picker::{
161 ACTIONS_MIN_HEIGHT, ARROW_ICON_SIZE, ARROW_PADDING, CALENDAR_PADDING, CALENDAR_WIDTH,
162 CalendarDatePicker, CalendarDatePickerWidget, DAY_GRID_TOP_PADDING, DAY_ROW_HEIGHT, DAY_SIZE,
163 DAYS_PER_WEEK, DIALOG_PORTRAIT_CALENDAR_WIDTH, DIALOG_PORTRAIT_INPUT_WIDTH,
164 DISABLED_DAY_OPACITY, DateInputError, DateInputField, DateInputFieldWidget, DatePickerDialog,
165 DatePickerEntryMode, DatePickerMode, DatePickerState, DatePickerStrings, DateRange,
166 HEADER_PORTRAIT_HEIGHT, MAX_DAY_PICKER_HEIGHT, MAX_DAY_PICKER_ROW_COUNT, MAX_YEAR, MIN_YEAR,
167 MONTH_NAV_BUTTONS_WIDTH, MONTH_SCROLL_DURATION, MaterialDate, MonthGrid, OnDatePickerChange,
168 RANGE_HIGHLIGHT_ALPHA, SUB_HEADER_HEIGHT, SUB_HEADER_START_INSET, WEEKDAY_ROW_HEIGHT,
169 YEAR_COLUMN_COUNT, YEAR_GRID_PADDING, YEAR_ROW_HEIGHT, YEAR_ROW_SPACING, calendar_date_picker,
170 date_input_field, date_picker_dialog, days_in_month, is_leap_year, parse_bounded,
171 show_date_picker,
172};
173pub use dialog::{
174 DialogView, FullScreenDialogView, SelectionDialogView, dialog, full_screen_dialog,
175 selection_dialog, show_dialog, show_full_screen_dialog, show_selection_dialog,
176};
177pub use dismissible::{
178 DISMISS_ACTION_GAP, DISMISS_BACKGROUND_RADIUS, DISMISS_COLLAPSE_HANDOFF,
179 DISMISS_FLING_VELOCITY, DISMISS_FLY_OVERSHOOT, DISMISS_ICON_SIZE, DISMISS_THRESHOLD,
180 DismissBackground, DismissDirection, DismissibleView, DismissibleWidget, dismiss_background,
181 dismissible, speed_multiplier,
182};
183pub use divider::{DividerView, DividerWidget, divider};
184pub use dropdown::{
185 DROPDOWN_CHIP_H_PADDING, DROPDOWN_CHIP_ICON_SIZE, DROPDOWN_CHIP_LABEL_GAP,
186 DROPDOWN_CHIP_RADIUS, DROPDOWN_CHIP_RUN_SPACING, DROPDOWN_CHIP_SPACING,
187 DROPDOWN_CHIP_V_PADDING, DROPDOWN_CONTENT_PADDING, DROPDOWN_EMPTY_TEXT,
188 DROPDOWN_FIELD_H_PADDING, DROPDOWN_FIELD_HOVER_ALPHA, DROPDOWN_FIELD_LOADING_SIZE,
189 DROPDOWN_FIELD_LOADING_STROKE, DROPDOWN_FIELD_PRESSED_ALPHA, DROPDOWN_FIELD_V_PADDING,
190 DROPDOWN_HINT, DROPDOWN_ICON_GAP, DROPDOWN_ICON_SIZE, DROPDOWN_ITEM_GAP,
191 DROPDOWN_ITEM_H_PADDING, DROPDOWN_ITEM_HOVER_RADIUS, DROPDOWN_ITEM_INNER_RADIUS,
192 DROPDOWN_ITEM_OUTER_RADIUS, DROPDOWN_ITEM_PRESSED_RADIUS, DROPDOWN_ITEM_V_PADDING,
193 DROPDOWN_LOADING_PADDING, DROPDOWN_MESSAGE_PADDING, DROPDOWN_PANEL_GAP,
194 DROPDOWN_PANEL_MAX_HEIGHT, DROPDOWN_SEARCH_HINT, DropdownColors, DropdownFieldView,
195 DropdownFieldWidget, DropdownItem, DropdownPanelView, DropdownPanelWidget, DropdownView,
196 dropdown, dropdown_field, dropdown_filter, dropdown_item, dropdown_matches, dropdown_panel,
197};
198pub use expandable_list::{
199 EXPANDABLE_GAP, EXPANDABLE_HOVER_RADIUS, EXPANDABLE_ICON_ROTATION, EXPANDABLE_INNER_RADIUS,
200 EXPANDABLE_OUTER_RADIUS, EXPANDABLE_PRESSED_RADIUS, ExpandMode, ExpandableItem,
201 ExpandableListView, ExpandableListWidget, REVEAL_SPRING, expandable_item, expandable_list,
202 is_expanded, toggle_expanded,
203};
204pub use fab::{FabColor, FabSize, FabView, FabWidget, extended_fab, fab};
205pub use fab_menu::{FabMenu, FabMenuItem, FabMenuView, FabMenuWidget, fab_menu, fab_menu_item};
206pub use icon_button::{
207 BadgeValue, IconButton, IconButtonShape, IconButtonSize, IconButtonVariant, IconButtonView,
208 IconButtonWidget, IconButtonWidth, filled_icon_button, icon_button, outlined_icon_button,
209 tonal_icon_button,
210};
211pub use interaction::{HapticSignal, InteractionState, MaterialHaptics};
212pub use list_item::{
213 ListItem, ListItemLines, ListItemWidget, ONE_LINE_HEIGHT, THREE_LINE_HEIGHT, TWO_LINE_HEIGHT,
214 list_item,
215};
216pub use loading_indicator::{
217 LoadingIndicator, LoadingIndicatorVariant, LoadingIndicatorView, LoadingIndicatorWidget,
218 loading_indicator,
219};
220pub use menu::{
221 MenuAction, MenuColorStyle, MenuColors, MenuEntry, MenuGroup, MenuIcon, MenuNode,
222 MenuPanelView, MenuPanelWidget, MenuSelectable, MenuSelection, MenuSubmenu, MenuToggleable,
223 MenuView, menu, menu_entry, menu_group, menu_panel, menu_selectable, menu_submenu,
224 menu_toggleable,
225};
226pub use navbar::{
227 HEIGHT_MEDIUM as NAV_BAR_HEIGHT_MEDIUM, HEIGHT_SMALL as NAV_BAR_HEIGHT_SMALL,
228 INDICATOR_H as NAV_BAR_INDICATOR_H, INDICATOR_W as NAV_BAR_INDICATOR_W,
229 LEAD_SPRING as NAV_INDICATOR_LEAD_SPRING, NavBarIndicatorStyle, NavBarLabelBehavior,
230 NavBarSize, NavItem, NavigationBar, NavigationBarView, NavigationBarWidget,
231 TRAIL_SPRING as NAV_INDICATOR_TRAIL_SPRING, UNDERLINE_THICKNESS as NAV_BAR_UNDERLINE_THICKNESS,
232 nav_item, navigation_bar,
233};
234pub use navigation_drawer::{
235 DRAWER_WIDTH, DrawerContentView, DrawerContentWidget, DrawerDestination, DrawerSection,
236 NavigationDrawer, NavigationDrawerView, drawer_destination, drawer_section, navigation_drawer,
237 navigation_drawer_content, show_navigation_drawer,
238};
239pub use navigation_rail::{
240 NavigationRail, NavigationRailModality, NavigationRailType, NavigationRailView,
241 NavigationRailWidget, RAIL_COLLAPSED_WIDTH, RAIL_EXPAND_CURVE, RAIL_EXPAND_DURATION,
242 RAIL_EXPANDED_MAX_WIDTH, RAIL_EXPANDED_MIN_WIDTH, RAIL_ITEM_COLLAPSED_HEIGHT,
243 RAIL_ITEM_EXPANDED_HEIGHT, RAIL_LEAD_SPRING, RAIL_TRAIL_SPRING, RailDestination, RailFab,
244 RailLabelBehavior, RailSection, navigation_rail, rail_destination, rail_fab, rail_section,
245};
246pub use overlay::{
247 ANCHORED_ENTER_SCALE, AnchoredOverlayView, AnchoredOverlayWidget, ModalDismiss,
248 OVERLAY_ANCHOR_GAP, OVERLAY_DIALOG_MAX_WIDTH, OVERLAY_DIALOG_MIN_WIDTH, OVERLAY_EDGE_FRACTION,
249 OVERLAY_FLING_VELOCITY, OVERLAY_HANDLE_RESERVE, OVERLAY_SCRIM_ALPHA,
250 OVERLAY_SHEET_MAX_HEIGHT_FRACTION, OVERLAY_SIDE_SHEET_MAX_WIDTH, OverlayAlign, OverlayAnchor,
251 OverlayAnchorView, OverlayAnchorWidget, OverlayBorder, OverlayContainer, OverlayCorners,
252 OverlayElevation, OverlayEntrance, OverlayExtent, OverlayGeometry, OverlayLimit,
253 OverlayModalConfig, OverlayModalContent, OverlayModalView, OverlayModalWidget,
254 OverlayPlacement, OverlayRole, OverlaySide, anchored_overlay, overlay_anchor, overlay_modal,
255 place_anchored, show_overlay_modal,
256};
257pub use progress::{
258 AmplitudeCurve, CircularProgress, CircularProgressView, CircularProgressWidget,
259 CircularWavyProgress, LinearProgress, LinearProgressView, LinearProgressWidget,
260 LinearWavyProgress, ProgressSize, ProgressValue, circular_progress, circular_wavy_progress,
261 linear_progress, linear_wavy_progress,
262};
263pub use radio::{Radio, RadioView, RadioWidget, radio};
264pub use refresh_indicator::{RefreshIndicatorView, RefreshIndicatorWidget, refresh_indicator};
265pub use search::{
266 DockedSearchViewView, FULL_SCREEN_HEADER_HEIGHT, SEARCH_BAR_MIN_HEIGHT, SEARCH_BAR_MIN_WIDTH,
267 SEARCH_VIEW_COMPACT_MAX_WIDTH, SEARCH_VIEW_HEIGHT_FRACTION, SEARCH_VIEW_MIN_HEIGHT,
268 SEARCH_VIEW_MIN_WIDTH, SearchBarView, SearchBarWidget, SearchView, SearchViewContentWidget,
269 SearchViewMode, SearchViewView, search_bar, search_view, show_search_view,
270};
271pub use segmented_button::{
272 MAX_SEGMENTS, MIN_SEGMENTS, Segment, SegmentedButton, SegmentedButtonView,
273 SegmentedButtonWidget, segment, segmented_button,
274};
275pub use selection::{
276 SelectionAppBarView, SelectionAppBarWidget, SelectionHostView, SelectionHostWidget,
277 selection_app_bar, selection_host,
278};
279pub use shapes::{CornerRounding, RoundedPolygon};
280pub use sheet::{BottomSheetView, BottomSheetWidget, bottom_sheet, show_bottom_sheet};
281pub use side_sheet::{SIDE_SHEET_WIDTH, SideSheetView, show_side_sheet, side_sheet};
282pub use slider::{
283 RangeSliderView, RangeSliderWidget, Slider, SliderIconPosition, SliderRange, SliderTrackIcons,
284 SliderTrackKind, SliderView, SliderWidget, centered_slider, range_slider, slider,
285 vertical_centered_slider, vertical_slider, wavy_centered_slider, wavy_range_slider,
286 wavy_slider,
287};
288pub use snackbar::{
289 SnackbarController, SnackbarHostView, SnackbarHostWidget, SnackbarMessage, snackbar,
290 snackbar_host,
291};
292pub use split_button::{
293 CHEVRON_OPEN_TURNS, CHEVRON_ROTATION_DURATION, SPLIT_ELEVATED_INNER_GAP,
294 SPLIT_FOCUS_RING_OUTSET, SPLIT_INNER_GAP, SPLIT_MIN_TAP_TARGET, SPLIT_SHAPE_SPRING,
295 SplitButton, SplitButtonItem, SplitButtonMenuRoute, SplitButtonMenuStyle, SplitButtonShape,
296 SplitButtonSize, SplitButtonTrailingAlignment, SplitButtonVariant, SplitButtonView,
297 SplitButtonWidget, TRAILING_SELECTED_CORNER_PERCENT, split_button, split_button_item,
298};
299pub use switch::{Switch, SwitchView, SwitchWidget, switch};
300pub use tabs::{MIN_TABS, Tab, Tabs, TabsVariant, TabsView, TabsWidget, tab, tabs};
301pub use text_field::{TextField, TextFieldVariant, TextFieldView, TextFieldWidget, text_field};
302pub use time_picker::{
303 DIAL_SIZE, DIAL_SLOTS, DIALOG_LANDSCAPE_WIDTH, DIALOG_PORTRAIT_WIDTH, HEADER_LANDSCAPE_WIDTH,
304 TimeDialView, TimeDialWidget, TimeEntry, TimeInputView, TimeInputWidget, TimeOfDay, TimePicker,
305 TimePickerEntryMode, TimePickerMode, TimePickerOrientation, TimePickerStrings, TimePickerView,
306 show_time_picker, time_dial, time_input, time_picker,
307};
308pub use toggle_button::{
309 ToggleButton, ToggleButtonSize, ToggleButtonView, ToggleButtonWidget, elevated_toggle_button,
310 filled_toggle_button, outlined_toggle_button, text_toggle_button, toggle_button,
311 tonal_toggle_button,
312};
313pub use toolbar::{
314 DockedToolbar, FloatingToolbar, TOOLBAR_DOCKED_PAD_X, TOOLBAR_EXPAND_SPRING,
315 TOOLBAR_FAB_BASELINE, TOOLBAR_FAB_MEDIUM, TOOLBAR_FLOATING_PAD, TOOLBAR_GAP, TOOLBAR_HEIGHT,
316 TOOLBAR_MAX_INLINE_ACTIONS, TOOLBAR_REVEAL_FADE_START, TOOLBAR_SCREEN_OFFSET,
317 TOOLBAR_SETTLE_VELOCITY, TOOLBAR_TO_FAB_GAP, ToolbarAction, ToolbarColorStyle, ToolbarColors,
318 ToolbarElevation, ToolbarExitDirection, ToolbarFabPosition, ToolbarMorph, ToolbarPartition,
319 ToolbarScrollHide, ToolbarSize, ToolbarVariant, ToolbarView, ToolbarWidget, docked_toolbar,
320 floating_toolbar, partition_actions, toolbar_action,
321};
322pub use tooltip::{
323 RichTooltipView, TooltipAction, TooltipHover, TooltipTriggerView, TooltipTriggerWidget,
324 TooltipView, rich_tooltip, tooltip, tooltip_action, tooltip_trigger,
325};
326
327pub use tokens::{CorePalette, Hct, TonalPalette, from_seed, theme_from_seed};
328pub use tokens::{
329 MaterialDimensions, MaterialMotion, MaterialSemanticColors, MaterialSpacing, MaterialSpring,
330 MaterialTokens, baseline, color_scheme_dark, color_scheme_light, elevation, motion_scheme,
331 shape_scale, status_palette, type_scale,
332};
333
334pub fn install() {
370 frust::set_default_theme(baseline());
371 for bytes in tokens::font_data() {
372 frust::register_app_fonts(bytes.to_vec());
373 }
374}
375
376#[cfg(test)]
380mod icons_tests {
381 use frust::IconData;
382
383 use crate::icons;
384
385 const EXPECTED_ICON_COUNT: usize = 88;
390
391 #[test]
392 fn all_has_the_expected_count() {
393 assert_eq!(icons::ALL.len(), EXPECTED_ICON_COUNT);
394 }
395
396 #[test]
397 fn every_generated_icon_source_resolves_to_a_non_empty_path() {
398 for source in icons::ALL {
399 let data: IconData = (*source).into();
400 let (path, design) = data.resolve();
401 assert_eq!(design, 24.0);
402 assert!(
403 !path.elements().is_empty(),
404 "generated icon `{}` resolved to an empty path",
405 source.d
406 );
407 }
408 }
409
410 #[test]
411 fn spot_check_check_search_arrow_back() {
412 for source in [icons::CHECK, icons::SEARCH, icons::ARROW_BACK] {
413 let data: IconData = source.into();
414 let (path, design) = data.resolve();
415 assert_eq!(design, 24.0);
416 assert!(
417 !path.elements().is_empty(),
418 "spot-checked icon `{}` resolved to an empty path",
419 source.d
420 );
421 }
422 }
423
424 #[test]
425 fn no_two_entries_share_identical_path_data() {
426 let mut seen = std::collections::HashSet::new();
427 for source in icons::ALL {
428 assert!(
429 seen.insert(source.d),
430 "two icons::ALL entries share identical path data: {}",
431 source.d
432 );
433 }
434 }
435}
436
437#[cfg(test)]
438mod baseline_tests {
439 use frust::NativeTypefaces;
440
441 #[test]
444 fn baseline_attaches_native_typefaces_and_material_tokens() {
445 let theme = crate::baseline();
446 assert!(
447 theme.extension::<NativeTypefaces>().is_some(),
448 "baseline theme must attach NativeTypefaces for native-widget font resolution"
449 );
450 assert!(
451 theme.extension::<crate::MaterialTokens>().is_some(),
452 "baseline theme must attach MaterialTokens"
453 );
454 }
455
456 #[cfg(feature = "bundled-fonts")]
458 #[test]
459 fn font_bytes_are_non_empty_and_sniff_as_ttf() {
460 let fonts = crate::tokens::font_data();
461 assert_eq!(fonts.len(), 2, "expected Roboto Flex + Roboto Mono");
462 for (i, bytes) in fonts.iter().enumerate() {
463 assert!(!bytes.is_empty(), "font face {i} bytes must be non-empty");
464 assert!(
465 bytes.len() > 4,
466 "font face {i} must have at least 4 bytes for magic number"
467 );
468 let magic = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
470 assert!(
471 magic == 0x00010000 || magic == 0x74727565,
472 "font face {i} must start with TTF magic bytes"
473 );
474 }
475 }
476
477 #[cfg(feature = "bundled-fonts")]
479 fn read_u16(bytes: &[u8], offset: usize) -> u16 {
480 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
481 }
482
483 #[cfg(feature = "bundled-fonts")]
485 fn read_u32(bytes: &[u8], offset: usize) -> u32 {
486 u32::from_be_bytes([
487 bytes[offset],
488 bytes[offset + 1],
489 bytes[offset + 2],
490 bytes[offset + 3],
491 ])
492 }
493
494 #[cfg(feature = "bundled-fonts")]
505 fn fvar_axis_tags(bytes: &[u8]) -> Vec<[u8; 4]> {
506 let num_tables = read_u16(bytes, 4) as usize;
507 let mut fvar_offset = None;
508 for i in 0..num_tables {
509 let record = 12 + i * 16;
510 let tag = &bytes[record..record + 4];
511 if tag == b"fvar" {
512 fvar_offset = Some(read_u32(bytes, record + 8) as usize);
513 break;
514 }
515 }
516 let Some(fvar_offset) = fvar_offset else {
517 return Vec::new();
518 };
519
520 let axes_array_offset = read_u16(bytes, fvar_offset + 4) as usize;
521 let axis_count = read_u16(bytes, fvar_offset + 8) as usize;
522 let axis_size = read_u16(bytes, fvar_offset + 10) as usize;
523 assert_eq!(axis_size, 20, "fvar axis record size must be 20 bytes");
524
525 (0..axis_count)
526 .map(|i| {
527 let record = fvar_offset + axes_array_offset + i * axis_size;
528 let mut tag = [0u8; 4];
529 tag.copy_from_slice(&bytes[record..record + 4]);
530 tag
531 })
532 .collect()
533 }
534
535 #[cfg(feature = "bundled-fonts")]
547 #[test]
548 fn roboto_flex_is_a_wght_only_instance() {
549 let bytes = crate::tokens::font_data()[0];
550 let axes = fvar_axis_tags(bytes);
551 assert_eq!(
552 axes,
553 vec![*b"wght"],
554 "Roboto Flex must carry exactly one fvar axis, `wght`; got {axes:?}"
555 );
556 }
557
558 #[cfg(feature = "bundled-fonts")]
564 #[test]
565 fn roboto_flex_is_smaller_than_the_upstream_variable_font() {
566 let bytes = crate::tokens::font_data()[0];
567 assert!(
568 bytes.len() < 200_000,
569 "Roboto Flex must be the wght-only instance (< 200,000 B); got {} B \
570 — did the bundled font regress to the full upstream variable font?",
571 bytes.len()
572 );
573 }
574
575 #[test]
579 fn install_is_safe_to_call_twice() {
580 crate::install();
581 crate::install();
582 }
583}