pub struct UiNodeStyle { /* private fields */ }Implementations§
Source§impl UiNodeStyle
impl UiNodeStyle
pub fn new(layout: impl Into<LayoutStyle>) -> Self
pub fn clipped(layout: impl Into<LayoutStyle>) -> Self
Sourcepub fn layout_style(&self) -> LayoutStyle
pub fn layout_style(&self) -> LayoutStyle
Examples found in repository?
examples/showcase.rs (line 946)
923 fn measured_open_window_sizes(&self, desktop_size: UiSize) -> Vec<ShowcaseWindowMeasurement> {
924 let measure_height = desktop_size.height.max(SHOWCASE_ORGANIZE_MEASURE_HEIGHT);
925 let viewport = UiSize::new(desktop_size.width + RIGHT_PANEL_WIDTH, measure_height);
926 let mut document = self.view(viewport);
927 #[cfg(feature = "text-cosmic")]
928 let mut measurer = CosmicTextMeasurer::new();
929 #[cfg(not(feature = "text-cosmic"))]
930 let mut measurer = ApproxTextMeasurer;
931 if document.compute_layout(viewport, &mut measurer).is_err() {
932 return Vec::new();
933 }
934 let options = showcase_desktop_options(desktop_size);
935 SHOWCASE_WIDGET_WINDOW_IDS
936 .into_iter()
937 .filter(|id| self.windows.is_visible(id))
938 .filter_map(|id| {
939 let name = format!("showcase.windows.window.{id}");
940 let collapsed_size = showcase_collapsed_window_size(id, &options);
941 document
942 .nodes()
943 .iter()
944 .find(|node| node.name() == name)
945 .map(|node| {
946 let min_size = node.style().layout_style().min_size();
947 ShowcaseWindowMeasurement {
948 id: id.to_string(),
949 size: UiSize::new(node.layout().rect.width, node.layout().rect.height),
950 min_size: UiSize::new(
951 min_size
952 .and_then(|size| size.width.points_value())
953 .unwrap_or(node.layout().rect.width),
954 min_size
955 .and_then(|size| size.height.points_value())
956 .unwrap_or(node.layout().rect.height),
957 ),
958 collapsed_size,
959 }
960 })
961 })
962 .collect()
963 }pub fn layout(&self) -> Option<Layout>
pub const fn clip(&self) -> ClipBehavior
pub const fn opacity(&self) -> f32
pub const fn z_index(&self) -> i16
pub fn with_clip(self, clip: ClipBehavior) -> Self
pub fn with_opacity(self, opacity: f32) -> Self
Sourcepub const fn with_z_index(self, z_index: i16) -> Self
pub const fn with_z_index(self, z_index: i16) -> Self
Examples found in repository?
examples/three_consumer_probe.rs (line 46)
32fn build_game_overlay() -> UiDocument {
33 let mut document = UiDocument::new(root_style(800.0, 600.0));
34 let hotbar = document.add_child(
35 document.root(),
36 UiNode::container(
37 "game.hotbar",
38 layout::clipped_node_style(layout::with_margin_bottom(
39 layout::with_auto_horizontal_margin(layout::with_size(
40 layout::centered_row(),
41 layout::px(360.0),
42 layout::px(64.0),
43 )),
44 18.0,
45 ))
46 .with_z_index(10),
47 )
48 .with_visual(UiVisual::panel(
49 ColorRgba::new(20, 24, 31, 230),
50 Some(StrokeStyle::new(ColorRgba::new(96, 113, 139, 255), 1.0)),
51 6.0,
52 )),
53 );
54
55 for slot in 0..8 {
56 document.add_child(
57 hotbar,
58 UiNode::container(
59 format!("game.hotbar.slot.{slot}"),
60 layout::node_style(layout::with_margin_all(layout::fixed(36.0, 36.0), 4.0)),
61 )
62 .with_input(InputBehavior::BUTTON)
63 .with_accessibility(
64 AccessibilityMeta::new(AccessibilityRole::Button)
65 .label(format!("Hotbar slot {}", slot + 1))
66 .focusable()
67 .action(AccessibilityAction::new("activate", "Activate")),
68 )
69 .with_visual(UiVisual::panel(
70 ColorRgba::new(40, 49, 62, 255),
71 Some(StrokeStyle::new(ColorRgba::new(105, 124, 153, 255), 1.0)),
72 4.0,
73 )),
74 );
75 }
76
77 document
78}Sourcepub fn set_clip(&mut self, clip: ClipBehavior)
pub fn set_clip(&mut self, clip: ClipBehavior)
Examples found in repository?
examples/showcase.rs (line 2494)
2480fn showcase_overlays(
2481 ui: &mut UiDocument,
2482 desktop: UiNodeId,
2483 state: &ShowcaseState,
2484 desktop_size: UiSize,
2485) {
2486 if state.toast_visible {
2487 let overlay_width = 320.0;
2488 let mut overlay_style = UiNodeStyle::from(operad::layout::absolute(
2489 (desktop_size.width - overlay_width - 18.0).max(18.0),
2490 18.0,
2491 overlay_width,
2492 180.0,
2493 ));
2494 overlay_style.set_clip(ClipBehavior::None);
2495 overlay_style.set_z_index(6000);
2496 let overlay = ui.add_child(
2497 desktop,
2498 UiNode::container("showcase.toast_overlay", overlay_style),
2499 );
2500 let mut stack = ext_widgets::ToastStack::new(3);
2501 stack.push_toast(
2502 ext_widgets::Toast::new(
2503 ext_widgets::ToastId::new(1),
2504 ext_widgets::ToastSeverity::Success,
2505 "Saved",
2506 Some("All changes are written".to_string()),
2507 None,
2508 )
2509 .with_action(ext_widgets::ToastAction::new("undo", "Undo")),
2510 );
2511 stack.push(
2512 ext_widgets::ToastSeverity::Warning,
2513 "Autosave paused",
2514 Some("Changes are kept locally".to_string()),
2515 None,
2516 );
2517 let mut options = ext_widgets::ToastStackOptions::default();
2518 options.z_index = 6100;
2519 ext_widgets::toast_stack(ui, overlay, "showcase.toast_overlay.stack", &stack, options);
2520 }
2521
2522 if state.popup_open {
2523 let popup_width = 280.0;
2524 let popup_height = 110.0;
2525 let popup = ext_widgets::popup_panel(
2526 ui,
2527 desktop,
2528 "showcase.popup_overlay",
2529 UiRect::new(
2530 (desktop_size.width - popup_width - 36.0).max(18.0),
2531 220.0_f32.min((desktop_size.height - popup_height - 18.0).max(18.0)),
2532 popup_width,
2533 popup_height,
2534 ),
2535 ext_widgets::PopupOptions {
2536 z_index: 6100,
2537 accessibility: Some(
2538 AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup panel"),
2539 ),
2540 ..Default::default()
2541 },
2542 );
2543 let body = ui.add_child(
2544 popup,
2545 UiNode::container(
2546 "showcase.popup_overlay.body",
2547 LayoutStyle::column()
2548 .with_width_percent(1.0)
2549 .with_height_percent(1.0)
2550 .padding(12.0)
2551 .gap(8.0),
2552 ),
2553 );
2554 let header = row(ui, body, "showcase.popup_overlay.header", 8.0);
2555 widgets::label(
2556 ui,
2557 header,
2558 "showcase.popup_overlay.title",
2559 "Popup panel",
2560 text(13.0, color(240, 244, 250)),
2561 LayoutStyle::new().with_width_percent(1.0),
2562 );
2563 let mut close =
2564 widgets::ButtonOptions::new(LayoutStyle::size(28.0, 24.0)).with_action("popup.close");
2565 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
2566 close.hovered_visual = Some(button_visual(54, 70, 92));
2567 close.text_style = text(13.0, color(220, 228, 238));
2568 widgets::button(ui, header, "showcase.popup_overlay.close", "x", close);
2569 widgets::label(
2570 ui,
2571 body,
2572 "showcase.popup_overlay.body_text",
2573 "This surface is rendered as an overlay.",
2574 text(12.0, color(196, 210, 230)),
2575 LayoutStyle::new().with_width_percent(1.0),
2576 );
2577 }
2578}
2579
2580fn showcase_window_descriptors(
2581 state: &ShowcaseState,
2582 desktop_size: UiSize,
2583) -> Vec<ext_widgets::FloatingWindowDescriptor> {
2584 let wide = (desktop_size.width - 36.0).clamp(320.0, 720.0);
2585 let medium = (desktop_size.width - 36.0).clamp(300.0, 604.0);
2586 let buttons_width = medium.min(620.0);
2587 let mut windows = Vec::new();
2588 push_window(
2589 &mut windows,
2590 state.windows.labels,
2591 "labels",
2592 "Labels",
2593 UiSize::new(380.0, 460.0),
2594 );
2595 push_window(
2596 &mut windows,
2597 state.windows.buttons,
2598 "buttons",
2599 "Buttons",
2600 UiSize::new(buttons_width, 220.0),
2601 );
2602 push_window(
2603 &mut windows,
2604 state.windows.checkbox,
2605 "checkbox",
2606 "Checkbox",
2607 UiSize::new(250.0, 72.0),
2608 );
2609 push_window(
2610 &mut windows,
2611 state.windows.toggles,
2612 "toggles",
2613 "Radio and toggles",
2614 UiSize::new(360.0, 320.0),
2615 );
2616 push_window(
2617 &mut windows,
2618 state.windows.slider,
2619 "slider",
2620 "Slider",
2621 UiSize::new(430.0, 560.0),
2622 );
2623 push_window(
2624 &mut windows,
2625 state.windows.numeric,
2626 "numeric",
2627 "Numeric input",
2628 UiSize::new(360.0, 180.0),
2629 );
2630 push_window(
2631 &mut windows,
2632 state.windows.text_input,
2633 "text_input",
2634 "Text input",
2635 UiSize::new(520.0, 560.0),
2636 );
2637 push_window(
2638 &mut windows,
2639 state.windows.selection,
2640 "selection",
2641 "Select controls",
2642 UiSize::new(360.0, 360.0),
2643 );
2644 push_window(
2645 &mut windows,
2646 state.windows.menus,
2647 "menus",
2648 "Menus",
2649 UiSize::new(wide, 520.0),
2650 );
2651 push_window(
2652 &mut windows,
2653 state.windows.command_palette,
2654 "command_palette",
2655 "Command palette",
2656 UiSize::new(520.0, 320.0),
2657 );
2658 push_window(
2659 &mut windows,
2660 state.windows.date_picker,
2661 "date_picker",
2662 "Date picker",
2663 UiSize::new(430.0, 390.0),
2664 );
2665 push_window(
2666 &mut windows,
2667 state.windows.color_picker,
2668 "color_picker",
2669 "Color picker",
2670 UiSize::new(340.0, 390.0),
2671 );
2672 push_window(
2673 &mut windows,
2674 state.windows.color_buttons,
2675 "color_buttons",
2676 "Color buttons",
2677 UiSize::new(430.0, 360.0),
2678 );
2679 push_window(
2680 &mut windows,
2681 state.windows.progress,
2682 "progress",
2683 "Progress indicator",
2684 UiSize::new(500.0, 168.0),
2685 );
2686 push_window(
2687 &mut windows,
2688 state.windows.animation,
2689 "animation",
2690 "Animation",
2691 UiSize::new(520.0, 430.0),
2692 );
2693 push_window(
2694 &mut windows,
2695 state.windows.lists_tables,
2696 "lists_tables",
2697 "Lists and tables",
2698 UiSize::new(wide, 620.0),
2699 );
2700 push_window(
2701 &mut windows,
2702 state.windows.property_inspector,
2703 "property_inspector",
2704 "Property inspector",
2705 UiSize::new(330.0, 250.0),
2706 );
2707 push_window(
2708 &mut windows,
2709 state.windows.diagnostics,
2710 "diagnostics",
2711 "Diagnostics",
2712 UiSize::new(640.0, 760.0),
2713 );
2714 push_window(
2715 &mut windows,
2716 state.windows.trees,
2717 "trees",
2718 "Trees",
2719 UiSize::new(430.0, 390.0),
2720 );
2721 push_window(
2722 &mut windows,
2723 state.windows.layout_widgets,
2724 "layout_widgets",
2725 "Layout widgets",
2726 UiSize::new(wide.min(560.0), 400.0),
2727 );
2728 push_window(
2729 &mut windows,
2730 state.windows.containers,
2731 "containers",
2732 "Containers",
2733 UiSize::new(560.0, 640.0),
2734 );
2735 push_window(
2736 &mut windows,
2737 state.windows.forms,
2738 "forms",
2739 "Forms",
2740 UiSize::new(520.0, 620.0),
2741 );
2742 push_window(
2743 &mut windows,
2744 state.windows.overlays,
2745 "overlays",
2746 "Overlays",
2747 UiSize::new(560.0, 560.0),
2748 );
2749 push_window(
2750 &mut windows,
2751 state.windows.drag_drop,
2752 "drag_drop",
2753 "Drag and drop",
2754 UiSize::new(500.0, 460.0),
2755 );
2756 push_window(
2757 &mut windows,
2758 state.windows.media,
2759 "media",
2760 "Media",
2761 UiSize::new(520.0, 430.0),
2762 );
2763 push_window(
2764 &mut windows,
2765 state.windows.timeline,
2766 "timeline",
2767 "Timeline",
2768 UiSize::new(600.0, 120.0),
2769 );
2770 push_window(
2771 &mut windows,
2772 state.windows.toasts,
2773 "toasts",
2774 "Toasts",
2775 UiSize::new(320.0, 270.0),
2776 );
2777 push_window(
2778 &mut windows,
2779 state.windows.popup_panel,
2780 "popup_panel",
2781 "Popup panel",
2782 UiSize::new(360.0, 200.0),
2783 );
2784 push_window(
2785 &mut windows,
2786 state.windows.canvas,
2787 "canvas",
2788 "Canvas",
2789 UiSize::new(560.0, 390.0),
2790 );
2791 push_window(
2792 &mut windows,
2793 state.windows.styling,
2794 "styling",
2795 "Styling",
2796 UiSize::new(540.0, 440.0),
2797 );
2798 for window in &mut windows {
2799 window.drag_action = Some(WidgetActionBinding::action(format!(
2800 "window.drag.{}",
2801 window.id
2802 )));
2803 window.collapse_action = Some(WidgetActionBinding::action(format!(
2804 "window.collapse.{}",
2805 window.id
2806 )));
2807 window.resize_action = Some(WidgetActionBinding::action(format!(
2808 "window.resize.{}",
2809 window.id
2810 )));
2811 state
2812 .desktop
2813 .apply_to_descriptor(window, window_defaults(window.id.as_str()));
2814 }
2815 windows
2816}
2817
2818fn push_window(
2819 windows: &mut Vec<ext_widgets::FloatingWindowDescriptor>,
2820 visible: bool,
2821 id: &'static str,
2822 title: &'static str,
2823 preferred_size: UiSize,
2824) {
2825 if visible {
2826 let mut window = ext_widgets::FloatingWindowDescriptor::new(id, title, preferred_size)
2827 .with_min_size(default_window_state_min_size(id))
2828 .with_auto_size_to_content(false)
2829 .with_activate_action(format!("window.activate.{id}"))
2830 .with_close_action(format!("window.close.{id}"));
2831 if id == "animation" {
2832 window = window.with_content_min_size(UiSize::new(
2833 ANIMATION_STAGE_MIN_WIDTH,
2834 ANIMATION_STAGE_HEIGHT * 4.0,
2835 ));
2836 } else if id == "layout_widgets" {
2837 window = window.with_content_min_size(UiSize::new(620.0, 360.0));
2838 }
2839 windows.push(window);
2840 }
2841}
2842
2843fn default_window_size(id: &str) -> UiSize {
2844 match id {
2845 "labels" => UiSize::new(380.0, 460.0),
2846 "buttons" => UiSize::new(604.0, 220.0),
2847 "checkbox" => UiSize::new(250.0, 72.0),
2848 "toggles" => UiSize::new(360.0, 380.0),
2849 "slider" => UiSize::new(430.0, 560.0),
2850 "numeric" => UiSize::new(430.0, 180.0),
2851 "text_input" => UiSize::new(520.0, 640.0),
2852 "selection" => UiSize::new(360.0, 360.0),
2853 "menus" => UiSize::new(640.0, 640.0),
2854 "command_palette" => UiSize::new(520.0, 320.0),
2855 "date_picker" => UiSize::new(284.0, 390.0),
2856 "color_picker" => UiSize::new(340.0, 390.0),
2857 "color_buttons" => UiSize::new(430.0, 360.0),
2858 "progress" => UiSize::new(500.0, 168.0),
2859 "animation" => UiSize::new(520.0, 430.0),
2860 "lists_tables" => UiSize::new(600.0, 700.0),
2861 "property_inspector" => UiSize::new(330.0, 250.0),
2862 "diagnostics" => UiSize::new(640.0, 760.0),
2863 "trees" => UiSize::new(430.0, 450.0),
2864 "layout_widgets" => UiSize::new(560.0, 400.0),
2865 "containers" => UiSize::new(560.0, 640.0),
2866 "forms" => UiSize::new(520.0, 620.0),
2867 "overlays" => UiSize::new(560.0, 560.0),
2868 "drag_drop" => UiSize::new(500.0, 460.0),
2869 "media" => UiSize::new(520.0, 430.0),
2870 "timeline" => UiSize::new(600.0, 120.0),
2871 "toasts" => UiSize::new(320.0, 270.0),
2872 "popup_panel" => UiSize::new(360.0, 200.0),
2873 "canvas" => UiSize::new(560.0, 390.0),
2874 "styling" => UiSize::new(640.0, 560.0),
2875 _ => UiSize::new(300.0, 180.0),
2876 }
2877}
2878
2879fn default_window_state_min_size(_id: &str) -> UiSize {
2880 UiSize::new(160.0, 96.0)
2881}
2882
2883fn showcase_window_title(id: &str) -> &'static str {
2884 match id {
2885 "labels" => "Labels",
2886 "buttons" => "Buttons",
2887 "checkbox" => "Checkbox",
2888 "toggles" => "Radio and toggles",
2889 "slider" => "Slider",
2890 "numeric" => "Numeric input",
2891 "text_input" => "Text input",
2892 "selection" => "Select controls",
2893 "menus" => "Menus",
2894 "command_palette" => "Command palette",
2895 "date_picker" => "Date picker",
2896 "color_picker" => "Color picker",
2897 "color_buttons" => "Color buttons",
2898 "progress" => "Progress indicator",
2899 "animation" => "Animation",
2900 "lists_tables" => "Lists and tables",
2901 "property_inspector" => "Property inspector",
2902 "diagnostics" => "Diagnostics",
2903 "trees" => "Trees",
2904 "layout_widgets" => "Layout widgets",
2905 "containers" => "Containers",
2906 "forms" => "Forms",
2907 "overlays" => "Overlays",
2908 "drag_drop" => "Drag and drop",
2909 "media" => "Media",
2910 "timeline" => "Timeline",
2911 "toasts" => "Toasts",
2912 "popup_panel" => "Popup panel",
2913 "canvas" => "Canvas",
2914 "styling" => "Styling",
2915 _ => "Window",
2916 }
2917}
2918
2919fn showcase_collapsed_window_size(
2920 id: &str,
2921 options: &ext_widgets::FloatingDesktopOptions,
2922) -> UiSize {
2923 let min_size = default_window_state_min_size(id);
2924 let padding = options.content_padding.max(0.0);
2925 let button = options.close_button_size.max(1.0);
2926 let control_width = (button + 8.0) * 2.0;
2927 let font_size = options.title_style.font_size.max(1.0);
2928 let title_width =
2929 (showcase_window_title(id).chars().count() as f32 * font_size * 0.55).max(font_size);
2930 UiSize::new(
2931 min_size
2932 .width
2933 .max(padding * 2.0 + control_width + title_width),
2934 options.title_bar_height.max(1.0),
2935 )
2936}
2937
2938fn default_window_position(id: &str) -> UiPoint {
2939 match id {
2940 "labels" => UiPoint::new(18.0, 18.0),
2941 "buttons" => UiPoint::new(420.0, 18.0),
2942 "checkbox" => UiPoint::new(360.0, 18.0),
2943 "toggles" => UiPoint::new(360.0, 110.0),
2944 "slider" => UiPoint::new(360.0, 110.0),
2945 "numeric" => UiPoint::new(360.0, 260.0),
2946 "text_input" => UiPoint::new(360.0, 18.0),
2947 "selection" => UiPoint::new(360.0, 404.0),
2948 "menus" => UiPoint::new(18.0, 18.0),
2949 "command_palette" => UiPoint::new(68.0, 88.0),
2950 "date_picker" => UiPoint::new(300.0, 170.0),
2951 "color_picker" => UiPoint::new(18.0, 560.0),
2952 "color_buttons" => UiPoint::new(380.0, 500.0),
2953 "progress" => UiPoint::new(72.0, 540.0),
2954 "animation" => UiPoint::new(180.0, 170.0),
2955 "lists_tables" => UiPoint::new(18.0, 90.0),
2956 "property_inspector" => UiPoint::new(300.0, 420.0),
2957 "diagnostics" => UiPoint::new(640.0, 70.0),
2958 "trees" => UiPoint::new(36.0, 220.0),
2959 "layout_widgets" => UiPoint::new(18.0, 18.0),
2960 "containers" => UiPoint::new(48.0, 120.0),
2961 "forms" => UiPoint::new(120.0, 160.0),
2962 "overlays" => UiPoint::new(80.0, 110.0),
2963 "drag_drop" => UiPoint::new(210.0, 250.0),
2964 "media" => UiPoint::new(120.0, 360.0),
2965 "timeline" => UiPoint::new(18.0, 620.0),
2966 "toasts" => UiPoint::new(320.0, 70.0),
2967 "popup_panel" => UiPoint::new(320.0, 370.0),
2968 "canvas" => UiPoint::new(280.0, 390.0),
2969 "styling" => UiPoint::new(86.0, 118.0),
2970 _ => UiPoint::new(18.0, 18.0),
2971 }
2972}
2973
2974fn window_for_action(action_id: &str) -> Option<&'static str> {
2975 match action_id {
2976 id if id.starts_with("labels.") => Some("labels"),
2977 id if id.starts_with("button.") => Some("buttons"),
2978 id if id.starts_with("checkbox.") => Some("checkbox"),
2979 id if id.starts_with("toggles.") => Some("toggles"),
2980 id if id.starts_with("theme.preference.") => Some("toggles"),
2981 id if id.starts_with("slider.") => Some("slider"),
2982 id if id.starts_with("numeric.") => Some("numeric"),
2983 id if id.starts_with("text.") => Some("text_input"),
2984 id if id.starts_with("combo.")
2985 || id.starts_with("selection.dropdown.")
2986 || id.starts_with("selection.menu.") =>
2987 {
2988 Some("selection")
2989 }
2990 id if id.starts_with("menus.") => Some("menus"),
2991 id if id.starts_with("command_palette.") => Some("command_palette"),
2992 id if id.starts_with("date.") => Some("date_picker"),
2993 id if id.starts_with("color.") => Some("color_picker"),
2994 id if id.starts_with("color_buttons.") => Some("color_buttons"),
2995 id if id.starts_with("progress.") => Some("progress"),
2996 id if id.starts_with("animation.") => Some("animation"),
2997 id if id.starts_with("lists_tables.") => Some("lists_tables"),
2998 id if id.starts_with("property_inspector.") => Some("property_inspector"),
2999 id if id.starts_with("diagnostics.") => Some("diagnostics"),
3000 id if id.starts_with("trees.") => Some("trees"),
3001 id if id.starts_with("layout.") || id.starts_with("layout_widgets.") => {
3002 Some("layout_widgets")
3003 }
3004 id if id.starts_with("containers.") => Some("containers"),
3005 id if id.starts_with("forms.") => Some("forms"),
3006 id if id.starts_with("overlays.") => Some("overlays"),
3007 id if id.starts_with("drag_drop.") => Some("drag_drop"),
3008 id if id.starts_with("media.") => Some("media"),
3009 id if id.starts_with("toast.") => Some("toasts"),
3010 id if id.starts_with("popup.") => Some("popup_panel"),
3011 id if id.starts_with("canvas.") => Some("canvas"),
3012 id if id.starts_with("styling.") => Some("styling"),
3013 _ => None,
3014 }
3015}
3016
3017fn focused_text_for_action(action_id: &str) -> Option<FocusedTextInput> {
3018 Some(match action_id {
3019 "text.input.edit" => FocusedTextInput::Editable,
3020 "text.selectable.edit" => FocusedTextInput::Selectable,
3021 "text.singleline.edit" => FocusedTextInput::Singleline,
3022 "text.multiline.edit" => FocusedTextInput::Multiline,
3023 "text.area.edit" => FocusedTextInput::TextArea,
3024 "text.code_editor.edit" => FocusedTextInput::CodeEditor,
3025 "text.search.edit" => FocusedTextInput::Search,
3026 "text.password.edit" => FocusedTextInput::Password,
3027 "forms.profile.name.input.edit" => FocusedTextInput::FormName,
3028 "forms.profile.email.input.edit" => FocusedTextInput::FormEmail,
3029 "forms.profile.role.input.edit" => FocusedTextInput::FormRole,
3030 "slider.value_text.edit" => FocusedTextInput::SliderValue,
3031 "slider.left_text.edit" => FocusedTextInput::SliderRangeLeft,
3032 "slider.right_text.edit" => FocusedTextInput::SliderRangeRight,
3033 "slider.step_text.edit" => FocusedTextInput::SliderStep,
3034 _ => return None,
3035 })
3036}
3037
3038fn control_panel(
3039 ui: &mut UiDocument,
3040 parent: UiNodeId,
3041 state: &ShowcaseState,
3042 viewport_height: f32,
3043) {
3044 widgets::label(
3045 ui,
3046 parent,
3047 "controls.title",
3048 "Widgets",
3049 text(16.0, color(244, 248, 252)),
3050 LayoutStyle::new().with_width_percent(1.0),
3051 );
3052 let list_viewport_height = controls_list_viewport_height(viewport_height);
3053 let controls_scroll =
3054 controls_scroll_state_for_view(state.controls_scroll, list_viewport_height);
3055 let list_nodes = scroll_area_widgets::scroll_container_shell(
3056 ui,
3057 parent,
3058 "controls.widget_list",
3059 controls_scroll,
3060 widgets::ScrollContainerOptions::default()
3061 .with_layout(
3062 LayoutStyle::column()
3063 .with_width_percent(1.0)
3064 .with_height(list_viewport_height)
3065 .with_flex_grow(1.0)
3066 .with_flex_shrink(1.0),
3067 )
3068 .with_viewport_layout(
3069 LayoutStyle::column()
3070 .with_width(0.0)
3071 .with_height_percent(1.0)
3072 .with_flex_grow(1.0)
3073 .with_flex_shrink(1.0)
3074 .gap(CONTROLS_WIDGET_ROW_GAP),
3075 )
3076 .with_axes(ScrollAxes::VERTICAL)
3077 .with_scrollbar_thickness(8.0)
3078 .with_gap(2.0)
3079 .with_action_prefix("controls.widget_list")
3080 .with_vertical_scrollbar(
3081 scrollbar_widgets::ScrollbarOptions::default()
3082 .with_action("controls.widget_list.scrollbar"),
3083 ),
3084 );
3085 let list = list_nodes.viewport;
3086
3087 window_toggle(ui, list, "labels", "Labels", state.windows.labels);
3088 window_toggle(ui, list, "buttons", "Buttons", state.windows.buttons);
3089 window_toggle(ui, list, "checkbox", "Checkbox", state.windows.checkbox);
3090 window_toggle(
3091 ui,
3092 list,
3093 "toggles",
3094 "Radio and toggles",
3095 state.windows.toggles,
3096 );
3097 window_toggle(ui, list, "slider", "Slider", state.windows.slider);
3098 window_toggle(ui, list, "numeric", "Numeric input", state.windows.numeric);
3099 window_toggle(
3100 ui,
3101 list,
3102 "text_input",
3103 "Text input",
3104 state.windows.text_input,
3105 );
3106 window_toggle(
3107 ui,
3108 list,
3109 "selection",
3110 "Select controls",
3111 state.windows.selection,
3112 );
3113 window_toggle(ui, list, "menus", "Menus", state.windows.menus);
3114 window_toggle(
3115 ui,
3116 list,
3117 "command_palette",
3118 "Command palette",
3119 state.windows.command_palette,
3120 );
3121 window_toggle(
3122 ui,
3123 list,
3124 "date_picker",
3125 "Date picker",
3126 state.windows.date_picker,
3127 );
3128 window_toggle(
3129 ui,
3130 list,
3131 "color_picker",
3132 "Color picker",
3133 state.windows.color_picker,
3134 );
3135 window_toggle(
3136 ui,
3137 list,
3138 "color_buttons",
3139 "Color buttons",
3140 state.windows.color_buttons,
3141 );
3142 window_toggle(
3143 ui,
3144 list,
3145 "progress",
3146 "Progress indicator",
3147 state.windows.progress,
3148 );
3149 window_toggle(ui, list, "animation", "Animation", state.windows.animation);
3150 window_toggle(
3151 ui,
3152 list,
3153 "lists_tables",
3154 "Lists and tables",
3155 state.windows.lists_tables,
3156 );
3157 window_toggle(
3158 ui,
3159 list,
3160 "property_inspector",
3161 "Property inspector",
3162 state.windows.property_inspector,
3163 );
3164 window_toggle(
3165 ui,
3166 list,
3167 "diagnostics",
3168 "Diagnostics",
3169 state.windows.diagnostics,
3170 );
3171 window_toggle(ui, list, "trees", "Trees", state.windows.trees);
3172 window_toggle(
3173 ui,
3174 list,
3175 "layout_widgets",
3176 "Layout widgets",
3177 state.windows.layout_widgets,
3178 );
3179 window_toggle(
3180 ui,
3181 list,
3182 "containers",
3183 "Containers",
3184 state.windows.containers,
3185 );
3186 window_toggle(ui, list, "forms", "Forms", state.windows.forms);
3187 window_toggle(ui, list, "overlays", "Overlays", state.windows.overlays);
3188 window_toggle(
3189 ui,
3190 list,
3191 "drag_drop",
3192 "Drag and drop",
3193 state.windows.drag_drop,
3194 );
3195 window_toggle(ui, list, "media", "Media", state.windows.media);
3196 window_toggle(ui, list, "timeline", "Timeline", state.windows.timeline);
3197 window_toggle(ui, list, "toasts", "Toasts", state.windows.toasts);
3198 window_toggle(
3199 ui,
3200 list,
3201 "popup_panel",
3202 "Popup panel",
3203 state.windows.popup_panel,
3204 );
3205 window_toggle(ui, list, "canvas", "Canvas", state.windows.canvas);
3206 window_toggle(ui, list, "styling", "Styling", state.windows.styling);
3207
3208 ui.add_child(
3209 parent,
3210 UiNode::container(
3211 "controls.clear_all.spacer",
3212 LayoutStyle::new()
3213 .with_width_percent(1.0)
3214 .with_height(1.0)
3215 .with_flex_grow(1.0)
3216 .with_flex_shrink(1.0),
3217 ),
3218 );
3219 let actions = ui.add_child(
3220 parent,
3221 UiNode::container(
3222 "controls.bulk_actions",
3223 LayoutStyle::row()
3224 .with_width_percent(1.0)
3225 .with_height(30.0)
3226 .with_flex_shrink(0.0)
3227 .gap(8.0),
3228 ),
3229 );
3230 control_action_button(
3231 ui,
3232 actions,
3233 "controls.add_all",
3234 "Add all",
3235 "window.add_all",
3236 "Add all widgets",
3237 );
3238 control_action_button(
3239 ui,
3240 actions,
3241 "controls.clear_all",
3242 "Clear all",
3243 "window.clear_all",
3244 "Clear all widgets",
3245 );
3246}
3247
3248fn control_action_button(
3249 ui: &mut UiDocument,
3250 parent: UiNodeId,
3251 name: &'static str,
3252 label: &'static str,
3253 action: &'static str,
3254 accessibility_label: &'static str,
3255) {
3256 let mut options = widgets::ButtonOptions::new(
3257 LayoutStyle::new()
3258 .with_width(0.0)
3259 .with_height_percent(1.0)
3260 .with_flex_grow(1.0)
3261 .with_flex_shrink(1.0),
3262 )
3263 .with_action(action);
3264 options.visual = UiVisual::panel(
3265 color(31, 38, 48),
3266 Some(StrokeStyle::new(color(76, 88, 106), 1.0)),
3267 4.0,
3268 );
3269 options.hovered_visual = Some(UiVisual::panel(
3270 color(45, 56, 70),
3271 Some(StrokeStyle::new(color(118, 144, 174), 1.0)),
3272 4.0,
3273 ));
3274 options.pressed_visual = Some(UiVisual::panel(
3275 color(20, 27, 36),
3276 Some(StrokeStyle::new(color(82, 104, 132), 1.0)),
3277 4.0,
3278 ));
3279 options.pressed_hovered_visual = Some(UiVisual::panel(
3280 color(36, 48, 62),
3281 Some(StrokeStyle::new(color(138, 170, 206), 1.0)),
3282 4.0,
3283 ));
3284 options.text_style = text(12.0, color(230, 236, 246));
3285 options.accessibility_label = Some(accessibility_label.to_string());
3286 widgets::button(ui, parent, name, label, options);
3287}
3288
3289fn window_toggle(
3290 ui: &mut UiDocument,
3291 parent: UiNodeId,
3292 id: &'static str,
3293 label: &'static str,
3294 checked: bool,
3295) {
3296 let mut options =
3297 widgets::CheckboxOptions::default().with_action(format!("window.toggle.{id}"));
3298 options.layout = LayoutStyle::new()
3299 .with_width_percent(1.0)
3300 .with_height(CONTROLS_WIDGET_ROW_HEIGHT);
3301 options.text_style = text(12.0, color(220, 228, 238));
3302 widgets::checkbox(
3303 ui,
3304 parent,
3305 format!("controls.{id}"),
3306 label,
3307 checked,
3308 options,
3309 );
3310}
3311
3312#[allow(clippy::field_reassign_with_default)]
3313fn labels(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3314 let body = section(ui, parent, "labels", "Labels");
3315 ui.set_node_style(
3316 body,
3317 LayoutStyle::column()
3318 .with_width_percent(1.0)
3319 .with_height_percent(1.0)
3320 .with_flex_grow(1.0)
3321 .gap(10.0),
3322 );
3323 widgets::label(
3324 ui,
3325 body,
3326 "labels.plain",
3327 "Plain label",
3328 text(13.0, color(226, 232, 242)),
3329 LayoutStyle::new().with_width_percent(1.0),
3330 );
3331 let locale_items = label_locale_options();
3332 let locale_id = state
3333 .label_locale
3334 .selected_id(&locale_items)
3335 .unwrap_or("es-MX");
3336 let localization =
3337 LocalizationPolicy::new(LocaleId::new(locale_id).unwrap_or_else(|_| LocaleId::default()));
3338 let locale_row = ui.add_child(
3339 body,
3340 UiNode::container(
3341 "labels.locale.row",
3342 LayoutStyle::row()
3343 .with_width_percent(1.0)
3344 .with_align_items(taffy::prelude::AlignItems::Center)
3345 .gap(10.0),
3346 ),
3347 );
3348 let locale_label_width = 270.0;
3349 let locale_dropdown_width = 148.0;
3350 let locale_gap = 10.0;
3351 widgets::localized_label(
3352 ui,
3353 locale_row,
3354 "labels.localized",
3355 DynamicLabelMeta::keyed("showcase.localized.greeting", localized_label(locale_id)),
3356 Some(&localization),
3357 text(13.0, color(170, 202, 255)),
3358 LayoutStyle::new().with_width(locale_label_width),
3359 );
3360 let mut locale_options = ext_widgets::DropdownSelectOptions::default();
3361 locale_options.trigger_layout = LayoutStyle::row()
3362 .with_width(locale_dropdown_width)
3363 .with_height(30.0)
3364 .with_align_items(taffy::prelude::AlignItems::Center)
3365 .with_justify_content(taffy::prelude::JustifyContent::Center)
3366 .padding(6.0);
3367 locale_options.text_style = text(13.0, color(226, 232, 242));
3368 locale_options.accessibility_label = Some("Locale".to_string());
3369 locale_options.menu =
3370 ext_widgets::SelectMenuOptions::default().with_action_prefix("labels.locale");
3371 locale_options.menu.width = locale_dropdown_width;
3372 locale_options.menu.row_height = 30.0;
3373 locale_options.menu.max_visible_rows = locale_items.len();
3374 locale_options.menu.text_style = text(13.0, color(226, 232, 242));
3375 locale_options.menu.portal = UiPortalTarget::Parent;
3376 let locale_nodes = ext_widgets::dropdown_select(
3377 ui,
3378 locale_row,
3379 "labels.locale",
3380 &locale_items,
3381 &state.label_locale,
3382 Some(ext_widgets::AnchoredPopup::new(
3383 UiRect::new(
3384 locale_label_width + locale_gap,
3385 0.0,
3386 locale_dropdown_width,
3387 30.0,
3388 ),
3389 UiRect::new(0.0, 0.0, 460.0, 260.0),
3390 ext_widgets::PopupPlacement::default().with_viewport_margin(0.0),
3391 )),
3392 locale_options,
3393 );
3394 ui.node_mut(locale_nodes.trigger)
3395 .set_action("labels.locale.toggle");
3396 widgets::label(
3397 ui,
3398 body,
3399 "labels.muted",
3400 "Muted helper label",
3401 text(12.0, color(154, 166, 184)),
3402 LayoutStyle::new().with_width_percent(1.0),
3403 );
3404
3405 let sizes = ui.add_child(
3406 body,
3407 UiNode::container(
3408 "labels.sizes",
3409 LayoutStyle::row()
3410 .with_width_percent(1.0)
3411 .with_align_items(taffy::prelude::AlignItems::FlexEnd)
3412 .gap(12.0),
3413 ),
3414 );
3415 widgets::label(
3416 ui,
3417 sizes,
3418 "labels.size.small",
3419 "12px",
3420 text(12.0, color(226, 232, 242)),
3421 LayoutStyle::new(),
3422 );
3423 widgets::label(
3424 ui,
3425 sizes,
3426 "labels.size.default",
3427 "13px",
3428 text(13.0, color(226, 232, 242)),
3429 LayoutStyle::new(),
3430 );
3431 widgets::label(
3432 ui,
3433 sizes,
3434 "labels.size.large",
3435 "18px",
3436 text(18.0, color(246, 249, 252)),
3437 LayoutStyle::new(),
3438 );
3439 widgets::label(
3440 ui,
3441 sizes,
3442 "labels.size.display",
3443 "24px",
3444 text(24.0, color(246, 249, 252)),
3445 LayoutStyle::new(),
3446 );
3447
3448 let style_row = row(ui, body, "labels.styles", 12.0);
3449 let mut bold = text(13.0, color(246, 249, 252));
3450 bold.weight = FontWeight::BOLD;
3451 widgets::label(
3452 ui,
3453 style_row,
3454 "labels.style.bold",
3455 "Bold",
3456 bold,
3457 LayoutStyle::new(),
3458 );
3459 widgets::label(
3460 ui,
3461 style_row,
3462 "labels.style.weak",
3463 "Muted",
3464 text(13.0, color(154, 166, 184)),
3465 LayoutStyle::new(),
3466 );
3467
3468 let font_row = row(ui, body, "labels.fonts", 12.0);
3469 let mut serif = text(13.0, color(226, 232, 242));
3470 serif.family = FontFamily::Serif;
3471 widgets::label(
3472 ui,
3473 font_row,
3474 "labels.font.serif",
3475 "Serif",
3476 serif,
3477 LayoutStyle::new(),
3478 );
3479 let mut mono = text(13.0, color(226, 232, 242));
3480 mono.family = FontFamily::Monospace;
3481 widgets::label(
3482 ui,
3483 font_row,
3484 "labels.font.mono",
3485 "Monospace",
3486 mono,
3487 LayoutStyle::new(),
3488 );
3489
3490 let code_panel = ui.add_child(
3491 body,
3492 UiNode::container(
3493 "labels.code.panel",
3494 LayoutStyle::new()
3495 .with_width_percent(1.0)
3496 .padding(8.0)
3497 .with_height(36.0),
3498 )
3499 .with_visual(UiVisual::panel(
3500 color(10, 14, 20),
3501 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
3502 4.0,
3503 )),
3504 );
3505 widgets::code_label(
3506 ui,
3507 code_panel,
3508 "labels.code",
3509 "let label = widgets::label(...);",
3510 LayoutStyle::new().with_width_percent(1.0),
3511 );
3512
3513 let colors = row(ui, body, "labels.colors", 14.0);
3514 widgets::colored_label(
3515 ui,
3516 colors,
3517 "labels.color.green",
3518 "Green",
3519 color(111, 203, 159),
3520 LayoutStyle::new(),
3521 );
3522 widgets::colored_label(
3523 ui,
3524 colors,
3525 "labels.color.yellow",
3526 "Yellow",
3527 color(232, 196, 101),
3528 LayoutStyle::new(),
3529 );
3530 widgets::colored_label(
3531 ui,
3532 colors,
3533 "labels.color.red",
3534 "Red",
3535 color(244, 118, 118),
3536 LayoutStyle::new(),
3537 );
3538
3539 let wrap_row = wrapping_row(ui, body, "labels.wrap.row", 10.0);
3540 let wrap_word = ui.add_child(
3541 wrap_row,
3542 UiNode::container(
3543 "labels.wrap.word.panel",
3544 LayoutStyle::column().with_width(172.0).padding(8.0),
3545 )
3546 .with_visual(UiVisual::panel(
3547 color(18, 23, 31),
3548 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
3549 4.0,
3550 )),
3551 );
3552 widgets::wrapped_label(
3553 ui,
3554 wrap_word,
3555 "labels.wrap.word",
3556 "Word wrapping keeps this sentence readable in a narrow box.",
3557 TextWrap::Word,
3558 LayoutStyle::new().with_width_percent(1.0),
3559 );
3560 let wrap_glyph = ui.add_child(
3561 wrap_row,
3562 UiNode::container(
3563 "labels.wrap.glyph.panel",
3564 LayoutStyle::column().with_width(172.0).padding(8.0),
3565 )
3566 .with_visual(UiVisual::panel(
3567 color(18, 23, 31),
3568 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
3569 4.0,
3570 )),
3571 );
3572 widgets::wrapped_label(
3573 ui,
3574 wrap_glyph,
3575 "labels.wrap.glyph",
3576 "LongIdentifierWithoutSpaces",
3577 TextWrap::Glyph,
3578 LayoutStyle::new().with_width_percent(1.0),
3579 );
3580
3581 let links = wrapping_row(ui, body, "labels.links", 12.0);
3582 widgets::link(
3583 ui,
3584 links,
3585 "labels.link",
3586 "Internal action",
3587 widgets::LinkOptions::default()
3588 .visited(state.label_link_visited)
3589 .with_action("labels.link"),
3590 );
3591 widgets::hyperlink(
3592 ui,
3593 links,
3594 "labels.hyperlink",
3595 "Open docs.rs",
3596 "https://docs.rs/operad",
3597 widgets::LinkOptions::default()
3598 .visited(state.label_hyperlink_visited)
3599 .with_action("labels.hyperlink"),
3600 );
3601 if state.label_link_status != "No link action yet" {
3602 widgets::label(
3603 ui,
3604 body,
3605 "labels.status",
3606 format!("Last action: {}", state.label_link_status),
3607 text(12.0, color(154, 166, 184)),
3608 LayoutStyle::new().with_width_percent(1.0),
3609 );
3610 }
3611}
3612
3613fn buttons(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3614 let body = section(ui, parent, "buttons", "Buttons");
3615 let primary_row = wrapping_row(ui, body, "buttons.row", 10.0);
3616 button(
3617 ui,
3618 primary_row,
3619 "button.default",
3620 "Default",
3621 "button.default",
3622 button_visual(38, 46, 58),
3623 );
3624 button(
3625 ui,
3626 primary_row,
3627 "button.primary",
3628 "Primary",
3629 "button.primary",
3630 button_visual(48, 112, 184),
3631 );
3632 button(
3633 ui,
3634 primary_row,
3635 "button.secondary",
3636 "Secondary",
3637 "button.secondary",
3638 button_visual(58, 78, 96),
3639 );
3640 button(
3641 ui,
3642 primary_row,
3643 "button.destructive",
3644 "Destructive",
3645 "button.destructive",
3646 button_visual(157, 65, 73),
3647 );
3648 let mut disabled = widgets::ButtonOptions::new(LayoutStyle::size(92.0, 32.0));
3649 disabled.enabled = false;
3650 disabled.visual = button_visual(40, 44, 52);
3651 disabled.text_style = text(13.0, color(138, 146, 158));
3652 widgets::button(ui, primary_row, "button.disabled", "Disabled", disabled);
3653 let second_row = wrapping_row(ui, body, "buttons.row.options", 10.0);
3654 button(
3655 ui,
3656 second_row,
3657 "button.momentary",
3658 "Press only",
3659 "button.default",
3660 button_visual(42, 50, 62),
3661 );
3662 let mut toggle =
3663 widgets::ButtonOptions::new(LayoutStyle::size(112.0, 32.0)).with_action("button.toggle");
3664 toggle.pressed = state.toggle_button;
3665 toggle.visual = button_visual(42, 50, 62);
3666 toggle.hovered_visual = Some(button_visual(62, 74, 92));
3667 toggle.pressed_visual = Some(button_visual(86, 64, 156));
3668 toggle.pressed_hovered_visual = Some(button_visual(126, 94, 218));
3669 toggle.text_style = text(13.0, color(246, 249, 252));
3670 widgets::button(
3671 ui,
3672 second_row,
3673 "button.toggle",
3674 if state.toggle_button {
3675 "Toggle on"
3676 } else {
3677 "Toggle off"
3678 },
3679 toggle,
3680 );
3681 let mut forced_pressed = widgets::ButtonOptions::new(LayoutStyle::size(112.0, 32.0));
3682 forced_pressed.pressed = true;
3683 forced_pressed.visual = button_visual(42, 50, 62);
3684 forced_pressed.hovered_visual = Some(button_visual(62, 74, 92));
3685 forced_pressed.pressed_visual = Some(button_visual(38, 82, 136));
3686 forced_pressed.pressed_hovered_visual = Some(button_visual(62, 126, 196));
3687 forced_pressed.text_style = text(13.0, color(246, 249, 252));
3688 widgets::button(
3689 ui,
3690 second_row,
3691 "button.state.pressed",
3692 "Pressed",
3693 forced_pressed,
3694 );
3695 let helper_row = wrapping_row(ui, body, "buttons.row.helpers", 10.0);
3696 widgets::small_button(
3697 ui,
3698 helper_row,
3699 "button.small",
3700 "Small",
3701 widgets::ButtonOptions::default().with_action("button.small"),
3702 );
3703 widgets::icon_button(
3704 ui,
3705 helper_row,
3706 "button.icon",
3707 icon_image(BuiltInIcon::Settings),
3708 "Settings",
3709 widgets::ButtonOptions::default().with_action("button.icon"),
3710 );
3711 widgets::image_button(
3712 ui,
3713 helper_row,
3714 "button.image",
3715 icon_image(BuiltInIcon::Folder),
3716 "Folder",
3717 widgets::ButtonOptions::default().with_action("button.image"),
3718 );
3719 widgets::reset_button(
3720 ui,
3721 helper_row,
3722 "button.reset",
3723 state.toggle_button,
3724 widgets::ButtonOptions::default().with_action("button.reset"),
3725 );
3726 widgets::toggle_button(
3727 ui,
3728 helper_row,
3729 "button.toggle_helper",
3730 "Toggle helper",
3731 state.toggle_button,
3732 widgets::ButtonOptions::default().with_action("button.toggle"),
3733 );
3734 widgets::label(
3735 ui,
3736 body,
3737 "buttons.last",
3738 format!("Last pressed: {}", state.last_button),
3739 text(12.0, color(154, 166, 184)),
3740 LayoutStyle::new().with_width_percent(1.0),
3741 );
3742}
3743
3744fn checkbox(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3745 let body = section(ui, parent, "checkbox", "Checkbox");
3746 let mut options = widgets::CheckboxOptions::default().with_action("checkbox.enabled");
3747 options.text_style = text(13.0, color(222, 228, 238));
3748 widgets::checkbox(
3749 ui,
3750 body,
3751 "checkbox.enabled",
3752 if state.checked { "Enabled" } else { "Disabled" },
3753 state.checked,
3754 options,
3755 );
3756}
3757
3758fn toggles(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3759 let body = section(ui, parent, "toggles", "Radio and toggles");
3760 let radio_options = [
3761 widgets::RadioOption::new("compact", "Compact").with_action("toggles.radio.compact"),
3762 widgets::RadioOption::new("comfortable", "Comfortable")
3763 .with_action("toggles.radio.comfortable"),
3764 widgets::RadioOption::new("spacious", "Spacious").with_action("toggles.radio.spacious"),
3765 widgets::RadioOption::new("disabled", "Disabled").enabled(false),
3766 ];
3767 widgets::radio_group(
3768 ui,
3769 body,
3770 "toggles.radio_group",
3771 &radio_options,
3772 Some(state.radio_choice),
3773 widgets::RadioGroupOptions::default(),
3774 );
3775 widgets::radio_button(
3776 ui,
3777 body,
3778 "toggles.radio_single",
3779 "Standalone radio button",
3780 true,
3781 widgets::RadioButtonOptions::default().with_action("toggles.radio.compact"),
3782 );
3783 widgets::toggle_switch(
3784 ui,
3785 body,
3786 "toggles.switch",
3787 if state.switch_enabled {
3788 "Switch on"
3789 } else {
3790 "Switch off"
3791 },
3792 ext_widgets::ToggleValue::from(state.switch_enabled),
3793 widgets::ToggleSwitchOptions::default().with_action("toggles.switch"),
3794 );
3795 widgets::toggle_switch(
3796 ui,
3797 body,
3798 "toggles.mixed",
3799 match state.mixed_switch {
3800 ext_widgets::ToggleValue::Mixed => "Mixed switch",
3801 ext_widgets::ToggleValue::On => "Mixed switch on",
3802 ext_widgets::ToggleValue::Off => "Mixed switch off",
3803 },
3804 state.mixed_switch,
3805 widgets::ToggleSwitchOptions::default().with_action("toggles.mixed"),
3806 );
3807 widgets::theme_preference_buttons(
3808 ui,
3809 body,
3810 "toggles.theme_buttons",
3811 state.theme_preference,
3812 widgets::ThemePreferenceButtonsOptions::default().with_action_prefix("toggles.theme"),
3813 );
3814 widgets::theme_preference_switch(
3815 ui,
3816 body,
3817 "toggles.theme_switch",
3818 state.theme_preference,
3819 widgets::ThemePreferenceSwitchOptions::default().with_action("theme.preference.dark"),
3820 );
3821}
3822
3823fn slider(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3824 let body = section(ui, parent, "slider", "Slider");
3825 widgets::label(
3826 ui,
3827 body,
3828 "slider.note",
3829 "Click a slider value to edit it with the keyboard.",
3830 text(12.0, color(166, 176, 190)),
3831 LayoutStyle::new().with_width_percent(1.0),
3832 );
3833
3834 let value_row = row(ui, body, "slider.value.row", 10.0);
3835 let options = slider_options(state, 180.0).with_value_edit_action("slider.value");
3836 let slider_unit = state.slider_value_spec().normalize(state.slider);
3837 widgets::slider(
3838 ui,
3839 value_row,
3840 "slider.value",
3841 slider_unit,
3842 0.0..1.0,
3843 options.clone(),
3844 );
3845 slider_number_input(
3846 ui,
3847 value_row,
3848 "slider.value_text",
3849 &state.slider_value_text,
3850 FocusedTextInput::SliderValue,
3851 state,
3852 86.0,
3853 );
3854 widgets::label(
3855 ui,
3856 value_row,
3857 "slider.value.label",
3858 "f64 demo slider",
3859 text(12.0, color(186, 198, 216)),
3860 LayoutStyle::new().with_width_percent(1.0),
3861 );
3862
3863 widgets::label(
3864 ui,
3865 body,
3866 "slider.precision",
3867 format!(
3868 "Displayed value: {} Full precision: {:.6}",
3869 widgets::slider::format_slider_value(state.slider),
3870 state.slider
3871 ),
3872 text(11.0, color(154, 166, 184)),
3873 LayoutStyle::new().with_width_percent(1.0),
3874 );
3875
3876 divider(ui, body, "slider.divider.range");
3877 widgets::label(
3878 ui,
3879 body,
3880 "slider.range.label",
3881 "Slider range",
3882 text(12.0, color(220, 228, 238)),
3883 LayoutStyle::new().with_width_percent(1.0),
3884 );
3885 let left_row = row(ui, body, "slider.range.left.row", 10.0);
3886 let left_options = widgets::SliderOptions::default()
3887 .with_layout(
3888 LayoutStyle::new()
3889 .with_width(180.0)
3890 .with_height(24.0)
3891 .with_flex_shrink(0.0),
3892 )
3893 .with_value_edit_action("slider.range_left");
3894 widgets::slider(
3895 ui,
3896 left_row,
3897 "slider.range_left",
3898 state.slider_left,
3899 0.0..state.slider_right.max(1.0),
3900 left_options,
3901 );
3902 slider_number_input(
3903 ui,
3904 left_row,
3905 "slider.left_text",
3906 &state.slider_left_text,
3907 FocusedTextInput::SliderRangeLeft,
3908 state,
3909 96.0,
3910 );
3911 widgets::label(
3912 ui,
3913 left_row,
3914 "slider.range.left.label",
3915 "left",
3916 text(12.0, color(186, 198, 216)),
3917 LayoutStyle::new().with_width(46.0),
3918 );
3919 let right_row = row(ui, body, "slider.range.right.row", 10.0);
3920 let right_options = widgets::SliderOptions::default()
3921 .with_layout(
3922 LayoutStyle::new()
3923 .with_width(180.0)
3924 .with_height(24.0)
3925 .with_flex_shrink(0.0),
3926 )
3927 .with_value_edit_action("slider.range_right");
3928 widgets::slider(
3929 ui,
3930 right_row,
3931 "slider.range_right",
3932 state.slider_right,
3933 (state.slider_left + 1.0)..10000.0,
3934 right_options,
3935 );
3936 slider_number_input(
3937 ui,
3938 right_row,
3939 "slider.right_text",
3940 &state.slider_right_text,
3941 FocusedTextInput::SliderRangeRight,
3942 state,
3943 96.0,
3944 );
3945 widgets::label(
3946 ui,
3947 right_row,
3948 "slider.range.right.label",
3949 "right",
3950 text(12.0, color(186, 198, 216)),
3951 LayoutStyle::new().with_width(46.0),
3952 );
3953
3954 divider(ui, body, "slider.divider.trailing");
3955 let trailing_row = row(ui, body, "slider.trailing.row", 8.0);
3956 slider_checkbox_with_layout(
3957 ui,
3958 trailing_row,
3959 "slider.trailing",
3960 "Trailing color",
3961 state.slider_trailing_color,
3962 LayoutStyle::new()
3963 .with_width(142.0)
3964 .with_height(30.0)
3965 .with_flex_shrink(0.0),
3966 );
3967 ext_widgets::color_edit_button(
3968 ui,
3969 trailing_row,
3970 "slider.trailing_color_button",
3971 state.slider_trailing_picker.value(),
3972 color_square_button_options("slider.trailing_color_button")
3973 .with_format(ext_widgets::ColorValueFormat::Rgb)
3974 .accessibility_label("Pick trailing slider color"),
3975 );
3976 if state.slider_trailing_picker_open {
3977 ext_widgets::color_picker(
3978 ui,
3979 body,
3980 "slider.trailing_picker",
3981 &state.slider_trailing_picker,
3982 ext_widgets::ColorPickerOptions::default()
3983 .with_label("Trailing slider color")
3984 .with_action_prefix("slider.trailing_picker"),
3985 );
3986 }
3987 let thumb_row = row(ui, body, "slider.thumb.row", 8.0);
3988 widgets::label(
3989 ui,
3990 thumb_row,
3991 "slider.thumb.label",
3992 "Thumb",
3993 text(12.0, color(166, 176, 190)),
3994 LayoutStyle::new().with_width(64.0),
3995 );
3996 choice_button(
3997 ui,
3998 thumb_row,
3999 "slider.thumb.circle",
4000 "Circle",
4001 state.slider_thumb_shape == SliderThumbChoice::Circle,
4002 );
4003 choice_button(
4004 ui,
4005 thumb_row,
4006 "slider.thumb.square",
4007 "Square",
4008 state.slider_thumb_shape == SliderThumbChoice::Square,
4009 );
4010 choice_button(
4011 ui,
4012 thumb_row,
4013 "slider.thumb.rectangle",
4014 "Rectangle",
4015 state.slider_thumb_shape == SliderThumbChoice::Rectangle,
4016 );
4017 slider_checkbox(
4018 ui,
4019 body,
4020 "slider.steps",
4021 "Use steps",
4022 state.slider_use_steps,
4023 );
4024 let step_row = row(ui, body, "slider.step.row", 10.0);
4025 widgets::label(
4026 ui,
4027 step_row,
4028 "slider.step.label",
4029 "Step value",
4030 text(12.0, color(166, 176, 190)),
4031 LayoutStyle::new().with_width(74.0),
4032 );
4033 slider_number_input(
4034 ui,
4035 step_row,
4036 "slider.step_text",
4037 &state.slider_step_text,
4038 FocusedTextInput::SliderStep,
4039 state,
4040 86.0,
4041 );
4042 slider_checkbox(
4043 ui,
4044 body,
4045 "slider.logarithmic",
4046 "Logarithmic",
4047 state.slider_logarithmic,
4048 );
4049 let clamp_row = row(ui, body, "slider.clamping.row", 8.0);
4050 widgets::label(
4051 ui,
4052 clamp_row,
4053 "slider.clamping.label",
4054 "Clamping",
4055 text(12.0, color(166, 176, 190)),
4056 LayoutStyle::new().with_width(74.0),
4057 );
4058 choice_button(
4059 ui,
4060 clamp_row,
4061 "slider.clamping.never",
4062 "Never",
4063 state.slider_clamping == widgets::SliderClamping::Never,
4064 );
4065 choice_button(
4066 ui,
4067 clamp_row,
4068 "slider.clamping.edits",
4069 "Edits",
4070 state.slider_clamping == widgets::SliderClamping::Edits,
4071 );
4072 choice_button(
4073 ui,
4074 clamp_row,
4075 "slider.clamping.always",
4076 "Always",
4077 state.slider_clamping == widgets::SliderClamping::Always,
4078 );
4079 slider_checkbox(
4080 ui,
4081 body,
4082 "slider.smart_aim",
4083 "Smart aim",
4084 state.slider_smart_aim,
4085 );
4086}
4087
4088fn numeric_inputs(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4089 let body = section(ui, parent, "numeric", "Numeric input");
4090 let row_one = row(ui, body, "numeric.row.values", 10.0);
4091 widgets::drag_value_input(
4092 ui,
4093 row_one,
4094 "numeric.drag_value",
4095 state.numeric_value as f64,
4096 widgets::DragValueOptions::default()
4097 .with_range(ext_widgets::NumericRange::new(0.0, 100.0))
4098 .with_precision(ext_widgets::NumericPrecision::decimals(1))
4099 .with_unit(ext_widgets::NumericUnitFormat::default().suffix(" px"))
4100 .with_action("numeric.drag_value"),
4101 );
4102 widgets::drag_angle(
4103 ui,
4104 row_one,
4105 "numeric.drag_angle",
4106 state.numeric_angle as f64,
4107 widgets::DragValueOptions::default().with_action("numeric.drag_angle"),
4108 );
4109 widgets::drag_angle_tau(
4110 ui,
4111 row_one,
4112 "numeric.drag_angle_tau",
4113 state.numeric_tau as f64,
4114 widgets::DragValueOptions::default().with_action("numeric.drag_angle_tau"),
4115 );
4116 widgets::label(
4117 ui,
4118 body,
4119 "numeric.note",
4120 "Drag values expose spinbutton semantics and unit-aware formatting.",
4121 text(12.0, color(166, 176, 190)),
4122 LayoutStyle::new().with_width_percent(1.0),
4123 );
4124}
4125
4126#[allow(clippy::field_reassign_with_default)]
4127fn selection_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4128 let body = section(ui, parent, "selection", "Select controls");
4129 let select_width = 180.0;
4130
4131 widgets::label(
4132 ui,
4133 body,
4134 "selection.combo.label",
4135 "Combo box",
4136 text(12.0, color(166, 176, 190)),
4137 LayoutStyle::new().with_width_percent(1.0),
4138 );
4139
4140 let mut options = widgets::ComboBoxOptions::default();
4141 options.accessibility_label = Some("Display density".to_string());
4142 options.text_style = text(13.0, color(230, 236, 246));
4143 options.layout = LayoutStyle::new()
4144 .with_width(select_width)
4145 .with_height(30.0);
4146 let combo_anchor = ui.add_child(
4147 body,
4148 UiNode::container(
4149 "selection.combo.anchor",
4150 LayoutStyle::new()
4151 .with_width(select_width)
4152 .with_height(30.0),
4153 ),
4154 );
4155 let combo = widgets::combo_box(
4156 ui,
4157 combo_anchor,
4158 "combo.toggle",
4159 state.combo_label.clone(),
4160 state.combo_open,
4161 options,
4162 );
4163 ui.node_mut(combo).set_action("combo.toggle");
4164 let select_options = select_options();
4165 if state.combo_open {
4166 let combo_state = select_options
4167 .iter()
4168 .position(|option| option.label == state.combo_label)
4169 .map(ext_widgets::SelectMenuState::with_selected)
4170 .unwrap_or_default()
4171 .with_open(&select_options);
4172 ext_widgets::select_menu_popup(
4173 ui,
4174 combo_anchor,
4175 "selection.combo_menu",
4176 ext_widgets::AnchoredPopup::new(
4177 UiRect::new(0.0, 0.0, select_width, 30.0),
4178 UiRect::new(0.0, 0.0, 320.0, 308.0),
4179 ext_widgets::PopupPlacement::default().with_viewport_margin(0.0),
4180 ),
4181 &select_options,
4182 &combo_state,
4183 select_menu_options(select_width).with_action_prefix("selection.combo"),
4184 );
4185 }
4186
4187 widgets::label(
4188 ui,
4189 body,
4190 "selection.menu.label",
4191 "Select menu",
4192 text(12.0, color(166, 176, 190)),
4193 LayoutStyle::new().with_width_percent(1.0),
4194 );
4195 ext_widgets::select_menu(
4196 ui,
4197 body,
4198 "selection.select_menu",
4199 &select_options,
4200 &state.select_menu,
4201 ext_widgets::SelectMenuOptions::default().with_action_prefix("selection.menu"),
4202 );
4203 widgets::label(
4204 ui,
4205 body,
4206 "selection.dropdown.label",
4207 "Dropdown select",
4208 text(12.0, color(166, 176, 190)),
4209 LayoutStyle::new().with_width_percent(1.0),
4210 );
4211 let mut dropdown_options = ext_widgets::DropdownSelectOptions::default();
4212 dropdown_options.menu =
4213 select_menu_options(select_width).with_action_prefix("selection.dropdown");
4214 let dropdown_anchor = ui.add_child(
4215 body,
4216 UiNode::container(
4217 "selection.dropdown.anchor",
4218 LayoutStyle::new()
4219 .with_width(select_width)
4220 .with_height(30.0),
4221 ),
4222 );
4223 let dropdown_nodes = ext_widgets::dropdown_select(
4224 ui,
4225 dropdown_anchor,
4226 "selection.dropdown",
4227 &select_options,
4228 &state.dropdown,
4229 Some(ext_widgets::AnchoredPopup::new(
4230 UiRect::new(0.0, 0.0, select_width, 30.0),
4231 UiRect::new(0.0, 0.0, 320.0, 308.0),
4232 ext_widgets::PopupPlacement::default().with_viewport_margin(0.0),
4233 )),
4234 dropdown_options,
4235 );
4236 ui.node_mut(dropdown_nodes.trigger)
4237 .set_action("selection.dropdown.toggle");
4238}
4239
4240#[allow(clippy::field_reassign_with_default)]
4241fn select_menu_options(width: f32) -> ext_widgets::SelectMenuOptions {
4242 let mut options = ext_widgets::SelectMenuOptions::default();
4243 options.width = width;
4244 options.portal = UiPortalTarget::Parent;
4245 options
4246}
4247
4248#[allow(clippy::field_reassign_with_default)]
4249fn text_input(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4250 let body = section(ui, parent, "text_input", "Text input");
4251 let mut options = TextInputOptions::default();
4252 options.placeholder = "Type here".to_string();
4253 options.layout = LayoutStyle::new().with_width(300.0).with_height(36.0);
4254 options.text_style = text(13.0, color(230, 236, 246));
4255 options.placeholder_style = text(13.0, color(144, 156, 174));
4256 options.edit_action = Some("text.input.edit".into());
4257 options.focused = state.focused_text == Some(FocusedTextInput::Editable);
4258 options.caret_visible = caret_visible(state.caret_phase);
4259 widgets::text_input(ui, body, "text.input", &state.text, options);
4260
4261 let mut selectable_options = TextInputOptions::default();
4262 selectable_options.layout = LayoutStyle::new().with_width(360.0).with_height(36.0);
4263 selectable_options.text_style = text(13.0, color(196, 210, 230));
4264 selectable_options.read_only = true;
4265 selectable_options.selectable = true;
4266 selectable_options.focused = state.focused_text == Some(FocusedTextInput::Selectable);
4267 selectable_options.edit_action = Some("text.selectable.edit".into());
4268 selectable_options.caret_visible = caret_visible(state.caret_phase);
4269 widgets::text_input(
4270 ui,
4271 body,
4272 "text.selectable",
4273 &state.selectable_text,
4274 selectable_options,
4275 );
4276
4277 let mut singleline = TextInputOptions::default();
4278 singleline.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
4279 singleline.text_style = text(13.0, color(230, 236, 246));
4280 singleline.placeholder = "Single line".to_string();
4281 singleline.edit_action = Some("text.singleline.edit".into());
4282 singleline.focused = state.focused_text == Some(FocusedTextInput::Singleline);
4283 singleline.caret_visible = caret_visible(state.caret_phase);
4284 widgets::singleline_text_input(
4285 ui,
4286 body,
4287 "text.singleline",
4288 &state.singleline_text,
4289 singleline,
4290 );
4291
4292 let mut multiline = TextInputOptions::default();
4293 multiline.layout = LayoutStyle::new().with_width(360.0).with_height(72.0);
4294 multiline.text_style = text(13.0, color(230, 236, 246));
4295 multiline.edit_action = Some("text.multiline.edit".into());
4296 multiline.focused = state.focused_text == Some(FocusedTextInput::Multiline);
4297 multiline.caret_visible = caret_visible(state.caret_phase);
4298 widgets::multiline_text_input(ui, body, "text.multiline", &state.multiline_text, multiline);
4299
4300 let mut area = TextInputOptions::default();
4301 area.layout = LayoutStyle::new().with_width(360.0).with_height(66.0);
4302 area.text_style = text(13.0, color(230, 236, 246));
4303 area.edit_action = Some("text.area.edit".into());
4304 area.focused = state.focused_text == Some(FocusedTextInput::TextArea);
4305 area.caret_visible = caret_visible(state.caret_phase);
4306 widgets::text_area(ui, body, "text.area", &state.text_area_text, area);
4307
4308 let mut code = TextInputOptions::default();
4309 code.layout = LayoutStyle::new().with_width(360.0).with_height(88.0);
4310 code.edit_action = Some("text.code_editor.edit".into());
4311 code.focused = state.focused_text == Some(FocusedTextInput::CodeEditor);
4312 code.caret_visible = caret_visible(state.caret_phase);
4313 widgets::code_editor(ui, body, "text.code_editor", &state.code_editor_text, code);
4314
4315 let mut search = TextInputOptions::default();
4316 search.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
4317 search.text_style = text(13.0, color(230, 236, 246));
4318 search.edit_action = Some("text.search.edit".into());
4319 search.focused = state.focused_text == Some(FocusedTextInput::Search);
4320 search.caret_visible = caret_visible(state.caret_phase);
4321 widgets::search_input(ui, body, "text.search", &state.search_text, search);
4322
4323 let mut password = TextInputOptions::default();
4324 password.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
4325 password.text_style = text(13.0, color(230, 236, 246));
4326 password.edit_action = Some("text.password.edit".into());
4327 password.focused = state.focused_text == Some(FocusedTextInput::Password);
4328 password.caret_visible = caret_visible(state.caret_phase);
4329 widgets::password_input(ui, body, "text.password", &state.password_text, password);
4330}
4331
4332fn date_picker(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4333 let body = section(ui, parent, "date", "Date picker");
4334 let controls = row(ui, body, "date.options", 8.0);
4335 choice_button(
4336 ui,
4337 controls,
4338 "date.week.sunday",
4339 "Sun first",
4340 state.date.first_weekday == ext_widgets::Weekday::Sunday,
4341 );
4342 choice_button(
4343 ui,
4344 controls,
4345 "date.week.monday",
4346 "Mon first",
4347 state.date.first_weekday == ext_widgets::Weekday::Monday,
4348 );
4349 let mut range_button =
4350 widgets::ButtonOptions::new(LayoutStyle::new().with_width(92.0).with_height(28.0))
4351 .with_action("date.range.toggle");
4352 range_button.visual = if state.date.min.is_some() || state.date.max.is_some() {
4353 button_visual(48, 112, 184)
4354 } else {
4355 button_visual(38, 46, 58)
4356 };
4357 range_button.hovered_visual = Some(button_visual(65, 86, 106));
4358 range_button.text_style = text(12.0, color(238, 244, 252));
4359 widgets::button(
4360 ui,
4361 controls,
4362 "date.range.toggle",
4363 "Limit range",
4364 range_button,
4365 );
4366 ext_widgets::date_picker(
4367 ui,
4368 body,
4369 "date.picker",
4370 &state.date,
4371 ext_widgets::DatePickerOptions::default().with_action_prefix("date"),
4372 );
4373 widgets::label(
4374 ui,
4375 body,
4376 "date.selected",
4377 format!(
4378 "Selected: {}",
4379 state
4380 .date
4381 .selected
4382 .map_or_else(|| "None".to_string(), CalendarDate::iso_string)
4383 ),
4384 text(11.0, color(154, 166, 184)),
4385 LayoutStyle::new().with_width_percent(1.0),
4386 );
4387}
4388
4389fn color_picker(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4390 let body = section(ui, parent, "color", "Color picker");
4391 ext_widgets::color_picker(
4392 ui,
4393 body,
4394 "color.picker",
4395 &state.color,
4396 ext_widgets::ColorPickerOptions::default()
4397 .with_action_prefix("color")
4398 .with_copy_hex_action("color.copy_hex")
4399 .with_copy_hex_label("Copy"),
4400 );
4401 if let Some(hex) = &state.color_copied_hex {
4402 widgets::label(
4403 ui,
4404 body,
4405 "color.copied",
4406 format!("Copied {hex}"),
4407 text(11.0, color(154, 166, 184)),
4408 LayoutStyle::new().with_width_percent(1.0),
4409 );
4410 }
4411}
4412
4413fn color_buttons(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4414 let body = section(ui, parent, "color_buttons", "Color buttons");
4415 let current_color = state.color.value();
4416
4417 widgets::label(
4418 ui,
4419 body,
4420 "color_buttons.edit_label",
4421 "Color edit button",
4422 text(12.0, color(166, 176, 190)),
4423 LayoutStyle::new().with_width_percent(1.0),
4424 );
4425 let edit_row = row(ui, body, "color_buttons.edit_row", 8.0);
4426 ext_widgets::color_edit_button(
4427 ui,
4428 edit_row,
4429 "color_buttons.compact",
4430 current_color,
4431 color_square_button_options("color_buttons.compact")
4432 .with_format(ext_widgets::ColorValueFormat::Rgb)
4433 .accessibility_label("Edit RGB color"),
4434 );
4435 widgets::label(
4436 ui,
4437 edit_row,
4438 "color_buttons.hex_value",
4439 ext_widgets::color_picker::format_hex_color(current_color, false),
4440 text(12.0, color(220, 228, 238)),
4441 LayoutStyle::new().with_width(92.0),
4442 );
4443
4444 widgets::label(
4445 ui,
4446 body,
4447 "color_buttons.format_label",
4448 "Value formats",
4449 text(12.0, color(166, 176, 190)),
4450 LayoutStyle::new().with_width_percent(1.0),
4451 );
4452 let rgb_row = row(ui, body, "color_buttons.rgb_row", 8.0);
4453 widgets::label(
4454 ui,
4455 rgb_row,
4456 "color_buttons.rgb_label",
4457 "RGB",
4458 text(12.0, color(186, 198, 216)),
4459 LayoutStyle::new().with_width(48.0),
4460 );
4461 ext_widgets::color_edit_button(
4462 ui,
4463 rgb_row,
4464 "color_buttons.rgb",
4465 current_color,
4466 color_value_button_options("color_buttons.rgb", 180.0)
4467 .with_format(ext_widgets::ColorValueFormat::Rgb),
4468 );
4469 let rgba_row = row(ui, body, "color_buttons.rgba_row", 8.0);
4470 widgets::label(
4471 ui,
4472 rgba_row,
4473 "color_buttons.rgba_label",
4474 "RGBA",
4475 text(12.0, color(186, 198, 216)),
4476 LayoutStyle::new().with_width(48.0),
4477 );
4478 ext_widgets::color_edit_button(
4479 ui,
4480 rgba_row,
4481 "color_buttons.rgba",
4482 current_color,
4483 color_value_button_options("color_buttons.rgba", 230.0)
4484 .with_format(ext_widgets::ColorValueFormat::Rgba),
4485 );
4486 let hsva_row = row(ui, body, "color_buttons.hsva_row", 8.0);
4487 widgets::label(
4488 ui,
4489 hsva_row,
4490 "color_buttons.hsva_label",
4491 "HSVA",
4492 text(12.0, color(186, 198, 216)),
4493 LayoutStyle::new().with_width(48.0),
4494 );
4495 ext_widgets::color_edit_button(
4496 ui,
4497 hsva_row,
4498 "color_buttons.hsva",
4499 current_color,
4500 color_value_button_options("color_buttons.hsva", 260.0)
4501 .with_format(ext_widgets::ColorValueFormat::Hsva),
4502 );
4503
4504 widgets::label(
4505 ui,
4506 body,
4507 "color_buttons.field_label",
4508 "2D color field",
4509 text(12.0, color(166, 176, 190)),
4510 LayoutStyle::new().with_width_percent(1.0),
4511 );
4512 ext_widgets::color_picker::color_picker_hsva_2d(
4513 ui,
4514 body,
4515 "color_buttons.hsva_2d",
4516 state.color.hsv(),
4517 ext_widgets::ColorHsva2dOptions::default()
4518 .with_layout(LayoutStyle::new().with_width(204.0).with_height(112.0))
4519 .with_action_prefix("color_buttons.hsva_2d"),
4520 );
4521 widgets::label(
4522 ui,
4523 body,
4524 "color_buttons.status",
4525 format!("Last activated: {}", state.color_button_status),
4526 text(11.0, color(154, 166, 184)),
4527 LayoutStyle::new().with_width_percent(1.0),
4528 );
4529}
4530
4531fn menu_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4532 let body = section(ui, parent, "menus", "Menus");
4533 let menus = menu_bar_menus(state.menu_autosave, state.menu_grid);
4534 let active_items = state
4535 .menu_bar
4536 .open_menu
4537 .and_then(|index| menus.get(index))
4538 .map(|menu| menu.items.clone())
4539 .unwrap_or_default();
4540 ext_widgets::menu_bar(
4541 ui,
4542 body,
4543 "menus.menu_bar",
4544 &menus,
4545 &state.menu_bar,
4546 None,
4547 ext_widgets::MenuBarOptions::default().with_action_prefix("menus.bar"),
4548 );
4549
4550 if !active_items.is_empty() {
4551 ext_widgets::menu_list(
4552 ui,
4553 body,
4554 "menus.menu_list",
4555 &active_items,
4556 state.menu_bar.active_item,
4557 ext_widgets::MenuListOptions::default().with_action_prefix("menus.item"),
4558 );
4559 if let Some(active_item) = state.menu_bar.active_item {
4560 if let Some(children) = active_items
4561 .get(active_item)
4562 .and_then(|item| item.children())
4563 {
4564 ext_widgets::menu_list_popup(
4565 ui,
4566 body,
4567 "menus.submenu",
4568 ext_widgets::AnchoredPopup::new(
4569 UiRect::new(
4570 0.0,
4571 40.0 + menu_item_top_offset(&active_items, active_item),
4572 240.0,
4573 menu_item_height(active_items.get(active_item)),
4574 ),
4575 UiRect::new(0.0, 0.0, 680.0, 468.0),
4576 ext_widgets::PopupPlacement::new(
4577 ext_widgets::PopupSide::Right,
4578 ext_widgets::PopupAlign::Start,
4579 )
4580 .with_offset(4.0),
4581 ),
4582 children,
4583 Some(0),
4584 ext_widgets::MenuListOptions::default().with_action_prefix("menus.item"),
4585 );
4586 }
4587 }
4588 }
4589 divider(ui, body, "menus.divider.buttons");
4590 let button_row = row(ui, body, "menus.buttons", 8.0);
4591 let button_items = menu_items(state.menu_autosave);
4592 ext_widgets::menu_button(
4593 ui,
4594 button_row,
4595 "menus.menu_button",
4596 "Menu button",
4597 &button_items,
4598 &state.menu_button,
4599 None,
4600 ext_widgets::MenuButtonOptions::default().with_action("menus.menu_button"),
4601 );
4602 ext_widgets::image_text_menu_button(
4603 ui,
4604 button_row,
4605 "menus.image_text_menu_button",
4606 "Image text",
4607 icon_image(BuiltInIcon::Folder),
4608 &button_items,
4609 &state.image_text_menu_button,
4610 None,
4611 ext_widgets::MenuButtonOptions::default().with_action("menus.image_text_menu_button"),
4612 );
4613 ext_widgets::image_menu_button(
4614 ui,
4615 button_row,
4616 "menus.image_menu_button",
4617 icon_image(BuiltInIcon::Settings),
4618 &button_items,
4619 &state.image_menu_button,
4620 None,
4621 ext_widgets::MenuButtonOptions::default().with_action("menus.image_menu_button"),
4622 );
4623 if state.menu_button.open || state.image_text_menu_button.open || state.image_menu_button.open {
4624 let active = state
4625 .menu_button
4626 .navigation
4627 .active_path
4628 .first()
4629 .copied()
4630 .or_else(|| {
4631 state
4632 .image_text_menu_button
4633 .navigation
4634 .active_path
4635 .first()
4636 .copied()
4637 })
4638 .or_else(|| {
4639 state
4640 .image_menu_button
4641 .navigation
4642 .active_path
4643 .first()
4644 .copied()
4645 });
4646 ext_widgets::menu_list(
4647 ui,
4648 body,
4649 "menus.button_menu",
4650 &button_items,
4651 active,
4652 ext_widgets::MenuListOptions::default().with_action_prefix("menus.item"),
4653 );
4654 }
4655
4656 let context_row = row(ui, body, "menus.context.controls", 8.0);
4657 button(
4658 ui,
4659 context_row,
4660 "menus.context.open",
4661 "Open context",
4662 "menus.context.open",
4663 button_visual(48, 112, 184),
4664 );
4665 button(
4666 ui,
4667 context_row,
4668 "menus.context.close",
4669 "Close",
4670 "menus.context.close",
4671 button_visual(58, 78, 96),
4672 );
4673 let mut context_options =
4674 ext_widgets::MenuListOptions::default().with_action_prefix("menus.context");
4675 context_options.width = 180.0;
4676 context_options.max_visible_rows = 4;
4677 let _ = ext_widgets::context_menu(
4678 ui,
4679 parent,
4680 "menus.context_menu",
4681 &button_items,
4682 &state.context_menu,
4683 UiRect::new(0.0, 0.0, 180.0, 120.0),
4684 ext_widgets::PopupPlacement::default(),
4685 context_options,
4686 );
4687}
4688
4689fn command_palette(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4690 let body = section(ui, parent, "command_palette", "Command palette");
4691 let items = command_palette_items_with_history(&state.command_history);
4692 let mut options =
4693 ext_widgets::CommandPaletteOptions::default().with_action_prefix("command_palette");
4694 options.width = 480.0;
4695 options.row_height = 44.0;
4696 options.max_visible_rows = 5;
4697 options.text_style = text(13.0, color(238, 244, 252));
4698 options.muted_text_style = text(11.0, color(166, 178, 196));
4699 ext_widgets::command_palette(
4700 ui,
4701 body,
4702 "command_palette.panel",
4703 &items,
4704 &state.command_palette,
4705 None,
4706 options,
4707 );
4708 widgets::label(
4709 ui,
4710 body,
4711 "command_palette.last",
4712 format!("Last command: {}", state.last_command),
4713 text(12.0, color(154, 166, 184)),
4714 LayoutStyle::new().with_width_percent(1.0),
4715 );
4716}
4717
4718#[allow(clippy::field_reassign_with_default)]
4719fn progress_indicator(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4720 let body = section(ui, parent, "progress", "Progress indicator");
4721 let animated = smooth_loop(state.progress_phase * 0.85, 0.0) * 100.0;
4722 let mut progress = ext_widgets::ProgressIndicatorOptions::default();
4723 progress.layout = LayoutStyle::new().with_width_percent(1.0).with_height(10.0);
4724 progress.accessibility_label = Some("Progress".to_string());
4725 ext_widgets::progress_indicator(
4726 ui,
4727 body,
4728 "progress.primary",
4729 ext_widgets::ProgressIndicatorValue::percent(animated),
4730 progress,
4731 );
4732 let compact_value = smooth_loop(state.progress_phase * 1.15, 0.7) * 100.0;
4733 let mut compact = ext_widgets::ProgressIndicatorOptions::default();
4734 compact.layout = LayoutStyle::new().with_width_percent(1.0).with_height(6.0);
4735 compact.fill_visual = UiVisual::panel(color(111, 203, 159), None, 3.0);
4736 ext_widgets::progress_indicator(
4737 ui,
4738 body,
4739 "progress.compact",
4740 ext_widgets::ProgressIndicatorValue::percent(compact_value),
4741 compact,
4742 );
4743 let warning_value = smooth_loop(state.progress_phase * 0.65, 1.4) * 100.0;
4744 let mut warning = ext_widgets::ProgressIndicatorOptions::default();
4745 warning.layout = LayoutStyle::new().with_width_percent(1.0).with_height(14.0);
4746 warning.fill_visual = UiVisual::panel(color(232, 186, 88), None, 4.0);
4747 ext_widgets::progress_indicator(
4748 ui,
4749 body,
4750 "progress.warning",
4751 ext_widgets::ProgressIndicatorValue::percent(warning_value),
4752 warning,
4753 );
4754 let spinner_row = row(ui, body, "progress.spinner.row", 8.0);
4755 widgets::spinner(
4756 ui,
4757 spinner_row,
4758 "progress.spinner",
4759 widgets::SpinnerOptions::default()
4760 .with_phase(state.progress_phase)
4761 .with_accessibility_label("Loading spinner"),
4762 );
4763 widgets::label(
4764 ui,
4765 spinner_row,
4766 "progress.spinner.label",
4767 "Spinner",
4768 text(12.0, color(196, 210, 230)),
4769 LayoutStyle::new().with_width_percent(1.0),
4770 );
4771}
4772
4773fn animation_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4774 let body = section(ui, parent, "animation", "Animation");
4775
4776 if let Some(section) = animation_section(
4777 ui,
4778 body,
4779 "animation.timed",
4780 "Timed playback",
4781 state.animation_timed_expanded,
4782 ) {
4783 let live_stage = animation_stage(ui, section, "animation.live.stage");
4784 let live_amount = smooth_loop(state.progress_phase * 1.65, 0.0);
4785 let live_values = animation_blend_machine(
4786 ANIMATION_INPUT_PROGRESS,
4787 live_amount,
4788 UiPoint::new(220.0, 0.0),
4789 0.88,
4790 1.10,
4791 1.0,
4792 )
4793 .with_bool_input("looping", true)
4794 .values();
4795 ui.add_child(
4796 live_stage,
4797 UiNode::scene(
4798 "animation.live.orb",
4799 animation_orb_primitives(
4800 color(108, 180, 255),
4801 ANIMATION_ORB_SIZE * live_values.scale,
4802 UiPoint::new(
4803 28.0 + live_values.translate.x,
4804 37.0 + live_values.translate.y,
4805 ),
4806 ),
4807 animation_scene_layout(),
4808 )
4809 .with_accessibility(
4810 AccessibilityMeta::new(AccessibilityRole::Image).label("Looping orb"),
4811 ),
4812 );
4813 }
4814
4815 if let Some(section) = animation_section(
4816 ui,
4817 body,
4818 "animation.scrub",
4819 "Scrubbed input",
4820 state.animation_scrub_expanded,
4821 ) {
4822 let scrub_row = row(ui, section, "animation.scrub.row", 10.0);
4823 widgets::slider(
4824 ui,
4825 scrub_row,
4826 "animation.scrub",
4827 state.animation_scrub,
4828 0.0..1.0,
4829 widgets::SliderOptions::default()
4830 .with_layout(
4831 LayoutStyle::new()
4832 .with_width(200.0)
4833 .with_height(28.0)
4834 .with_flex_shrink(0.0),
4835 )
4836 .with_value_edit_action("animation.scrub"),
4837 );
4838 widgets::label(
4839 ui,
4840 scrub_row,
4841 "animation.scrub.value",
4842 format!("{:.0}%", state.animation_scrub * 100.0),
4843 text(12.0, color(186, 198, 216)),
4844 LayoutStyle::new().with_width_percent(1.0),
4845 );
4846 let scrub_stage = animation_stage(ui, section, "animation.scrub.stage");
4847 let scrub_values = animation_blend_machine(
4848 ANIMATION_INPUT_SCRUB,
4849 state.animation_scrub,
4850 UiPoint::new(220.0, 0.0),
4851 0.82,
4852 1.14,
4853 1.0,
4854 )
4855 .values();
4856 ui.add_child(
4857 scrub_stage,
4858 UiNode::scene(
4859 "animation.scrub.shape",
4860 animation_morph_shape_primitives(
4861 color(111, 203, 159),
4862 ANIMATION_SHAPE_SIZE * scrub_values.scale,
4863 UiPoint::new(
4864 28.0 + scrub_values.translate.x,
4865 37.0 + scrub_values.translate.y,
4866 ),
4867 scrub_values.morph,
4868 ),
4869 animation_scene_layout(),
4870 )
4871 .with_accessibility(
4872 AccessibilityMeta::new(AccessibilityRole::Image).label("Scrubbed morphing shape"),
4873 ),
4874 );
4875 }
4876
4877 if let Some(section) = animation_section(
4878 ui,
4879 body,
4880 "animation.state",
4881 "Boolean input transition",
4882 state.animation_state_expanded,
4883 ) {
4884 let state_row = row(ui, section, "animation.state.row", 10.0);
4885 let mut open = widgets::ButtonOptions::new(
4886 LayoutStyle::new()
4887 .with_width(92.0)
4888 .with_height(30.0)
4889 .with_flex_shrink(0.0),
4890 )
4891 .with_action("animation.open");
4892 open.visual = if state.animation_open {
4893 button_visual(48, 112, 184)
4894 } else {
4895 button_visual(38, 46, 58)
4896 };
4897 open.hovered_visual = Some(button_visual(65, 86, 106));
4898 open.pressed_visual = Some(button_visual(34, 54, 84));
4899 open.text_style = text(12.0, color(238, 244, 252));
4900 widgets::button(
4901 ui,
4902 state_row,
4903 "animation.open",
4904 if state.animation_open {
4905 "Close"
4906 } else {
4907 "Open"
4908 },
4909 open,
4910 );
4911 let open_stage = animation_stage(ui, section, "animation.state.stage");
4912 let panel_offset = if state.animation_open {
4913 UiPoint::new(
4914 ANIMATION_STAGE_MIN_WIDTH - ANIMATION_PANEL_WIDTH - ANIMATION_PANEL_INSET_X,
4915 ANIMATION_PANEL_Y,
4916 )
4917 } else {
4918 UiPoint::new(ANIMATION_PANEL_INSET_X, ANIMATION_PANEL_Y)
4919 };
4920 ui.add_child(
4921 open_stage,
4922 UiNode::scene(
4923 "animation.state.panel",
4924 animation_panel_primitives(panel_offset),
4925 animation_scene_layout(),
4926 )
4927 .with_animation(animation_open_machine(state.animation_open))
4928 .with_accessibility(
4929 AccessibilityMeta::new(AccessibilityRole::Image).label("Open state panel"),
4930 ),
4931 );
4932 }
4933
4934 if let Some(section) = animation_section(
4935 ui,
4936 body,
4937 "animation.interaction",
4938 "Interaction inputs",
4939 state.animation_interaction_expanded,
4940 ) {
4941 let interaction_stage = animation_stage(ui, section, "animation.interaction.stage");
4942 ui.add_child(
4943 interaction_stage,
4944 UiNode::scene(
4945 "animation.interaction.target",
4946 animation_interaction_primitives(
4947 color(176, 126, 230),
4948 ANIMATION_ORB_SIZE,
4949 UiPoint::new(40.0, 37.0),
4950 ),
4951 animation_scene_layout(),
4952 )
4953 .with_input(InputBehavior::BUTTON)
4954 .with_animation(animation_interaction_machine())
4955 .with_accessibility(
4956 AccessibilityMeta::new(AccessibilityRole::Button)
4957 .label("Interaction animation target")
4958 .focusable(),
4959 ),
4960 );
4961 }
4962}
4963
4964fn animation_section(
4965 ui: &mut UiDocument,
4966 parent: UiNodeId,
4967 name: &'static str,
4968 title: &'static str,
4969 expanded: bool,
4970) -> Option<UiNodeId> {
4971 let mut options = widgets::CollapsingHeaderOptions::default()
4972 .expanded(expanded)
4973 .with_toggle_action(format!("{name}.toggle"));
4974 options.text_style = text(12.0, color(220, 228, 238));
4975 options.indicator_text_style = text(12.0, color(186, 198, 216));
4976 options.header_visual = UiVisual::panel(
4977 color(21, 26, 33),
4978 Some(StrokeStyle::new(color(48, 58, 72), 1.0)),
4979 4.0,
4980 );
4981 options.hovered_visual = UiVisual::panel(color(38, 48, 61), None, 4.0);
4982 options.pressed_visual = UiVisual::panel(color(27, 36, 48), None, 4.0);
4983 options.body_layout = LayoutStyle::column()
4984 .with_width_percent(1.0)
4985 .with_padding(0.0)
4986 .with_gap(10.0);
4987 widgets::collapsing_header(ui, parent, name, title, options).body
4988}
4989
4990fn animation_stage(ui: &mut UiDocument, parent: UiNodeId, name: impl Into<String>) -> UiNodeId {
4991 let layout = LayoutStyle::row()
4992 .with_width_percent(1.0)
4993 .with_height(ANIMATION_STAGE_HEIGHT)
4994 .with_align_items(taffy::prelude::AlignItems::Center)
4995 .with_flex_shrink(0.0);
4996 let layout = operad::layout::with_min_size(
4997 layout,
4998 operad::length(ANIMATION_STAGE_MIN_WIDTH),
4999 operad::length(ANIMATION_STAGE_HEIGHT),
5000 );
5001 ui.add_child(
5002 parent,
5003 UiNode::container(name, layout).with_visual(UiVisual::panel(
5004 color(16, 21, 28),
5005 Some(StrokeStyle::new(color(48, 58, 72), 1.0)),
5006 6.0,
5007 )),
5008 )
5009}
5010
5011fn animation_scene_layout() -> LayoutStyle {
5012 let layout = LayoutStyle::new()
5013 .with_width_percent(1.0)
5014 .with_height_percent(1.0)
5015 .with_flex_grow(1.0)
5016 .with_flex_shrink(1.0);
5017 operad::layout::with_min_size(layout, operad::length(0.0), operad::length(0.0))
5018}
5019
5020fn animation_blend_machine(
5021 input: &'static str,
5022 value: f32,
5023 translate: UiPoint,
5024 start_scale: f32,
5025 end_scale: f32,
5026 end_opacity: f32,
5027) -> AnimationMachine {
5028 let start_values = AnimatedValues::new(0.45, UiPoint::new(0.0, 0.0), start_scale);
5029 let end_values = AnimatedValues::new(end_opacity, translate, end_scale).with_morph(1.0);
5030 AnimationMachine::new(
5031 vec![
5032 AnimationState::new("start", start_values),
5033 AnimationState::new("end", end_values),
5034 ],
5035 Vec::new(),
5036 "start",
5037 )
5038 .unwrap_or_else(|_| AnimationMachine::single_state("start", start_values))
5039 .with_number_input(input, value)
5040 .with_blend_binding(AnimationBlendBinding::new(input, "start", "end"))
5041}
5042
5043fn animation_open_machine(open: bool) -> AnimationMachine {
5044 let closed_values = AnimatedValues::new(0.35, UiPoint::new(0.0, 0.0), 1.0);
5045 let open_values = AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0);
5046 let fallback_values = if open { open_values } else { closed_values };
5047 AnimationMachine::new(
5048 vec![
5049 AnimationState::new("closed", closed_values),
5050 AnimationState::new("open", open_values),
5051 ],
5052 vec![
5053 AnimationTransition::when(
5054 "closed",
5055 "open",
5056 AnimationCondition::bool(ANIMATION_INPUT_OPEN, true),
5057 0.18,
5058 ),
5059 AnimationTransition::when(
5060 "open",
5061 "closed",
5062 AnimationCondition::bool(ANIMATION_INPUT_OPEN, false),
5063 0.14,
5064 ),
5065 ],
5066 "closed",
5067 )
5068 .unwrap_or_else(|_| AnimationMachine::single_state("closed", fallback_values))
5069 .with_bool_input(ANIMATION_INPUT_OPEN, open)
5070}
5071
5072fn animation_interaction_machine() -> AnimationMachine {
5073 let rest_values = AnimatedValues::new(0.72, UiPoint::new(0.0, 0.0), 1.0);
5074 let right_values = AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0).with_morph(1.0);
5075 AnimationMachine::new(
5076 vec![
5077 AnimationState::new("rest", rest_values),
5078 AnimationState::new("right", right_values),
5079 ],
5080 Vec::new(),
5081 "rest",
5082 )
5083 .unwrap_or_else(|_| AnimationMachine::single_state("rest", rest_values))
5084 .with_number_input(ANIMATION_INPUT_POINTER_NORM_X, 0.0)
5085 .with_blend_binding(AnimationBlendBinding::new(
5086 ANIMATION_INPUT_POINTER_NORM_X,
5087 "rest",
5088 "right",
5089 ))
5090}
5091
5092fn animation_interaction_primitives(
5093 fill: ColorRgba,
5094 size: f32,
5095 offset: UiPoint,
5096) -> Vec<ScenePrimitive> {
5097 vec![
5098 ScenePrimitive::MorphPolygon {
5099 from_points: animation_square_points(size, offset),
5100 to_points: animation_pentagon_points(size, offset),
5101 amount: 0.0,
5102 fill,
5103 stroke: Some(StrokeStyle::new(color(236, 244, 255), 1.0)),
5104 },
5105 ScenePrimitive::Circle {
5106 center: UiPoint::new(offset.x + size * 0.34, offset.y + size * 0.30),
5107 radius: size * 0.10,
5108 fill: color(244, 248, 255),
5109 stroke: None,
5110 },
5111 ]
5112}
5113
5114fn animation_orb_primitives(fill: ColorRgba, size: f32, offset: UiPoint) -> Vec<ScenePrimitive> {
5115 let center = size * 0.5;
5116 let radius = size * 0.44;
5117 vec![
5118 ScenePrimitive::Circle {
5119 center: UiPoint::new(offset.x + center, offset.y + center),
5120 radius,
5121 fill,
5122 stroke: Some(StrokeStyle::new(color(236, 244, 255), 1.0)),
5123 },
5124 ScenePrimitive::Circle {
5125 center: UiPoint::new(offset.x + size * 0.34, offset.y + size * 0.30),
5126 radius: size * 0.12,
5127 fill: color(244, 248, 255),
5128 stroke: None,
5129 },
5130 ]
5131}
5132
5133fn animation_morph_shape_primitives(
5134 fill: ColorRgba,
5135 size: f32,
5136 offset: UiPoint,
5137 amount: f32,
5138) -> Vec<ScenePrimitive> {
5139 vec![ScenePrimitive::MorphPolygon {
5140 from_points: animation_square_points(size, offset),
5141 to_points: animation_pentagon_points(size, offset),
5142 amount,
5143 fill,
5144 stroke: Some(StrokeStyle::new(color(226, 246, 236), 1.0)),
5145 }]
5146}
5147
5148fn animation_square_points(size: f32, offset: UiPoint) -> Vec<UiPoint> {
5149 let inset = size * 0.08;
5150 let left = offset.x + inset;
5151 let top = offset.y + inset;
5152 let right = offset.x + size - inset;
5153 let bottom = offset.y + size - inset;
5154 let center_x = offset.x + size * 0.5;
5155 vec![
5156 UiPoint::new(center_x, top),
5157 UiPoint::new(right, top),
5158 UiPoint::new(right, bottom),
5159 UiPoint::new(left, bottom),
5160 UiPoint::new(left, top),
5161 ]
5162}
5163
5164fn animation_pentagon_points(size: f32, offset: UiPoint) -> Vec<UiPoint> {
5165 let center = size * 0.5;
5166 let radius = size * 0.46;
5167 (0..5)
5168 .map(|index| {
5169 let angle = -std::f32::consts::FRAC_PI_2 + index as f32 * std::f32::consts::TAU / 5.0;
5170 UiPoint::new(
5171 offset.x + center + angle.cos() * radius,
5172 offset.y + center + angle.sin() * radius,
5173 )
5174 })
5175 .collect()
5176}
5177
5178fn animation_panel_primitives(offset: UiPoint) -> Vec<ScenePrimitive> {
5179 vec![ScenePrimitive::Rect(
5180 PaintRect::solid(
5181 UiRect::new(
5182 offset.x,
5183 offset.y,
5184 ANIMATION_PANEL_WIDTH,
5185 ANIMATION_PANEL_HEIGHT,
5186 ),
5187 color(232, 186, 88),
5188 )
5189 .stroke(AlignedStroke::inside(StrokeStyle::new(
5190 color(255, 226, 154),
5191 1.0,
5192 )))
5193 .corner_radii(CornerRadii::uniform(6.0)),
5194 )]
5195}
5196
5197fn list_and_table_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5198 let body = section(ui, parent, "lists_tables", "Lists and tables");
5199
5200 let scroll_shell = row(ui, body, "lists_tables.scroll_area.shell", 8.0);
5201 let nested_scroll = widgets::scroll_area(
5202 ui,
5203 scroll_shell,
5204 "lists_tables.scroll_area",
5205 ScrollAxes::VERTICAL,
5206 LayoutStyle::column()
5207 .with_width(0.0)
5208 .with_flex_grow(1.0)
5209 .with_height(92.0),
5210 );
5211 ui.node_mut(nested_scroll)
5212 .set_action("lists_tables.scroll_area.scroll");
5213 if let Some(scroll) = ui.node_mut(nested_scroll).scroll_mut() {
5214 scroll.set_offset(UiPoint::new(0.0, state.list_scroll));
5215 }
5216 for index in 0..6 {
5217 widgets::label(
5218 ui,
5219 nested_scroll,
5220 format!("lists_tables.scroll_area.row.{index}"),
5221 format!("Scroll row {}", index + 1),
5222 text(12.0, color(200, 212, 228)),
5223 LayoutStyle::new()
5224 .with_width_percent(1.0)
5225 .with_height(26.0)
5226 .with_flex_shrink(0.0),
5227 );
5228 }
5229 scrollbar_widgets::scrollbar(
5230 ui,
5231 scroll_shell,
5232 "lists_tables.scroll_area.scrollbar",
5233 scroll_state(state.list_scroll, 92.0, 6.0 * 26.0),
5234 scrollbar_widgets::ScrollAxis::Vertical,
5235 scrollbar_widgets::ScrollbarOptions::default()
5236 .with_layout(LayoutStyle::size(8.0, 92.0))
5237 .with_track_size(UiSize::new(8.0, 92.0))
5238 .with_action("lists_tables.scroll_area.scrollbar"),
5239 );
5240
5241 widgets::table_header(ui, body, "lists_tables.table_header", &table_columns());
5242
5243 let virtual_shell = row(ui, body, "lists_tables.virtual_list.shell", 8.0);
5244 let virtual_list = widgets::virtual_list(
5245 ui,
5246 virtual_shell,
5247 "lists_tables.virtual_list",
5248 widgets::VirtualListSpec {
5249 row_count: 24,
5250 row_height: 28.0,
5251 viewport_height: 112.0,
5252 scroll_offset: state.virtual_scroll,
5253 overscan: 1,
5254 },
5255 |ui, row_parent, row| {
5256 widgets::label(
5257 ui,
5258 row_parent,
5259 format!("lists_tables.virtual_list.row.{row}"),
5260 format!("Virtual row {}", row + 1),
5261 text(12.0, color(214, 224, 238)),
5262 LayoutStyle::new()
5263 .with_width_percent(1.0)
5264 .with_height(28.0)
5265 .with_flex_shrink(0.0),
5266 );
5267 },
5268 );
5269 ui.node_mut(virtual_list)
5270 .set_action("lists_tables.virtual_list.scroll");
5271 scrollbar_widgets::scrollbar(
5272 ui,
5273 virtual_shell,
5274 "lists_tables.virtual_list.scrollbar",
5275 scroll_state(state.virtual_scroll, 112.0, 24.0 * 28.0),
5276 scrollbar_widgets::ScrollAxis::Vertical,
5277 scrollbar_widgets::ScrollbarOptions::default()
5278 .with_layout(LayoutStyle::size(8.0, 112.0))
5279 .with_track_size(UiSize::new(8.0, 112.0))
5280 .with_action("lists_tables.virtual_list.scrollbar"),
5281 );
5282
5283 let table_shell = row(ui, body, "lists_tables.data_table.shell", 8.0);
5284 let table_scroll = widgets::scroll_area(
5285 ui,
5286 table_shell,
5287 "lists_tables.data_table",
5288 ScrollAxes::VERTICAL,
5289 LayoutStyle::column()
5290 .with_width(0.0)
5291 .with_flex_grow(1.0)
5292 .with_height(128.0),
5293 );
5294 ui.node_mut(table_scroll)
5295 .set_action("lists_tables.data_table.scroll");
5296 if let Some(scroll) = ui.node_mut(table_scroll).scroll_mut() {
5297 scroll.set_offset(UiPoint::new(0.0, state.table_scroll));
5298 }
5299 for row_index in 0..16 {
5300 data_table_row(ui, table_scroll, row_index, state);
5301 }
5302 scrollbar_widgets::scrollbar(
5303 ui,
5304 table_shell,
5305 "lists_tables.data_table.scrollbar",
5306 scroll_state(state.table_scroll, 128.0, 16.0 * 28.0),
5307 scrollbar_widgets::ScrollAxis::Vertical,
5308 scrollbar_widgets::ScrollbarOptions::default()
5309 .with_layout(LayoutStyle::size(8.0, 128.0))
5310 .with_track_size(UiSize::new(8.0, 128.0))
5311 .with_action("lists_tables.data_table.scrollbar"),
5312 );
5313
5314 let virtual_controls = wrapping_row(ui, body, "lists_tables.virtualized_table.controls", 8.0);
5315 button(
5316 ui,
5317 virtual_controls,
5318 "lists_tables.virtualized_table.sort.name",
5319 if state.virtual_table_descending {
5320 "Name desc"
5321 } else {
5322 "Name asc"
5323 },
5324 "lists_tables.virtualized_table.sort.name",
5325 button_visual(38, 52, 70),
5326 );
5327 button(
5328 ui,
5329 virtual_controls,
5330 "lists_tables.virtualized_table.filter.status",
5331 if state.virtual_table_ready_only {
5332 "Ready only"
5333 } else {
5334 "All status"
5335 },
5336 "lists_tables.virtualized_table.filter.status",
5337 button_visual(38, 52, 70),
5338 );
5339 button(
5340 ui,
5341 virtual_controls,
5342 "lists_tables.virtualized_table.resize.reset",
5343 "Reset width",
5344 "lists_tables.virtualized_table.resize.reset",
5345 button_visual(38, 52, 70),
5346 );
5347
5348 let columns = virtual_table_columns(state);
5349 let visible_rows = virtual_table_visible_rows(state);
5350 let mut table_options = ext_widgets::DataTableOptions::default()
5351 .with_row_action_prefix("lists_tables.virtualized_table")
5352 .with_cell_action_prefix("lists_tables.virtualized_table")
5353 .with_scroll_action("lists_tables.virtualized_table.scroll");
5354 table_options.layout = LayoutStyle::column()
5355 .with_width(0.0)
5356 .with_flex_grow(1.0)
5357 .with_flex_shrink(1.0);
5358 table_options.selection = state.table_selection.clone();
5359 let virtual_shell = row(ui, body, "lists_tables.virtualized_table.shell", 8.0);
5360 ext_widgets::virtualized_data_table(
5361 ui,
5362 virtual_shell,
5363 "lists_tables.virtualized_table",
5364 &columns,
5365 ext_widgets::VirtualDataTableSpec {
5366 row_count: visible_rows.len(),
5367 row_height: 28.0,
5368 viewport_width: 420.0,
5369 viewport_height: 128.0,
5370 scroll_offset: UiPoint::new(0.0, state.virtual_table_scroll),
5371 overscan_rows: 1,
5372 },
5373 table_options,
5374 |ui, cell_parent, cell| {
5375 let source_row = visible_rows.get(cell.row).copied().unwrap_or(cell.row);
5376 let value = virtual_table_cell_value(source_row, cell.column);
5377 widgets::label(
5378 ui,
5379 cell_parent,
5380 format!(
5381 "lists_tables.virtualized_table.cell.{}.{}.label",
5382 cell.row, cell.column
5383 ),
5384 value,
5385 text(12.0, color(220, 228, 238)),
5386 LayoutStyle::new().with_width_percent(1.0),
5387 );
5388 },
5389 );
5390 scrollbar_widgets::scrollbar(
5391 ui,
5392 virtual_shell,
5393 "lists_tables.virtualized_table.scrollbar",
5394 scroll_state(
5395 state.virtual_table_scroll,
5396 128.0,
5397 visible_rows.len() as f32 * 28.0,
5398 ),
5399 scrollbar_widgets::ScrollAxis::Vertical,
5400 scrollbar_widgets::ScrollbarOptions::default()
5401 .with_layout(LayoutStyle::size(8.0, 158.0))
5402 .with_track_size(UiSize::new(8.0, 158.0))
5403 .with_action("lists_tables.virtualized_table.scrollbar"),
5404 );
5405}
5406
5407fn data_table_row(ui: &mut UiDocument, parent: UiNodeId, row_index: usize, state: &ShowcaseState) {
5408 let selected = state.table_selection.contains_row(row_index);
5409 let row = ui.add_child(
5410 parent,
5411 UiNode::container(
5412 format!("lists_tables.data_table.row.{row_index}"),
5413 LayoutStyle::row()
5414 .with_width_percent(1.0)
5415 .with_height(28.0)
5416 .with_flex_shrink(0.0),
5417 )
5418 .with_input(operad::InputBehavior::BUTTON)
5419 .with_action(format!("lists_tables.data_table.row.{row_index}"))
5420 .with_visual(if selected {
5421 UiVisual::panel(color(45, 73, 109), None, 0.0)
5422 } else {
5423 UiVisual::TRANSPARENT
5424 }),
5425 );
5426 let values = [
5427 format!("Item {}", row_index + 1),
5428 if row_index % 2 == 0 {
5429 "Ready".to_string()
5430 } else {
5431 "Pending".to_string()
5432 },
5433 format!("{}%", 40 + row_index * 3),
5434 ];
5435 let widths = [0.42, 0.33, 0.25];
5436 for (column, value) in values.into_iter().enumerate() {
5437 let cell = ui.add_child(
5438 row,
5439 UiNode::container(
5440 format!("lists_tables.data_table.cell.{row_index}.{column}"),
5441 LayoutStyle::new()
5442 .with_width_percent(widths[column])
5443 .with_height_percent(1.0)
5444 .padding(6.0),
5445 )
5446 .with_input(operad::InputBehavior::BUTTON)
5447 .with_action(format!("lists_tables.data_table.cell.{row_index}.{column}")),
5448 );
5449 widgets::label(
5450 ui,
5451 cell,
5452 format!("lists_tables.data_table.cell.{row_index}.{column}.label"),
5453 value,
5454 text(12.0, color(222, 230, 240)),
5455 LayoutStyle::new().with_width_percent(1.0),
5456 );
5457 }
5458}
5459
5460#[allow(clippy::field_reassign_with_default)]
5461fn property_inspector(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5462 let body = section(ui, parent, "property_inspector", "Property inspector");
5463 widgets::label(
5464 ui,
5465 body,
5466 "property_inspector.target",
5467 "Inspecting: Styling preview",
5468 text(12.0, color(196, 210, 230)),
5469 LayoutStyle::new().with_width_percent(1.0),
5470 );
5471 let mut options = ext_widgets::PropertyInspectorOptions::default();
5472 options.selected_index = Some(0);
5473 options.label_width = 120.0;
5474 options.row_height = 30.0;
5475 ext_widgets::property_inspector_grid(
5476 ui,
5477 body,
5478 "property_inspector.grid",
5479 &[
5480 ext_widgets::PropertyGridRow::new("target", "Widget", "Button preview").read_only(),
5481 ext_widgets::PropertyGridRow::new(
5482 "inner",
5483 "Inner margin",
5484 format!("{:.0}px", state.styling.inner_margin),
5485 )
5486 .with_kind(ext_widgets::PropertyValueKind::Number),
5487 ext_widgets::PropertyGridRow::new(
5488 "outer",
5489 "Outer margin",
5490 format!("{:.0}px", state.styling.outer_margin),
5491 )
5492 .with_kind(ext_widgets::PropertyValueKind::Number),
5493 ext_widgets::PropertyGridRow::new(
5494 "radius",
5495 "Corner radius",
5496 format!("{:.0}px", state.styling.corner_radius),
5497 )
5498 .with_kind(ext_widgets::PropertyValueKind::Number),
5499 ext_widgets::PropertyGridRow::new(
5500 "stroke",
5501 "Stroke",
5502 format!("{:.1}px", state.styling.stroke_width),
5503 )
5504 .with_kind(ext_widgets::PropertyValueKind::Number)
5505 .changed(),
5506 ext_widgets::PropertyGridRow::new("state", "Source", "Styling widget").read_only(),
5507 ],
5508 options,
5509 );
5510}
5511
5512fn diagnostics_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5513 let body = section(ui, parent, "diagnostics", "Diagnostics");
5514
5515 widgets::label(
5516 ui,
5517 body,
5518 "diagnostics.layout.title",
5519 "Layout and animation inspector",
5520 text(14.0, color(222, 230, 240)),
5521 LayoutStyle::new().with_width_percent(1.0),
5522 );
5523 let debug_snapshot = &state.diagnostics_snapshot;
5524 ext_widgets::debug_inspector_panel(
5525 ui,
5526 body,
5527 "diagnostics.inspector",
5528 debug_snapshot,
5529 ext_widgets::DebugInspectorPanelOptions {
5530 selected_node: Some("diagnostics.sample.preview".to_owned()),
5531 label_width: 104.0,
5532 max_layout_rows: 5,
5533 max_animation_rows: 1,
5534 show_animation: false,
5535 ..Default::default()
5536 },
5537 );
5538 ext_widgets::animation_state_graph_panel(
5539 ui,
5540 body,
5541 "diagnostics.animation.graph",
5542 debug_snapshot.animation("diagnostics.sample.preview"),
5543 ext_widgets::AnimationStateGraphPanelOptions {
5544 state_width: 72.0,
5545 state_height: 28.0,
5546 edge_row_height: 22.0,
5547 max_edges: 2,
5548 action_prefix: Some("diagnostics.animation.graph".to_owned()),
5549 ..Default::default()
5550 },
5551 );
5552 ext_widgets::animation_inspector_controls_panel(
5553 ui,
5554 body,
5555 "diagnostics.animation.controls",
5556 debug_snapshot.animation("diagnostics.sample.preview"),
5557 ext_widgets::AnimationInspectorControlsOptions {
5558 max_inputs: 3,
5559 paused: state.diagnostics_animation_paused,
5560 scrub_progress: Some(state.diagnostics_animation_scrub),
5561 action_prefix: Some("diagnostics.animation.controls".to_owned()),
5562 ..Default::default()
5563 },
5564 );
5565 widgets::label(
5566 ui,
5567 body,
5568 "diagnostics.animation.controls.status",
5569 format!(
5570 "scrub {:.0}% hover {:.0}% pulses {}",
5571 state.diagnostics_animation_scrub * 100.0,
5572 state.diagnostics_animation_hover * 100.0,
5573 state.diagnostics_animation_pulse_count
5574 ),
5575 text(12.0, color(166, 180, 198)),
5576 LayoutStyle::new().with_width_percent(1.0),
5577 );
5578
5579 widgets::label(
5580 ui,
5581 body,
5582 "diagnostics.a11y.title",
5583 "Accessibility overlay",
5584 text(14.0, color(222, 230, 240)),
5585 LayoutStyle::new().with_width_percent(1.0),
5586 );
5587 let mut overlay_preview_style = UiNodeStyle::from(
5588 LayoutStyle::new()
5589 .with_width(320.0)
5590 .with_height(140.0)
5591 .with_flex_shrink(0.0),
5592 );
5593 overlay_preview_style.set_clip(ClipBehavior::Clip);
5594 let overlay_preview = ui.add_child(
5595 body,
5596 UiNode::container("diagnostics.a11y.preview", overlay_preview_style).with_visual(
5597 UiVisual::panel(
5598 color(12, 17, 24),
5599 Some(StrokeStyle::new(color(47, 62, 82), 1.0)),
5600 4.0,
5601 ),
5602 ),
5603 );
5604 let mut overlay_options = ext_widgets::AccessibilityDebugOverlayOptions {
5605 action_prefix: Some("diagnostics.a11y.visual".to_owned()),
5606 ..Default::default()
5607 };
5608 overlay_options.show_labels = false;
5609 ext_widgets::accessibility_debug_overlay(
5610 ui,
5611 overlay_preview,
5612 "diagnostics.a11y.visual",
5613 &debug_snapshot,
5614 overlay_options,
5615 );
5616 ext_widgets::accessibility_overlay_panel(
5617 ui,
5618 body,
5619 "diagnostics.a11y",
5620 &debug_snapshot,
5621 ext_widgets::AccessibilityOverlayPanelOptions {
5622 label_width: 118.0,
5623 max_rows: 1,
5624 action_prefix: Some("diagnostics.a11y".to_owned()),
5625 ..Default::default()
5626 },
5627 );
5628
5629 let diagnostic_columns = ui.add_child(
5630 body,
5631 UiNode::container(
5632 "diagnostics.columns",
5633 LayoutStyle::column()
5634 .with_width_percent(1.0)
5635 .with_flex_shrink(0.0)
5636 .gap(10.0),
5637 ),
5638 );
5639 let command_column = ui.add_child(
5640 diagnostic_columns,
5641 UiNode::container(
5642 "diagnostics.commands.column",
5643 LayoutStyle::column()
5644 .with_width_percent(1.0)
5645 .with_flex_shrink(0.0)
5646 .gap(8.0),
5647 ),
5648 );
5649 let theme_column = ui.add_child(
5650 diagnostic_columns,
5651 UiNode::container(
5652 "diagnostics.theme.column",
5653 LayoutStyle::column()
5654 .with_width_percent(1.0)
5655 .with_flex_shrink(0.0)
5656 .gap(8.0),
5657 ),
5658 );
5659
5660 widgets::label(
5661 ui,
5662 command_column,
5663 "diagnostics.commands.title",
5664 "Command registry",
5665 text(14.0, color(222, 230, 240)),
5666 LayoutStyle::new().with_width_percent(1.0),
5667 );
5668 let registry = diagnostics_command_registry();
5669 ext_widgets::command_diagnostics_panel(
5670 ui,
5671 command_column,
5672 "diagnostics.commands",
5673 ®istry,
5674 &[CommandScope::Global, CommandScope::Panel],
5675 &ShortcutFormatter::default(),
5676 ext_widgets::CommandDiagnosticsPanelOptions {
5677 label_width: 92.0,
5678 max_command_rows: 3,
5679 max_conflict_rows: 1,
5680 action_prefix: Some("diagnostics.commands".to_owned()),
5681 ..Default::default()
5682 },
5683 );
5684
5685 widgets::label(
5686 ui,
5687 theme_column,
5688 "diagnostics.theme.title",
5689 "Theme editor",
5690 text(14.0, color(222, 230, 240)),
5691 LayoutStyle::new().with_width_percent(1.0),
5692 );
5693 let theme_snapshot = DebugThemeSnapshot::from_theme(&Theme::dark());
5694 ext_widgets::theme_editor_panel(
5695 ui,
5696 theme_column,
5697 "diagnostics.theme",
5698 &theme_snapshot,
5699 ext_widgets::ThemeEditorPanelOptions {
5700 label_width: 92.0,
5701 max_token_rows: 1,
5702 max_component_rows: 1,
5703 action_prefix: Some("diagnostics.theme".to_owned()),
5704 ..Default::default()
5705 },
5706 );
5707}pub fn set_opacity(&mut self, opacity: f32)
Sourcepub fn set_z_index(&mut self, z_index: i16)
pub fn set_z_index(&mut self, z_index: i16)
Examples found in repository?
examples/showcase.rs (line 2382)
2347fn organize_windows_button(ui: &mut UiDocument, desktop: UiNodeId) {
2348 let mut options =
2349 widgets::ButtonOptions::new(operad::layout::absolute(12.0, 12.0, 104.0, 28.0))
2350 .with_action("window.organize_open")
2351 .with_accessibility_label("Organize open windows");
2352 options.visual = UiVisual::panel(
2353 ColorRgba::new(20, 26, 34, 230),
2354 Some(StrokeStyle::new(color(76, 88, 106), 1.0)),
2355 4.0,
2356 );
2357 options.hovered_visual = Some(UiVisual::panel(
2358 color(45, 56, 70),
2359 Some(StrokeStyle::new(color(118, 144, 174), 1.0)),
2360 4.0,
2361 ));
2362 options.pressed_visual = Some(UiVisual::panel(
2363 color(18, 24, 32),
2364 Some(StrokeStyle::new(color(82, 104, 132), 1.0)),
2365 4.0,
2366 ));
2367 options.pressed_hovered_visual = Some(UiVisual::panel(
2368 color(36, 48, 62),
2369 Some(StrokeStyle::new(color(138, 170, 206), 1.0)),
2370 4.0,
2371 ));
2372 options.text_style = text(12.0, color(230, 236, 246));
2373 let button = widgets::button(
2374 ui,
2375 desktop,
2376 "showcase.organize_windows",
2377 "Organize",
2378 options,
2379 );
2380 ui.node_mut(button)
2381 .style_mut()
2382 .set_z_index(SHOWCASE_WINDOW_Z_MAX.saturating_add(20));
2383}
2384
2385fn fps_counter(
2386 ui: &mut UiDocument,
2387 desktop: UiNodeId,
2388 state: &ShowcaseState,
2389 viewport_height: f32,
2390) {
2391 let mut counter_style = UiNodeStyle::from(operad::layout::absolute(
2392 12.0,
2393 (viewport_height - 34.0).max(12.0),
2394 92.0,
2395 24.0,
2396 ));
2397 counter_style.set_z_index(SHOWCASE_WINDOW_Z_MAX.saturating_add(16));
2398 let counter = ui.add_child(
2399 desktop,
2400 UiNode::container("showcase.fps", counter_style)
2401 .with_visual(UiVisual::panel(
2402 ColorRgba::new(11, 15, 21, 210),
2403 Some(StrokeStyle::new(color(56, 68, 84), 1.0)),
2404 4.0,
2405 ))
2406 .with_accessibility(
2407 AccessibilityMeta::new(AccessibilityRole::Label).label("FPS counter"),
2408 ),
2409 );
2410 let fps = if state.fps > 0.0 {
2411 format!("{:.0} FPS", state.fps)
2412 } else {
2413 "-- FPS".to_string()
2414 };
2415 widgets::label(
2416 ui,
2417 counter,
2418 "showcase.fps.label",
2419 fps,
2420 text(11.0, color(198, 211, 230)),
2421 LayoutStyle::new()
2422 .with_width_percent(1.0)
2423 .with_height_percent(1.0)
2424 .padding(5.0),
2425 );
2426}
2427
2428fn showcase_windows(
2429 ui: &mut UiDocument,
2430 desktop: UiNodeId,
2431 state: &ShowcaseState,
2432 desktop_size: UiSize,
2433) {
2434 let windows = showcase_window_descriptors(state, desktop_size);
2435 let options = showcase_desktop_options(desktop_size);
2436 ext_widgets::floating_desktop(
2437 ui,
2438 desktop,
2439 "showcase.windows",
2440 &windows,
2441 options,
2442 |ui, window, descriptor| match descriptor.id.as_str() {
2443 "labels" => labels(ui, window, state),
2444 "buttons" => buttons(ui, window, state),
2445 "checkbox" => checkbox(ui, window, state),
2446 "toggles" => toggles(ui, window, state),
2447 "slider" => slider(ui, window, state),
2448 "numeric" => numeric_inputs(ui, window, state),
2449 "text_input" => text_input(ui, window, state),
2450 "selection" => selection_widgets(ui, window, state),
2451 "menus" => menu_widgets(ui, window, state),
2452 "command_palette" => command_palette(ui, window, state),
2453 "date_picker" => date_picker(ui, window, state),
2454 "color_picker" => color_picker(ui, window, state),
2455 "color_buttons" => color_buttons(ui, window, state),
2456 "progress" => progress_indicator(ui, window, state),
2457 "animation" => animation_widgets(ui, window, state),
2458 "lists_tables" => list_and_table_widgets(ui, window, state),
2459 "property_inspector" => property_inspector(ui, window, state),
2460 "diagnostics" => diagnostics_widgets(ui, window, state),
2461 "trees" => tree_widgets(ui, window, state),
2462 "layout_widgets" => tab_split_dock_widgets(ui, window, state),
2463 "containers" => container_widgets(ui, window, state),
2464 "forms" => form_widgets(ui, window, state),
2465 "overlays" => overlay_widgets(ui, window, state),
2466 "drag_drop" => drag_drop_widgets(ui, window, state),
2467 "media" => media_widgets(ui, window),
2468 "timeline" => timeline_ruler(ui, window),
2469 "toasts" => toast_controls(ui, window, state),
2470 "popup_panel" => popup_controls(ui, window, state),
2471 "canvas" => canvas(ui, window, state),
2472 "styling" => styling_widgets(ui, window, state),
2473 _ => {}
2474 },
2475 );
2476 showcase_overlays(ui, desktop, state, desktop_size);
2477}
2478
2479#[allow(clippy::field_reassign_with_default)]
2480fn showcase_overlays(
2481 ui: &mut UiDocument,
2482 desktop: UiNodeId,
2483 state: &ShowcaseState,
2484 desktop_size: UiSize,
2485) {
2486 if state.toast_visible {
2487 let overlay_width = 320.0;
2488 let mut overlay_style = UiNodeStyle::from(operad::layout::absolute(
2489 (desktop_size.width - overlay_width - 18.0).max(18.0),
2490 18.0,
2491 overlay_width,
2492 180.0,
2493 ));
2494 overlay_style.set_clip(ClipBehavior::None);
2495 overlay_style.set_z_index(6000);
2496 let overlay = ui.add_child(
2497 desktop,
2498 UiNode::container("showcase.toast_overlay", overlay_style),
2499 );
2500 let mut stack = ext_widgets::ToastStack::new(3);
2501 stack.push_toast(
2502 ext_widgets::Toast::new(
2503 ext_widgets::ToastId::new(1),
2504 ext_widgets::ToastSeverity::Success,
2505 "Saved",
2506 Some("All changes are written".to_string()),
2507 None,
2508 )
2509 .with_action(ext_widgets::ToastAction::new("undo", "Undo")),
2510 );
2511 stack.push(
2512 ext_widgets::ToastSeverity::Warning,
2513 "Autosave paused",
2514 Some("Changes are kept locally".to_string()),
2515 None,
2516 );
2517 let mut options = ext_widgets::ToastStackOptions::default();
2518 options.z_index = 6100;
2519 ext_widgets::toast_stack(ui, overlay, "showcase.toast_overlay.stack", &stack, options);
2520 }
2521
2522 if state.popup_open {
2523 let popup_width = 280.0;
2524 let popup_height = 110.0;
2525 let popup = ext_widgets::popup_panel(
2526 ui,
2527 desktop,
2528 "showcase.popup_overlay",
2529 UiRect::new(
2530 (desktop_size.width - popup_width - 36.0).max(18.0),
2531 220.0_f32.min((desktop_size.height - popup_height - 18.0).max(18.0)),
2532 popup_width,
2533 popup_height,
2534 ),
2535 ext_widgets::PopupOptions {
2536 z_index: 6100,
2537 accessibility: Some(
2538 AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup panel"),
2539 ),
2540 ..Default::default()
2541 },
2542 );
2543 let body = ui.add_child(
2544 popup,
2545 UiNode::container(
2546 "showcase.popup_overlay.body",
2547 LayoutStyle::column()
2548 .with_width_percent(1.0)
2549 .with_height_percent(1.0)
2550 .padding(12.0)
2551 .gap(8.0),
2552 ),
2553 );
2554 let header = row(ui, body, "showcase.popup_overlay.header", 8.0);
2555 widgets::label(
2556 ui,
2557 header,
2558 "showcase.popup_overlay.title",
2559 "Popup panel",
2560 text(13.0, color(240, 244, 250)),
2561 LayoutStyle::new().with_width_percent(1.0),
2562 );
2563 let mut close =
2564 widgets::ButtonOptions::new(LayoutStyle::size(28.0, 24.0)).with_action("popup.close");
2565 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
2566 close.hovered_visual = Some(button_visual(54, 70, 92));
2567 close.text_style = text(13.0, color(220, 228, 238));
2568 widgets::button(ui, header, "showcase.popup_overlay.close", "x", close);
2569 widgets::label(
2570 ui,
2571 body,
2572 "showcase.popup_overlay.body_text",
2573 "This surface is rendered as an overlay.",
2574 text(12.0, color(196, 210, 230)),
2575 LayoutStyle::new().with_width_percent(1.0),
2576 );
2577 }
2578}Trait Implementations§
Source§impl Clone for UiNodeStyle
impl Clone for UiNodeStyle
Source§fn clone(&self) -> UiNodeStyle
fn clone(&self) -> UiNodeStyle
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for UiNodeStyle
impl Debug for UiNodeStyle
Source§impl Default for UiNodeStyle
impl Default for UiNodeStyle
Source§impl From<LayoutStyle> for UiNodeStyle
impl From<LayoutStyle> for UiNodeStyle
Source§fn from(layout: LayoutStyle) -> Self
fn from(layout: LayoutStyle) -> Self
Converts to this type from the input type.
Auto Trait Implementations§
impl Freeze for UiNodeStyle
impl RefUnwindSafe for UiNodeStyle
impl !Send for UiNodeStyle
impl !Sync for UiNodeStyle
impl Unpin for UiNodeStyle
impl UnsafeUnpin for UiNodeStyle
impl UnwindSafe for UiNodeStyle
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.