1#![cfg_attr(windows, windows_subsystem = "windows")]
2
3#[path = "pages/mod.rs"]
4mod pages;
5
6use iced::time::Instant;
7use iced::{Size, Subscription, Task};
8use material::Theme;
9use material::widget::{navigation, theme_picker};
10use material_ui_rs as material;
11
12pub fn main() -> iced::Result {
13 let window_size = Size::new(1080.0, 980.0);
14
15 material::application(boot, update, view)
16 .title("material-ui-rs showcase")
17 .subscription(subscription)
18 .theme(theme)
19 .window(material::window_with_min_size(
20 window_size,
21 Size::new(420.0, 720.0),
22 ))
23 .run()
24}
25
26#[cfg(any(target_arch = "wasm32", test))]
27const CJK_CORE_FONT_URL: &str = "fonts/NotoSansSC-Core-0a7ff25a.otf";
28#[cfg(any(target_arch = "wasm32", test))]
29const CJK_REGIONAL_FONT_URL: &str = "fonts/NotoSansSC-faa6c9df.otf";
30
31fn boot() -> (Showcase, Task<Message>) {
32 let state = Showcase::default();
33
34 #[cfg(any(target_arch = "wasm32", test))]
35 let load_cjk_core =
36 material::fonts::load_web_font(CJK_CORE_FONT_URL).map(|_| Message::CjkCoreFontFinished);
37 #[cfg(not(any(target_arch = "wasm32", test)))]
38 let load_cjk_core = Task::none();
39
40 (state, load_cjk_core)
41}
42
43#[derive(Debug, Clone)]
44enum Message {
45 #[cfg(any(target_arch = "wasm32", test))]
46 CjkCoreFontFinished,
47 #[cfg(any(target_arch = "wasm32", test))]
48 CjkRegionalFontFinished,
49 Navigate(ShowcasePage),
50 Increment,
51 Decrement,
52 TextChanged(String),
53 EditorAction(material::widget::text_editor::Action),
54 SelectChanged(&'static str),
55 ComboboxSelected(&'static str),
56 ComboboxInputChanged(String),
57 SearchChanged(String),
58 DatePickerChanged(material::widget::picker::DatePickerAction),
59 DateRangePickerChanged(material::widget::picker::DateRangePickerAction),
60 TimePickerChanged(material::widget::picker::TimePickerAction),
61 SliderChanged(f32),
62 EnabledChanged(bool),
63 ThemeChanged(theme_picker::ThemeAction),
64 ChoiceSelected(RadioChoice),
65 SegmentSelected(SegmentChoice),
66 PrimaryTabSelected(TabChoice),
67 SecondaryTabSelected(TabChoice),
68 MenuPressed,
69 DialogOpened,
70 DialogDismissed,
71 DialogConfirmed,
72 ShowSnackbar,
73 SnackbarUndo,
74 WindowResized(Size),
75 Frame(Instant),
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79enum ShowcasePage {
80 Inputs,
81 Controls,
82 Feedback,
83 Surfaces,
84 Navigation,
85 Structure,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89enum RadioChoice {
90 Standard,
91 Expressive,
92 Dense,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96enum SegmentChoice {
97 List,
98 Grid,
99 Map,
100}
101
102impl SegmentChoice {
103 const fn index(self) -> usize {
104 match self {
105 Self::List => 0,
106 Self::Grid => 1,
107 Self::Map => 2,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113enum TabChoice {
114 Inputs,
115 Controls,
116 Feedback,
117}
118
119impl TabChoice {
120 const fn index(self) -> usize {
121 match self {
122 Self::Inputs => 0,
123 Self::Controls => 1,
124 Self::Feedback => 2,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy)]
130struct InventoryRow {
131 component: &'static str,
132 status: &'static str,
133 count: u32,
134}
135
136const NAV_DESTINATIONS: [navigation::Destination<ShowcasePage>; 6] = [
137 navigation::Destination::new(ShowcasePage::Inputs, "input", "Inputs"),
138 navigation::Destination::new(ShowcasePage::Controls, "tune", "Controls"),
139 navigation::Destination::new(ShowcasePage::Feedback, "info", "Feedback").badge("3"),
140 navigation::Destination::new(ShowcasePage::Surfaces, "layers", "Surfaces").small_badge(),
141 navigation::Destination::new(ShowcasePage::Navigation, "navigation", "Navigation"),
142 navigation::Destination::new(ShowcasePage::Structure, "layers", "Structure"),
143];
144
145const INVENTORY_ROWS: [InventoryRow; 3] = [
146 InventoryRow {
147 component: "Buttons",
148 status: "Enabled",
149 count: 4,
150 },
151 InventoryRow {
152 component: "Selection",
153 status: "Animated",
154 count: 3,
155 },
156 InventoryRow {
157 component: "Inputs",
158 status: "Focused",
159 count: 5,
160 },
161];
162
163#[derive(Debug)]
164struct Showcase {
165 navigation: navigation::NavigationState<ShowcasePage>,
166 window_size: Size,
167 count: i32,
168 note: String,
169 editor_content: material::widget::text_editor::Content,
170 select_choice: Option<&'static str>,
171 combobox_options: material::widget::combobox::State<&'static str>,
172 combobox_choice: Option<&'static str>,
173 combobox_input: String,
174 search_query: String,
175 date_picker: material::widget::picker::DatePickerState,
176 date_range_picker: material::widget::picker::DateRangePickerState,
177 time_picker: material::widget::picker::TimePickerState,
178 progress: f32,
179 enabled: bool,
180 radio_choice: Option<RadioChoice>,
181 segment_choice: SegmentChoice,
182 segment_state: material::widget::segmented_button::State,
183 primary_tab: TabChoice,
184 primary_tab_state: material::widget::tabs::State,
185 secondary_tab: TabChoice,
186 secondary_tab_state: material::widget::tabs::State,
187 progress_animation: material::widget::progress_bar::IndeterminateState,
188 alert_dialog: material::widget::dialog::Transition,
189 snackbar: material::widget::snackbar::Transition,
190 theme_controller: theme_picker::ThemeController,
191}
192
193impl Default for Showcase {
194 fn default() -> Self {
195 Self {
196 navigation: navigation::NavigationState::new(ShowcasePage::Inputs),
197 window_size: Size::new(1080.0, 980.0),
198 count: 0,
199 note: String::new(),
200 editor_content: material::widget::text_editor::Content::with_text(
201 "Material 3 multi-line text editor",
202 ),
203 select_choice: Some("Assist"),
204 combobox_options: material::widget::combobox::State::with_selection(
205 vec!["Assist", "Suggestion", "Filter"],
206 Some(&"Suggestion"),
207 ),
208 combobox_choice: Some("Suggestion"),
209 combobox_input: String::new(),
210 search_query: String::new(),
211 date_picker: material::widget::picker::DatePickerState::new(
212 material::widget::picker::Date::new(2026, 7, 4),
213 ),
214 date_range_picker: material::widget::picker::DateRangePickerState::new(
215 material::widget::picker::Date::new(2026, 7, 4),
216 material::widget::picker::Date::new(2026, 7, 10),
217 ),
218 time_picker: material::widget::picker::TimePickerState::new(14, 30, false),
219 progress: 42.0,
220 enabled: true,
221 radio_choice: Some(RadioChoice::Standard),
222 segment_choice: SegmentChoice::List,
223 segment_state: material::widget::segmented_button::State::new(
224 SegmentChoice::List.index(),
225 ),
226 primary_tab: TabChoice::Inputs,
227 primary_tab_state: material::widget::tabs::State::new(TabChoice::Inputs.index()),
228 secondary_tab: TabChoice::Controls,
229 secondary_tab_state: material::widget::tabs::State::new(TabChoice::Controls.index()),
230 progress_animation: material::widget::progress_bar::IndeterminateState::new(
231 Instant::now(),
232 ),
233 alert_dialog: material::widget::dialog::Transition::default(),
234 snackbar: material::widget::snackbar::Transition::default(),
235 theme_controller: theme_picker::ThemeController::default(),
236 }
237 }
238}
239
240impl Showcase {
241 fn theme(&self) -> Theme {
242 self.theme_controller.theme("Material 3 animated")
243 }
244
245 fn navigation_selection(&self) -> navigation::Selection<ShowcasePage> {
246 self.navigation.selection()
247 }
248
249 fn adaptive_navigation_layout(&self) -> navigation::AdaptiveLayout {
250 navigation::adaptive_layout(self.window_size.width, self.window_size.height)
251 }
252}
253
254fn update(state: &mut Showcase, message: Message) -> Task<Message> {
255 match message {
256 #[cfg(any(target_arch = "wasm32", test))]
257 Message::CjkCoreFontFinished => load_cjk_regional_font(),
258 #[cfg(any(target_arch = "wasm32", test))]
259 Message::CjkRegionalFontFinished => Task::none(),
260 Message::Navigate(page) => {
261 state
262 .navigation
263 .select(page, Instant::now(), state.adaptive_navigation_layout());
264 Task::none()
265 }
266 Message::Increment => {
267 state.count += 1;
268 Task::none()
269 }
270 Message::Decrement => {
271 state.count -= 1;
272 Task::none()
273 }
274 Message::TextChanged(note) => {
275 state.note = note;
276 Task::none()
277 }
278 Message::EditorAction(action) => {
279 state.editor_content.perform(action);
280 Task::none()
281 }
282 Message::SelectChanged(choice) => {
283 state.select_choice = Some(choice);
284 Task::none()
285 }
286 Message::ComboboxSelected(choice) => {
287 state.combobox_choice = Some(choice);
288 state.combobox_input.clear();
289 state.combobox_options.set_selection(Some(&choice));
290 Task::none()
291 }
292 Message::ComboboxInputChanged(input) => {
293 state.combobox_options.set_input(input.clone());
294 state.combobox_input = input;
295 state.combobox_choice = None;
296 Task::none()
297 }
298 Message::SearchChanged(query) => {
299 state.search_query = query;
300 Task::none()
301 }
302 Message::DatePickerChanged(action) => state.date_picker.update_and_scroll(action),
303 Message::DateRangePickerChanged(action) => {
304 state.date_range_picker.update_and_scroll(action)
305 }
306 Message::TimePickerChanged(action) => {
307 state.time_picker.update(action);
308 Task::none()
309 }
310 Message::SliderChanged(progress) => {
311 state.progress = progress;
312 Task::none()
313 }
314 Message::EnabledChanged(enabled) => {
315 state.enabled = enabled;
316 Task::none()
317 }
318 Message::ChoiceSelected(choice) => {
319 state.radio_choice = Some(choice);
320 Task::none()
321 }
322 Message::SegmentSelected(choice) => {
323 state.segment_choice = choice;
324 state.segment_state.select(choice.index(), Instant::now());
325 Task::none()
326 }
327 Message::PrimaryTabSelected(choice) => {
328 state.primary_tab = choice;
329 state.primary_tab_state.select(
330 choice.index(),
331 Instant::now(),
332 material::widget::tabs::Variant::Primary,
333 );
334 Task::none()
335 }
336 Message::SecondaryTabSelected(choice) => {
337 state.secondary_tab = choice;
338 state.secondary_tab_state.select(
339 choice.index(),
340 Instant::now(),
341 material::widget::tabs::Variant::Secondary,
342 );
343 Task::none()
344 }
345 Message::MenuPressed => {
346 state.navigation.toggle_menu_now();
347 Task::none()
348 }
349 Message::DialogOpened => {
350 state.alert_dialog.show(Instant::now());
351 Task::none()
352 }
353 Message::DialogDismissed => {
354 state.alert_dialog.dismiss(Instant::now());
355 Task::none()
356 }
357 Message::DialogConfirmed => {
358 state.alert_dialog.dismiss(Instant::now());
359 state.count += 1;
360 Task::none()
361 }
362 Message::ShowSnackbar => {
363 state.snackbar.show(Instant::now());
364 Task::none()
365 }
366 Message::SnackbarUndo => {
367 state.count -= 1;
368 state.snackbar.dismiss(Instant::now());
369 Task::none()
370 }
371 Message::WindowResized(size) => {
372 state.window_size = size;
373 Task::none()
374 }
375 Message::ThemeChanged(action) => {
376 state.theme_controller.update(
377 action,
378 state.window_size,
379 theme_picker::bottom_margin(state.adaptive_navigation_layout()),
380 Instant::now(),
381 );
382 Task::none()
383 }
384 Message::Frame(now) => {
385 let _ = state.theme_controller.advance(now);
386 let _ = state.navigation.advance(now);
387 let _ = state.segment_state.advance(now);
388 let _ = state.primary_tab_state.advance(now);
389 let _ = state.secondary_tab_state.advance(now);
390 state.progress_animation.advance(now);
391 let _ = state.alert_dialog.advance(now);
392 let _ = state.snackbar.advance(now);
393 let _ = state.date_picker.advance(now);
394 let _ = state.date_range_picker.advance(now);
395 let _ = state.time_picker.advance(now);
396 Task::none()
397 }
398 }
399}
400
401#[cfg(any(target_arch = "wasm32", test))]
402fn load_cjk_regional_font() -> Task<Message> {
403 material::fonts::load_web_font(CJK_REGIONAL_FONT_URL).map(|_| Message::CjkRegionalFontFinished)
404}
405
406fn theme(state: &Showcase) -> Theme {
407 state.theme()
408}
409
410fn subscription(state: &Showcase) -> Subscription<Message> {
411 let mut subscriptions =
412 vec![iced::window::resize_events().map(|(_id, size)| Message::WindowResized(size))];
413
414 if state.theme_controller.is_animating()
415 || state.navigation.is_animating()
416 || state.segment_state.is_animating()
417 || state.primary_tab_state.is_animating()
418 || state.secondary_tab_state.is_animating()
419 || state.alert_dialog.is_animating()
420 || state.snackbar.is_active()
421 || state.date_picker.is_animating()
422 || state.date_range_picker.is_animating()
423 || state.time_picker.is_animating()
424 || (state.navigation.selected() == ShowcasePage::Feedback
425 && state.progress_animation.is_animating())
426 {
427 subscriptions.push(iced::window::frames().map(Message::Frame));
428 }
429
430 Subscription::batch(subscriptions)
431}
432
433fn view(state: &Showcase) -> material::Element<'_, Message> {
434 let now = Instant::now();
435 let page_content = material::widget::snackbar::host(
436 pages::view(state),
437 &state.snackbar,
438 now,
439 "Photo archived",
440 "Undo",
441 Message::SnackbarUndo,
442 );
443
444 let content = navigation::suite(&NAV_DESTINATIONS, &state.navigation)
445 .layout(state.adaptive_navigation_layout())
446 .with_menu("Showcase", Message::MenuPressed)
447 .view(Message::Navigate, page_content);
448 let content = state.theme_controller.controls_over(
449 content,
450 theme_picker::bottom_margin(state.adaptive_navigation_layout()),
451 Message::ThemeChanged,
452 );
453
454 let content = material::widget::dialog::modal_animated(
455 content,
456 &state.alert_dialog,
457 now,
458 alert_dialog(state.alert_dialog.alpha(now)),
459 );
460
461 state.theme_controller.reveal_over(content, now)
462}
463
464fn alert_dialog(alpha: f32) -> material::Element<'static, Message> {
465 let action_options = material::widget::dialog::AlphaOptions::default().alpha(alpha);
466
467 material::widget::dialog::alert_with(
468 "Discard draft?",
469 "Your current changes will be removed from this device.",
470 material::widget::dialog::actions([
471 material::widget::dialog::action_button_with(
472 "Cancel",
473 Message::DialogDismissed,
474 action_options,
475 ),
476 material::widget::dialog::action_button_with(
477 "Discard",
478 Message::DialogConfirmed,
479 action_options,
480 ),
481 ]),
482 material::widget::dialog::AlertOptions::default()
483 .icon("info")
484 .alpha(alpha),
485 )
486 .into()
487}
488
489#[cfg(test)]
490#[allow(unused_must_use)]
491mod tests {
492 use super::*;
493 use iced::Point;
494
495 #[test]
496 fn combobox_input_preserves_typed_query_and_clears_stale_selection() {
497 let mut showcase = Showcase::default();
498
499 update(&mut showcase, Message::ComboboxInputChanged("xxx".into()));
500
501 assert_eq!(showcase.combobox_choice, None);
502 assert_eq!(showcase.combobox_input, "xxx");
503
504 update(&mut showcase, Message::ComboboxSelected("Assist"));
505
506 assert_eq!(showcase.combobox_choice, Some("Assist"));
507 assert_eq!(showcase.combobox_input, "");
508 }
509
510 #[test]
511 fn date_picker_action_updates_showcase_state() {
512 let mut showcase = Showcase::default();
513 let date = material::widget::picker::Date::new(2026, 12, 25).unwrap();
514
515 update(
516 &mut showcase,
517 Message::DatePickerChanged(material::widget::picker::DatePickerAction::SelectDate(
518 date,
519 )),
520 );
521
522 assert_eq!(showcase.date_picker.selected_date(), Some(date));
523 assert_eq!(
524 showcase.date_picker.displayed_month(),
525 material::widget::picker::YearMonth::new(2026, 12).unwrap()
526 );
527 }
528
529 #[test]
530 fn date_range_picker_action_updates_showcase_state() {
531 let mut showcase = Showcase::default();
532 let start = material::widget::picker::Date::new(2026, 8, 1).unwrap();
533 let end = material::widget::picker::Date::new(2026, 8, 5).unwrap();
534
535 update(
536 &mut showcase,
537 Message::DateRangePickerChanged(
538 material::widget::picker::DateRangePickerAction::SelectDate(start),
539 ),
540 );
541 update(
542 &mut showcase,
543 Message::DateRangePickerChanged(
544 material::widget::picker::DateRangePickerAction::SelectDate(end),
545 ),
546 );
547
548 assert_eq!(
549 showcase.date_range_picker.selected_start_date(),
550 Some(start)
551 );
552 assert_eq!(showcase.date_range_picker.selected_end_date(), Some(end));
553 }
554
555 #[test]
556 fn time_picker_action_updates_showcase_state() {
557 let mut showcase = Showcase::default();
558
559 update(
560 &mut showcase,
561 Message::TimePickerChanged(material::widget::picker::TimePickerAction::SelectHour(9)),
562 );
563 update(
564 &mut showcase,
565 Message::TimePickerChanged(material::widget::picker::TimePickerAction::SelectMinute(
566 45,
567 )),
568 );
569
570 assert_eq!(showcase.time_picker.hour(), 21);
571 assert_eq!(showcase.time_picker.minute(), 45);
572 }
573
574 #[test]
575 fn navigation_starts_selection_animation() {
576 let mut showcase = Showcase::default();
577
578 update(&mut showcase, Message::Navigate(ShowcasePage::Controls));
579
580 assert_eq!(showcase.navigation.selected(), ShowcasePage::Controls);
581 assert!(showcase.navigation.is_animating());
582 assert_eq!(
583 showcase
584 .navigation
585 .selection()
586 .progress(ShowcasePage::Controls),
587 0.0
588 );
589 assert_eq!(
590 showcase
591 .navigation
592 .selection()
593 .progress(ShowcasePage::Inputs),
594 1.0
595 );
596 }
597
598 #[test]
599 fn alert_dialog_messages_toggle_modal_state() {
600 let mut showcase = Showcase::default();
601
602 update(&mut showcase, Message::DialogOpened);
603 assert_eq!(
604 showcase.alert_dialog.phase(),
605 material::widget::dialog::TransitionPhase::Showing
606 );
607 assert!(showcase.alert_dialog.is_active());
608
609 update(&mut showcase, Message::DialogDismissed);
610 assert_eq!(
611 showcase.alert_dialog.phase(),
612 material::widget::dialog::TransitionPhase::Dismissing
613 );
614
615 update(&mut showcase, Message::DialogOpened);
616 update(&mut showcase, Message::DialogConfirmed);
617 assert_eq!(
618 showcase.alert_dialog.phase(),
619 material::widget::dialog::TransitionPhase::Dismissing
620 );
621 assert_eq!(showcase.count, 1);
622 }
623
624 #[test]
625 fn snackbar_button_starts_android_transition() {
626 let mut showcase = Showcase::default();
627
628 update(&mut showcase, Message::ShowSnackbar);
629
630 assert_eq!(
631 showcase.snackbar.phase(),
632 material::widget::snackbar::TransitionPhase::Showing
633 );
634 assert!(showcase.snackbar.is_active());
635 }
636
637 #[test]
638 fn snackbar_action_dismisses_with_exit_transition() {
639 let mut showcase = Showcase::default();
640
641 update(&mut showcase, Message::ShowSnackbar);
642 update(&mut showcase, Message::SnackbarUndo);
643
644 assert_eq!(showcase.count, -1);
645 assert_eq!(
646 showcase.snackbar.phase(),
647 material::widget::snackbar::TransitionPhase::Dismissing
648 );
649 }
650
651 #[test]
652 fn theme_picker_uses_navigation_bar_clearance() {
653 assert_eq!(
654 theme_picker::bottom_margin(navigation::AdaptiveLayout::NavigationBar),
655 theme_picker::FLOATING_MARGIN
656 + material::tokens::component::navigation_bar::CONTAINER_HEIGHT
657 );
658 assert_eq!(
659 theme_picker::bottom_margin(navigation::AdaptiveLayout::NavigationRail),
660 theme_picker::FLOATING_MARGIN
661 );
662 }
663
664 #[test]
665 fn selecting_current_theme_does_not_start_animation() {
666 let mut showcase = Showcase::default();
667
668 update(
669 &mut showcase,
670 Message::ThemeChanged(theme_picker::ThemeAction::SetDarkMode {
671 dark_mode: true,
672 origin: Point::new(120.0, 360.0),
673 }),
674 );
675
676 assert!(!showcase.theme_controller.is_animating());
677 assert!(showcase.theme_controller.dark_mode());
678 }
679
680 #[test]
681 fn dark_mode_action_starts_reveal_from_switch_origin() {
682 let mut showcase = Showcase::default();
683 let origin = Point::new(120.0, 640.0);
684
685 update(
686 &mut showcase,
687 Message::ThemeChanged(theme_picker::ThemeAction::SetDarkMode {
688 dark_mode: false,
689 origin,
690 }),
691 );
692
693 let animation = showcase
694 .theme_controller
695 .transition()
696 .expect("dark mode should animate");
697
698 assert!(!showcase.theme_controller.dark_mode());
699 assert_eq!(animation.origin(), origin);
700 }
701
702 #[test]
703 fn theme_picker_selects_color_and_closes() {
704 let mut showcase = Showcase::default();
705
706 update(
707 &mut showcase,
708 Message::ThemeChanged(theme_picker::ThemeAction::TogglePicker),
709 );
710 assert!(showcase.theme_controller.is_picker_open());
711
712 update(
713 &mut showcase,
714 Message::ThemeChanged(theme_picker::ThemeAction::SelectColor(
715 theme_picker::MaterialColor::Blue,
716 )),
717 );
718
719 let expected_origin = theme_picker::swatch_center(
720 showcase.window_size,
721 theme_picker::bottom_margin(showcase.adaptive_navigation_layout()),
722 theme_picker::MaterialColor::Blue,
723 );
724 let animation = showcase
725 .theme_controller
726 .transition()
727 .expect("theme selection should animate");
728
729 assert_eq!(
730 showcase.theme_controller.selected_color(),
731 theme_picker::MaterialColor::Blue
732 );
733 assert!(!showcase.theme_controller.is_picker_open());
734 assert_eq!(animation.origin(), expected_origin);
735 }
736
737 #[test]
738 fn navigation_uses_material_symbol_icon_names() {
739 assert_eq!(material::fonts::all().len(), 5);
740 assert_eq!(
741 NAV_DESTINATIONS.map(|destination| destination.icon),
742 ["input", "tune", "info", "layers", "navigation", "layers"]
743 );
744
745 for destination in NAV_DESTINATIONS {
746 assert!(material::fonts::material_symbol_codepoint(destination.icon).is_some());
747 }
748 }
749
750 #[test]
751 fn cjk_fonts_load_serially_from_boot_without_input_trigger() {
752 let (mut showcase, core_load) = boot();
753 assert!(core_load.units() > 0);
754
755 let input_update = update(&mut showcase, Message::TextChanged("中文".into()));
756 assert_eq!(input_update.units(), 0);
757 assert_eq!(showcase.note, "中文");
758
759 let regional_load = update(&mut showcase, Message::CjkCoreFontFinished);
760 assert!(regional_load.units() > 0);
761 assert_eq!(showcase.note, "中文");
762
763 let finished = update(&mut showcase, Message::CjkRegionalFontFinished);
764 assert_eq!(finished.units(), 0);
765 assert_eq!(showcase.note, "中文");
766 }
767
768 #[test]
769 fn every_free_text_surface_preserves_cjk_input_during_font_loading() {
770 let mut note = Showcase::default();
771 let note_update = update(&mut note, Message::TextChanged("中文".into()));
772 assert_eq!(note_update.units(), 0);
773 assert_eq!(note.note, "中文");
774
775 let mut editor = Showcase::default();
776 let editor_update = update(
777 &mut editor,
778 Message::EditorAction(material::widget::text_editor::Action::Edit(
779 iced::widget::text_editor::Edit::Insert('中'),
780 )),
781 );
782 assert_eq!(editor_update.units(), 0);
783 assert!(editor.editor_content.text().contains('中'));
784
785 let mut combobox = Showcase::default();
786 let combobox_update = update(&mut combobox, Message::ComboboxInputChanged("中文".into()));
787 assert_eq!(combobox_update.units(), 0);
788 assert_eq!(combobox.combobox_input, "中文");
789
790 let mut search = Showcase::default();
791 let search_update = update(&mut search, Message::SearchChanged("中文".into()));
792 assert_eq!(search_update.units(), 0);
793 assert_eq!(search.search_query, "中文");
794 }
795
796 #[test]
797 fn resize_updates_adaptive_layout_inputs() {
798 let mut showcase = Showcase::default();
799
800 update(
801 &mut showcase,
802 Message::WindowResized(Size::new(500.0, 900.0)),
803 );
804
805 assert_eq!(
806 showcase.adaptive_navigation_layout(),
807 material::widget::navigation::AdaptiveLayout::NavigationBar
808 );
809
810 update(
811 &mut showcase,
812 Message::WindowResized(Size::new(900.0, 900.0)),
813 );
814
815 assert_eq!(
816 showcase.adaptive_navigation_layout(),
817 material::widget::navigation::AdaptiveLayout::NavigationRail
818 );
819 }
820}