Skip to main content

UiNodeStyle

Struct UiNodeStyle 

Source
pub struct UiNodeStyle { /* private fields */ }

Implementations§

Source§

impl UiNodeStyle

Source

pub fn new(layout: impl Into<LayoutStyle>) -> Self

Source

pub fn clipped(layout: impl Into<LayoutStyle>) -> Self

Source

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    }
Source

pub fn layout(&self) -> Option<Layout>

Source

pub const fn clip(&self) -> ClipBehavior

Source

pub const fn opacity(&self) -> f32

Source

pub const fn z_index(&self) -> i16

Source

pub fn with_clip(self, clip: ClipBehavior) -> Self

Source

pub fn with_opacity(self, opacity: f32) -> Self

Source

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}
Source

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        &registry,
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}
Source

pub fn set_opacity(&mut self, opacity: f32)

Source

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

Source§

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)

Performs copy-assignment from source. Read more
Source§

impl Debug for UiNodeStyle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for UiNodeStyle

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl From<LayoutStyle> for UiNodeStyle

Source§

fn from(layout: LayoutStyle) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

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>

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)

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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more