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