Skip to main content

ScrollContainerOptions

Struct ScrollContainerOptions 

Source
pub struct ScrollContainerOptions {
    pub layout: LayoutStyle,
    pub viewport_layout: LayoutStyle,
    pub axes: ScrollAxes,
    pub vertical_scrollbar: ScrollbarOptions,
    pub horizontal_scrollbar: ScrollbarOptions,
    pub scrollbar_thickness: f32,
    pub gap: f32,
    pub scrollbar_visibility: ScrollbarVisibility,
    pub auto_actions: bool,
    pub action_prefix: Option<String>,
    pub accessibility_label: Option<String>,
}

Fields§

§layout: LayoutStyle§viewport_layout: LayoutStyle§axes: ScrollAxes§vertical_scrollbar: ScrollbarOptions§horizontal_scrollbar: ScrollbarOptions§scrollbar_thickness: f32§gap: f32§scrollbar_visibility: ScrollbarVisibility§auto_actions: bool§action_prefix: Option<String>§accessibility_label: Option<String>

Implementations§

Source§

impl ScrollContainerOptions

Source

pub fn with_layout(self, layout: impl Into<LayoutStyle>) -> Self

Examples found in repository?
examples/showcase.rs (lines 3061-3067)
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}
5708
5709fn diagnostics_sample_snapshot(state: &ShowcaseState) -> DebugInspectorSnapshot {
5710    diagnostics_sample_snapshot_for(
5711        state.diagnostics_animation_hover,
5712        state.diagnostics_animation_active,
5713    )
5714}
5715
5716fn diagnostics_sample_snapshot_for(hover: f32, active: bool) -> DebugInspectorSnapshot {
5717    let mut sample = UiDocument::new(root_style(320.0, 180.0));
5718    let card = sample.add_child(
5719        sample.root(),
5720        UiNode::container(
5721            "diagnostics.sample.card",
5722            LayoutStyle::column()
5723                .with_width_percent(1.0)
5724                .with_height(120.0)
5725                .padding(12.0)
5726                .gap(8.0),
5727        )
5728        .with_visual(UiVisual::panel(
5729            color(16, 22, 30),
5730            Some(StrokeStyle::new(color(62, 77, 98), 1.0)),
5731            6.0,
5732        ))
5733        .with_accessibility(
5734            AccessibilityMeta::new(AccessibilityRole::Group).label("Diagnostics sample"),
5735        ),
5736    );
5737    sample.add_child(
5738        card,
5739        UiNode::container(
5740            "diagnostics.sample.preview",
5741            LayoutStyle::new().with_width(160.0).with_height(38.0),
5742        )
5743        .with_input(InputBehavior::BUTTON)
5744        .with_visual(UiVisual::panel(
5745            color(52, 112, 180),
5746            Some(StrokeStyle::new(color(116, 183, 255), 1.0)),
5747            5.0,
5748        ))
5749        .with_accessibility(
5750            AccessibilityMeta::new(AccessibilityRole::Button)
5751                .label("Preview action")
5752                .focusable(),
5753        )
5754        .with_animation(
5755            AnimationMachine::new(
5756                vec![
5757                    AnimationState::new(
5758                        "idle",
5759                        AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0),
5760                    ),
5761                    AnimationState::new(
5762                        "hot",
5763                        AnimatedValues::new(0.92, UiPoint::new(18.0, 0.0), 1.08),
5764                    ),
5765                ],
5766                vec![AnimationTransition::when(
5767                    "idle",
5768                    "hot",
5769                    AnimationCondition::bool("active", true),
5770                    0.18,
5771                )],
5772                "idle",
5773            )
5774            .expect("sample animation")
5775            .with_number_input("hover", hover)
5776            .with_blend_binding(AnimationBlendBinding::new("hover", "idle", "hot"))
5777            .with_bool_input("active", active)
5778            .with_trigger_input("pulse"),
5779        ),
5780    );
5781    widgets::label(
5782        &mut sample,
5783        card,
5784        "diagnostics.sample.label",
5785        "Sample node",
5786        text(12.0, color(198, 210, 226)),
5787        LayoutStyle::new().with_width_percent(1.0),
5788    );
5789    sample
5790        .compute_layout(UiSize::new(320.0, 180.0), &mut ApproxTextMeasurer)
5791        .expect("sample layout");
5792    DebugInspectorSnapshot::from_document(&sample, &mut ApproxTextMeasurer)
5793}
5794
5795fn diagnostics_command_registry() -> CommandRegistry {
5796    let mut registry = CommandRegistry::new();
5797    registry
5798        .register(
5799            CommandMeta::new("diagnostics.palette", "Open command palette")
5800                .description("Show command search")
5801                .category("Debug"),
5802        )
5803        .expect("command");
5804    registry
5805        .register(
5806            CommandMeta::new("diagnostics.inspect", "Inspect selected node")
5807                .description("Focus the layout inspector")
5808                .category("Debug"),
5809        )
5810        .expect("command");
5811    registry
5812        .register(
5813            CommandMeta::new("diagnostics.record", "Start interaction recording")
5814                .description("Capture replay steps")
5815                .category("Testing"),
5816        )
5817        .expect("command");
5818    registry
5819        .register(CommandMeta::new(
5820            "diagnostics.export_theme",
5821            "Export theme patch",
5822        ))
5823        .expect("command");
5824    registry
5825        .bind_shortcut(
5826            CommandScope::Global,
5827            Shortcut::ctrl('k'),
5828            "diagnostics.palette",
5829        )
5830        .expect("shortcut");
5831    registry
5832        .bind_shortcut(
5833            CommandScope::Panel,
5834            Shortcut::ctrl('i'),
5835            "diagnostics.inspect",
5836        )
5837        .expect("shortcut");
5838    registry
5839        .bind_shortcut(
5840            CommandScope::Panel,
5841            Shortcut::ctrl('r'),
5842            "diagnostics.record",
5843        )
5844        .expect("shortcut");
5845    registry
5846        .disable("diagnostics.export_theme", "No changes to export")
5847        .expect("disable");
5848    registry
5849}
5850
5851fn tree_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5852    let body = section(ui, parent, "trees", "Tree view");
5853    ext_widgets::tree_view(
5854        ui,
5855        body,
5856        "trees.tree_view",
5857        &tree_items(),
5858        &state.tree,
5859        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.tree"),
5860    );
5861    ext_widgets::outliner(
5862        ui,
5863        body,
5864        "trees.outliner",
5865        &tree_items(),
5866        &state.outliner,
5867        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.outliner"),
5868    );
5869    let virtual_state = ext_widgets::TreeViewState::expanded(["root"]);
5870    let virtual_nodes = ext_widgets::virtualized_tree_view(
5871        ui,
5872        body,
5873        "trees.virtual",
5874        &virtual_tree_items(),
5875        &virtual_state,
5876        ext_widgets::VirtualTreeViewSpec::new(24.0, 112.0)
5877            .scroll_offset(state.tree_virtual_scroll)
5878            .overscan_rows(1),
5879        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.virtual"),
5880    );
5881    ui.node_mut(virtual_nodes.body)
5882        .set_action("trees.virtual.scroll");
5883    tree_table_widgets(ui, body, state);
5884}
5885
5886fn tree_table_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5887    let tree_state = ext_widgets::TreeViewState::expanded(["root", "branch-a"]);
5888    let rows = tree_state.visible_items(&tree_table_items());
5889    let columns = [
5890        ext_widgets::DataTableColumn::new("name", "Name", 220.0),
5891        ext_widgets::DataTableColumn::new("kind", "Kind", 84.0),
5892        ext_widgets::DataTableColumn::new("status", "Status", 92.0),
5893    ];
5894    let mut options = ext_widgets::DataTableOptions::default()
5895        .with_row_action_prefix("trees.table")
5896        .with_cell_action_prefix("trees.table");
5897    options.layout = LayoutStyle::column()
5898        .with_width_percent(1.0)
5899        .with_height(132.0)
5900        .with_flex_shrink(0.0);
5901    ext_widgets::virtualized_data_table(
5902        ui,
5903        parent,
5904        "trees.table",
5905        &columns,
5906        ext_widgets::VirtualDataTableSpec {
5907            row_count: rows.len(),
5908            row_height: 24.0,
5909            viewport_width: 396.0,
5910            viewport_height: 96.0,
5911            scroll_offset: UiPoint::new(0.0, state.tree_virtual_scroll),
5912            overscan_rows: 1,
5913        },
5914        options,
5915        |ui, cell_parent, cell| {
5916            let value = rows
5917                .get(cell.row)
5918                .map(|item| tree_table_cell_value(item, cell.column))
5919                .unwrap_or_default();
5920            widgets::label(
5921                ui,
5922                cell_parent,
5923                format!("trees.table.cell.{}.{}.label", cell.row, cell.column),
5924                value,
5925                text(12.0, color(220, 228, 238)),
5926                LayoutStyle::new().with_width_percent(1.0),
5927            );
5928        },
5929    );
5930}
5931
5932fn tree_table_cell_value(item: &ext_widgets::TreeVisibleItem, column: usize) -> String {
5933    match column {
5934        0 => format!("{}{}", "  ".repeat(item.depth), item.label),
5935        1 => {
5936            if item.has_children() {
5937                "Folder".to_owned()
5938            } else {
5939                "File".to_owned()
5940            }
5941        }
5942        _ => {
5943            if item.disabled {
5944                "Locked".to_owned()
5945            } else if item.expanded {
5946                "Expanded".to_owned()
5947            } else {
5948                "Ready".to_owned()
5949            }
5950        }
5951    }
5952}
5953
5954fn tab_split_dock_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5955    let body = section_with_min_viewport(
5956        ui,
5957        parent,
5958        "layout_widgets",
5959        "Dock workspace",
5960        UiSize::new(546.0, 360.0),
5961    );
5962    let shell = ui.add_child(
5963        body,
5964        UiNode::container(
5965            "layout_widgets.dock_shell",
5966            LayoutStyle::column()
5967                .with_width_percent(1.0)
5968                .with_height(360.0)
5969                .with_flex_shrink(0.0),
5970        )
5971        .with_visual(UiVisual::panel(
5972            color(13, 17, 23),
5973            Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
5974            0.0,
5975        )),
5976    );
5977
5978    let mut panels = base_layout_dock_panels();
5979    state.layout_dock.apply_order_to_panels(&mut panels);
5980    state.layout_dock.apply_visibility_to_panels(&mut panels);
5981
5982    let mut drawer_options = ext_widgets::DockDrawerRailOptions::default();
5983    drawer_options.layout = LayoutStyle::row()
5984        .with_width_percent(1.0)
5985        .with_height(34.0)
5986        .with_padding(4.0)
5987        .with_gap(4.0);
5988    ext_widgets::dock_drawer_rail(
5989        ui,
5990        shell,
5991        "layout_widgets.dock.drawers",
5992        &[
5993            ext_widgets::DockDrawerDescriptor::new(
5994                "inspector",
5995                "Inspector",
5996                "inspector",
5997                ext_widgets::DockSide::Left,
5998            )
5999            .open(!state.layout_dock.is_hidden("inspector"))
6000            .with_action("layout_widgets.drawer.inspector"),
6001            ext_widgets::DockDrawerDescriptor::new(
6002                "assets",
6003                "Assets",
6004                "assets",
6005                ext_widgets::DockSide::Right,
6006            )
6007            .open(!state.layout_dock.is_hidden("assets"))
6008            .with_action("layout_widgets.drawer.assets"),
6009        ],
6010        drawer_options,
6011    );
6012
6013    let mut options = ext_widgets::DockWorkspaceOptions::default();
6014    options.layout = LayoutStyle::column()
6015        .with_width_percent(1.0)
6016        .with_height(0.0)
6017        .with_flex_grow(1.0);
6018    options.show_titles = false;
6019    options.panel_visual = UiVisual::panel(
6020        color(18, 22, 29),
6021        Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
6022        0.0,
6023    );
6024    options.center_visual = UiVisual::panel(
6025        color(15, 19, 25),
6026        Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
6027        0.0,
6028    );
6029
6030    ext_widgets::dock_workspace(
6031        ui,
6032        shell,
6033        "layout_widgets.dock",
6034        &panels,
6035        options,
6036        |ui, parent, panel| match panel.id.as_str() {
6037            "inspector" => egui_panel_contents(
6038                ui,
6039                parent,
6040                "layout.inspector",
6041                "Inspector",
6042                state.layout_inspector_scroll,
6043            ),
6044            "assets" => egui_panel_contents(
6045                ui,
6046                parent,
6047                "layout.assets",
6048                "Assets",
6049                state.layout_assets_scroll,
6050            ),
6051            _ => dock_document_panel(ui, parent, state),
6052        },
6053    );
6054
6055    if let Some(floating) = state.layout_dock.floating_panel("inspector") {
6056        let floating_panel = ui.add_child(
6057            shell,
6058            UiNode::container(
6059                "layout_widgets.floating.inspector",
6060                operad::layout::absolute(
6061                    floating.rect.x,
6062                    floating.rect.y,
6063                    floating.rect.width,
6064                    floating.rect.height,
6065                ),
6066            )
6067            .with_visual(UiVisual::panel(
6068                color(18, 22, 29),
6069                Some(StrokeStyle::new(color(86, 102, 124), 1.0)),
6070                4.0,
6071            )),
6072        );
6073        egui_panel_contents(
6074            ui,
6075            floating_panel,
6076            "layout.inspector_floating",
6077            "Inspector",
6078            state.layout_inspector_scroll,
6079        );
6080    }
6081}
6082
6083fn base_layout_dock_panels() -> Vec<ext_widgets::DockPanelDescriptor> {
6084    vec![
6085        ext_widgets::DockPanelDescriptor::new(
6086            "inspector",
6087            "Inspector",
6088            ext_widgets::DockSide::Left,
6089            120.0,
6090        )
6091        .with_min_size(104.0)
6092        .resizable(true),
6093        ext_widgets::DockPanelDescriptor::center("document", "Document"),
6094        ext_widgets::DockPanelDescriptor::new(
6095            "assets",
6096            "Assets",
6097            ext_widgets::DockSide::Right,
6098            104.0,
6099        )
6100        .with_min_size(94.0)
6101        .resizable(true),
6102    ]
6103}
6104
6105fn dock_document_panel(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6106    let content = ui.add_child(
6107        parent,
6108        UiNode::container(
6109            "layout_widgets.document.content",
6110            LayoutStyle::column()
6111                .with_width_percent(1.0)
6112                .with_height_percent(1.0)
6113                .with_padding(8.0)
6114                .with_gap(8.0),
6115        ),
6116    );
6117
6118    let controls = wrapping_row(ui, content, "layout_widgets.dock.controls", 8.0);
6119    let (action, label) = if state.layout_dock.is_floating("inspector") {
6120        ("layout_widgets.dock_inspector", "Dock inspector")
6121    } else {
6122        ("layout_widgets.float_inspector", "Float inspector")
6123    };
6124    let mut float_button = widgets::ButtonOptions::new(
6125        LayoutStyle::new()
6126            .with_width(132.0)
6127            .with_height(28.0)
6128            .with_flex_shrink(0.0),
6129    )
6130    .with_action(action);
6131    float_button.visual = button_visual(40, 52, 68);
6132    float_button.hovered_visual = Some(button_visual(54, 70, 92));
6133    float_button.text_style = text(12.0, color(232, 238, 248));
6134    widgets::button(
6135        ui,
6136        controls,
6137        "layout_widgets.dock.float_inspector",
6138        label,
6139        float_button,
6140    );
6141
6142    let mut before_button = widgets::ButtonOptions::new(
6143        LayoutStyle::new()
6144            .with_width(136.0)
6145            .with_height(28.0)
6146            .with_flex_shrink(0.0),
6147    )
6148    .with_action("layout_widgets.reorder.assets.before.inspector");
6149    before_button.visual = button_visual(34, 44, 58);
6150    before_button.hovered_visual = Some(button_visual(48, 64, 84));
6151    before_button.text_style = text(12.0, color(232, 238, 248));
6152    widgets::button(
6153        ui,
6154        controls,
6155        "layout_widgets.dock.assets_before_inspector",
6156        "Assets before",
6157        before_button,
6158    );
6159
6160    let mut after_button = widgets::ButtonOptions::new(
6161        LayoutStyle::new()
6162            .with_width(126.0)
6163            .with_height(28.0)
6164            .with_flex_shrink(0.0),
6165    )
6166    .with_action("layout_widgets.reorder.assets.after.inspector");
6167    after_button.visual = button_visual(34, 44, 58);
6168    after_button.hovered_visual = Some(button_visual(48, 64, 84));
6169    after_button.text_style = text(12.0, color(232, 238, 248));
6170    widgets::button(
6171        ui,
6172        controls,
6173        "layout_widgets.dock.assets_after_inspector",
6174        "Assets after",
6175        after_button,
6176    );
6177
6178    let zones = ext_widgets::dock_workspace::dock_workspace_drop_zones(
6179        "layout_widgets.dock",
6180        UiRect::new(0.0, 0.0, 520.0, 340.0),
6181        ext_widgets::DockWorkspaceDragOptions::default()
6182            .allowed_sides([
6183                ext_widgets::DockSide::Left,
6184                ext_widgets::DockSide::Right,
6185                ext_widgets::DockSide::Center,
6186            ])
6187            .edge_thickness(44.0),
6188    );
6189    let targets = wrapping_row(ui, content, "layout_widgets.dock.targets", 6.0);
6190    for zone in zones {
6191        dock_drop_target_chip(ui, targets, &zone);
6192    }
6193
6194    let mut panels = base_layout_dock_panels();
6195    state.layout_dock.apply_order_to_panels(&mut panels);
6196    let reorder_targets: Vec<_> = [
6197        ext_widgets::DockSide::Left,
6198        ext_widgets::DockSide::Right,
6199        ext_widgets::DockSide::Center,
6200    ]
6201    .into_iter()
6202    .flat_map(|side| {
6203        ext_widgets::dock_workspace::dock_panel_reorder_drop_targets(
6204            "layout_widgets.dock",
6205            &panels,
6206            side,
6207            UiRect::new(0.0, 0.0, 180.0, 120.0),
6208            ext_widgets::DockWorkspaceReorderOptions::default().target_thickness(20.0),
6209        )
6210    })
6211    .collect();
6212    let reorder_row = wrapping_row(ui, content, "layout_widgets.dock.reorder_targets", 6.0);
6213    for target in reorder_targets {
6214        dock_reorder_target_chip(ui, reorder_row, &target);
6215    }
6216
6217    let tabs = [
6218        ext_widgets::TabItem::new("preview", "Preview"),
6219        ext_widgets::TabItem::new("log", "Output").dirty(),
6220        ext_widgets::TabItem::new("settings", "Settings").closable(),
6221    ];
6222    let mut tab_options = ext_widgets::TabGroupOptions::default();
6223    tab_options.layout = LayoutStyle::column()
6224        .with_width_percent(1.0)
6225        .with_height(0.0)
6226        .with_flex_grow(1.0);
6227    tab_options.tab_strip_height = 30.0;
6228    tab_options.min_tab_width = 92.0;
6229    tab_options.text_style = text(12.0, color(226, 234, 246));
6230    tab_options.muted_text_style = text(12.0, color(150, 162, 178));
6231    ext_widgets::tab_group(
6232        ui,
6233        content,
6234        "layout_widgets.document.tabs",
6235        &tabs,
6236        ext_widgets::TabGroupState::selected(0),
6237        tab_options,
6238        |ui, panel, _index| {
6239            widgets::label(
6240                ui,
6241                panel,
6242                "layout_widgets.document.tabs.preview.body",
6243                "Workspace preview",
6244                text(12.0, color(190, 202, 218)),
6245                LayoutStyle::new().with_width_percent(1.0).with_height(26.0),
6246            );
6247        },
6248    );
6249}
6250
6251fn dock_drop_target_chip(
6252    ui: &mut UiDocument,
6253    parent: UiNodeId,
6254    zone: &ext_widgets::DockWorkspaceDropZone,
6255) -> UiNodeId {
6256    let chip = ui.add_child(
6257        parent,
6258        UiNode::container(
6259            format!("{}.chip", zone.target.id.as_str()),
6260            LayoutStyle::row()
6261                .with_width(78.0)
6262                .with_height(26.0)
6263                .with_padding(6.0)
6264                .with_flex_shrink(0.0),
6265        )
6266        .with_input(InputBehavior::BUTTON)
6267        .with_visual(UiVisual::panel(
6268            color(24, 32, 42),
6269            Some(StrokeStyle::new(color(78, 94, 116), 1.0)),
6270            4.0,
6271        ))
6272        .with_accessibility(zone.target.accessibility_meta()),
6273    );
6274    widgets::label(
6275        ui,
6276        chip,
6277        format!("{}.label", zone.target.id.as_str()),
6278        dock_drop_target_short_label(zone.placement),
6279        text(11.0, color(206, 216, 230)),
6280        LayoutStyle::new().with_width_percent(1.0),
6281    );
6282    chip
6283}
6284
6285fn dock_reorder_target_chip(
6286    ui: &mut UiDocument,
6287    parent: UiNodeId,
6288    target: &ext_widgets::DockPanelReorderTarget,
6289) -> UiNodeId {
6290    let chip = ui.add_child(
6291        parent,
6292        UiNode::container(
6293            format!("{}.chip", target.target.id.as_str()),
6294            LayoutStyle::row()
6295                .with_width(104.0)
6296                .with_height(26.0)
6297                .with_padding(6.0)
6298                .with_flex_shrink(0.0),
6299        )
6300        .with_input(InputBehavior::BUTTON)
6301        .with_visual(UiVisual::panel(
6302            color(22, 34, 42),
6303            Some(StrokeStyle::new(color(80, 112, 128), 1.0)),
6304            4.0,
6305        ))
6306        .with_accessibility(target.target.accessibility_meta()),
6307    );
6308    widgets::label(
6309        ui,
6310        chip,
6311        format!("{}.label", target.target.id.as_str()),
6312        dock_reorder_target_short_label(target),
6313        text(11.0, color(206, 216, 230)),
6314        LayoutStyle::new().with_width_percent(1.0),
6315    );
6316    chip
6317}
6318
6319fn dock_drop_target_short_label(placement: ext_widgets::DockDropPlacement) -> &'static str {
6320    match placement {
6321        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Left) => "Left",
6322        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Right) => "Right",
6323        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Center) => "Center",
6324        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Top) => "Top",
6325        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Bottom) => "Bottom",
6326        ext_widgets::DockDropPlacement::Floating => "Float",
6327    }
6328}
6329
6330fn dock_reorder_target_short_label(target: &ext_widgets::DockPanelReorderTarget) -> String {
6331    let placement = match target.placement {
6332        ext_widgets::DockPanelReorderPlacement::Before => "Before",
6333        ext_widgets::DockPanelReorderPlacement::After => "After",
6334    };
6335    format!("{placement} {}", target.panel_id)
6336}
6337
6338fn container_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6339    let body = section(ui, parent, "containers", "Containers");
6340
6341    let frame = widgets::frame(
6342        ui,
6343        body,
6344        "containers.frame",
6345        widgets::FrameOptions::default().with_layout(
6346            LayoutStyle::column()
6347                .with_width_percent(1.0)
6348                .with_height(64.0)
6349                .with_padding(8.0)
6350                .with_gap(6.0),
6351        ),
6352    );
6353    widgets::strong_label(
6354        ui,
6355        frame,
6356        "containers.frame.title",
6357        "Frame",
6358        LayoutStyle::new().with_width_percent(1.0),
6359    );
6360    widgets::weak_label(
6361        ui,
6362        frame,
6363        "containers.frame.body",
6364        "Default framed surface with padding, stroke, and clipping.",
6365        LayoutStyle::new().with_width_percent(1.0),
6366    );
6367
6368    let group = widgets::group(ui, body, "containers.group");
6369    widgets::label(
6370        ui,
6371        group,
6372        "containers.group.label",
6373        "Group helper",
6374        text(12.0, color(220, 228, 238)),
6375        LayoutStyle::new().with_width_percent(1.0),
6376    );
6377    let generic_panel = widgets::panel(
6378        ui,
6379        body,
6380        "containers.panel",
6381        widgets::PanelOptions::group().with_layout(
6382            LayoutStyle::column()
6383                .with_width_percent(1.0)
6384                .with_height(44.0)
6385                .with_padding(8.0),
6386        ),
6387    );
6388    widgets::label(
6389        ui,
6390        generic_panel,
6391        "containers.panel.label",
6392        "Generic panel",
6393        text(12.0, color(220, 228, 238)),
6394        LayoutStyle::new().with_width_percent(1.0),
6395    );
6396    let group_panel = widgets::group_panel(ui, body, "containers.group_panel");
6397    widgets::label(
6398        ui,
6399        group_panel,
6400        "containers.group_panel.label",
6401        "Group panel",
6402        text(12.0, color(220, 228, 238)),
6403        LayoutStyle::new().with_width_percent(1.0),
6404    );
6405
6406    widgets::separator(
6407        ui,
6408        body,
6409        "containers.separator",
6410        widgets::SeparatorOptions::default(),
6411    );
6412    widgets::spacer(
6413        ui,
6414        body,
6415        "containers.spacer",
6416        LayoutStyle::new()
6417            .with_width_percent(1.0)
6418            .with_height(8.0)
6419            .with_flex_shrink(0.0),
6420    );
6421
6422    let grid = widgets::grid::grid(
6423        ui,
6424        body,
6425        "containers.grid",
6426        widgets::grid::GridOptions::default().with_layout(
6427            LayoutStyle::column()
6428                .with_width_percent(1.0)
6429                .with_height(78.0)
6430                .with_gap(4.0),
6431        ),
6432    );
6433    for row_index in 0..2 {
6434        let row = widgets::grid::grid_row(
6435            ui,
6436            grid,
6437            format!("containers.grid.row.{row_index}"),
6438            widgets::grid::GridRowOptions::default(),
6439        );
6440        for column_index in 0..3 {
6441            widgets::grid::grid_text_cell(
6442                ui,
6443                row,
6444                format!("containers.grid.row.{row_index}.cell.{column_index}"),
6445                format!("R{} C{}", row_index + 1, column_index + 1),
6446                widgets::grid::GridCellOptions {
6447                    text_style: text(12.0, color(214, 224, 238)),
6448                    ..Default::default()
6449                },
6450            );
6451        }
6452    }
6453
6454    widgets::sides(
6455        ui,
6456        body,
6457        "containers.sides",
6458        widgets::SidesOptions::default()
6459            .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
6460            .with_gap(8.0)
6461            .with_visual(UiVisual::panel(
6462                color(20, 25, 32),
6463                Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
6464                4.0,
6465            )),
6466        |ui, left| {
6467            widgets::label(
6468                ui,
6469                left,
6470                "containers.sides.left.label",
6471                "Left side",
6472                text(12.0, color(220, 228, 238)),
6473                LayoutStyle::new().with_width_percent(1.0),
6474            );
6475        },
6476        |ui, right| {
6477            widgets::label(
6478                ui,
6479                right,
6480                "containers.sides.right.label",
6481                "Right side",
6482                text(12.0, color(220, 228, 238)),
6483                LayoutStyle::new().with_width_percent(1.0),
6484            );
6485        },
6486    );
6487
6488    widgets::columns(
6489        ui,
6490        body,
6491        "containers.columns",
6492        3,
6493        widgets::ColumnsOptions::default()
6494            .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
6495            .with_gap(8.0),
6496        |ui, column, index| {
6497            widgets::label(
6498                ui,
6499                column,
6500                format!("containers.columns.{index}.label"),
6501                format!("Column {}", index + 1),
6502                text(12.0, color(220, 228, 238)),
6503                LayoutStyle::new().with_width_percent(1.0),
6504            );
6505        },
6506    );
6507
6508    let indented = widgets::indented_section(
6509        ui,
6510        body,
6511        "containers.indented",
6512        widgets::IndentOptions::default().with_amount(24.0),
6513    );
6514    widgets::label(
6515        ui,
6516        indented,
6517        "containers.indented.label",
6518        "Indented section",
6519        text(12.0, color(196, 210, 230)),
6520        LayoutStyle::new().with_width_percent(1.0),
6521    );
6522
6523    widgets::resize_container(
6524        ui,
6525        body,
6526        "containers.resize_container",
6527        widgets::ResizeContainerOptions::default().with_layout(
6528            LayoutStyle::column()
6529                .with_width_percent(1.0)
6530                .with_height(92.0)
6531                .with_flex_shrink(0.0),
6532        ),
6533        |ui, content| {
6534            widgets::label(
6535                ui,
6536                content,
6537                "containers.resize_container.label",
6538                "Resize container",
6539                text(12.0, color(220, 228, 238)),
6540                LayoutStyle::new().with_width_percent(1.0),
6541            );
6542        },
6543    );
6544    widgets::container::resize_handle(
6545        ui,
6546        body,
6547        "containers.resize_handle",
6548        widgets::container::ResizeHandleOptions::default()
6549            .with_layout(LayoutStyle::size(20.0, 20.0))
6550            .accessibility_label("Inline resize handle"),
6551    );
6552
6553    widgets::scene(
6554        ui,
6555        body,
6556        "containers.scene",
6557        vec![
6558            ScenePrimitive::Rect(
6559                PaintRect::solid(UiRect::new(8.0, 12.0, 108.0, 46.0), color(48, 112, 184))
6560                    .stroke(AlignedStroke::inside(StrokeStyle::new(
6561                        color(132, 174, 222),
6562                        1.0,
6563                    )))
6564                    .corner_radii(CornerRadii::uniform(6.0)),
6565            ),
6566            ScenePrimitive::Circle {
6567                center: UiPoint::new(150.0, 35.0),
6568                radius: 22.0,
6569                fill: color(111, 203, 159),
6570                stroke: Some(StrokeStyle::new(color(176, 236, 206), 1.0)),
6571            },
6572            ScenePrimitive::Line {
6573                from: UiPoint::new(188.0, 18.0),
6574                to: UiPoint::new(238.0, 52.0),
6575                stroke: StrokeStyle::new(color(232, 186, 88), 3.0),
6576            },
6577        ],
6578        widgets::SceneOptions::default()
6579            .with_layout(LayoutStyle::new().with_width(260.0).with_height(70.0))
6580            .accessibility_label("Scene primitives"),
6581    );
6582
6583    let panel_shell = widgets::frame(
6584        ui,
6585        body,
6586        "containers.panels",
6587        widgets::FrameOptions::default().with_layout(
6588            LayoutStyle::column()
6589                .with_width_percent(1.0)
6590                .with_height(160.0)
6591                .with_padding(0.0)
6592                .with_gap(0.0),
6593        ),
6594    );
6595    let top = widgets::top_panel(ui, panel_shell, "containers.panels.top", 28.0);
6596    widgets::label(
6597        ui,
6598        top,
6599        "containers.panels.top.label",
6600        "Top panel",
6601        text(12.0, color(220, 228, 238)),
6602        LayoutStyle::new().with_width_percent(1.0),
6603    );
6604    let middle = row(ui, panel_shell, "containers.panels.middle", 0.0);
6605    let left = widgets::side_panel(
6606        ui,
6607        middle,
6608        "containers.panels.side",
6609        widgets::SidePanelSide::Left,
6610        90.0,
6611    );
6612    widgets::label(
6613        ui,
6614        left,
6615        "containers.panels.side.label",
6616        "Side",
6617        text(12.0, color(220, 228, 238)),
6618        LayoutStyle::new().with_width_percent(1.0),
6619    );
6620    let left = widgets::left_panel(ui, middle, "containers.panels.left", 90.0);
6621    widgets::label(
6622        ui,
6623        left,
6624        "containers.panels.left.label",
6625        "Left",
6626        text(12.0, color(220, 228, 238)),
6627        LayoutStyle::new().with_width_percent(1.0),
6628    );
6629    let center = widgets::central_panel(ui, middle, "containers.panels.center");
6630    widgets::label(
6631        ui,
6632        center,
6633        "containers.panels.center.label",
6634        "Central panel",
6635        text(12.0, color(220, 228, 238)),
6636        LayoutStyle::new().with_width_percent(1.0),
6637    );
6638    let right = widgets::right_panel(ui, middle, "containers.panels.right", 110.0);
6639    widgets::label(
6640        ui,
6641        right,
6642        "containers.panels.right.label",
6643        "Right",
6644        text(12.0, color(220, 228, 238)),
6645        LayoutStyle::new().with_width_percent(1.0),
6646    );
6647    let bottom = widgets::bottom_panel(ui, panel_shell, "containers.panels.bottom", 28.0);
6648    widgets::label(
6649        ui,
6650        bottom,
6651        "containers.panels.bottom.label",
6652        "Bottom panel",
6653        text(12.0, color(220, 228, 238)),
6654        LayoutStyle::new().with_width_percent(1.0),
6655    );
6656
6657    widgets::scroll_container(
6658        ui,
6659        body,
6660        "containers.scroll_area_with_bars",
6661        state.containers_scroll,
6662        widgets::ScrollContainerOptions::default()
6663            .with_axes(ScrollAxes::BOTH)
6664            .with_layout(LayoutStyle::column().with_width(300.0).with_height(116.0)),
6665        |ui, viewport| {
6666            for index in 0..5 {
6667                widgets::label(
6668                    ui,
6669                    viewport,
6670                    format!("containers.scroll_area_with_bars.row.{index}"),
6671                    format!("Scrollable row {}", index + 1),
6672                    text(12.0, color(200, 212, 228)),
6673                    LayoutStyle::new()
6674                        .with_width(420.0)
6675                        .with_height(28.0)
6676                        .with_flex_shrink(0.0),
6677                );
6678            }
6679        },
6680    );
6681
6682    let area_host = ui.add_child(
6683        body,
6684        UiNode::container(
6685            "containers.area.host",
6686            LayoutStyle::new()
6687                .with_width_percent(1.0)
6688                .with_height(82.0)
6689                .with_flex_shrink(0.0),
6690        )
6691        .with_visual(UiVisual::panel(
6692            color(17, 20, 25),
6693            Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
6694            4.0,
6695        )),
6696    );
6697    widgets::container::area(
6698        ui,
6699        area_host,
6700        "containers.area",
6701        widgets::container::AreaOptions::new(UiRect::new(14.0, 14.0, 180.0, 44.0))
6702            .with_visual(UiVisual::panel(color(39, 72, 109), None, 4.0))
6703            .accessibility_label("Absolute positioned area"),
6704        |ui, area| {
6705            widgets::label(
6706                ui,
6707                area,
6708                "containers.area.label",
6709                "Area",
6710                text(12.0, color(238, 244, 252)),
6711                LayoutStyle::new().with_width_percent(1.0),
6712            );
6713        },
6714    );
6715}
Source

pub fn with_viewport_layout(self, layout: impl Into<LayoutStyle>) -> Self

Examples found in repository?
examples/showcase.rs (lines 3068-3075)
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}
Source

pub const fn with_axes(self, axes: ScrollAxes) -> Self

Examples found in repository?
examples/showcase.rs (line 3076)
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}
5708
5709fn diagnostics_sample_snapshot(state: &ShowcaseState) -> DebugInspectorSnapshot {
5710    diagnostics_sample_snapshot_for(
5711        state.diagnostics_animation_hover,
5712        state.diagnostics_animation_active,
5713    )
5714}
5715
5716fn diagnostics_sample_snapshot_for(hover: f32, active: bool) -> DebugInspectorSnapshot {
5717    let mut sample = UiDocument::new(root_style(320.0, 180.0));
5718    let card = sample.add_child(
5719        sample.root(),
5720        UiNode::container(
5721            "diagnostics.sample.card",
5722            LayoutStyle::column()
5723                .with_width_percent(1.0)
5724                .with_height(120.0)
5725                .padding(12.0)
5726                .gap(8.0),
5727        )
5728        .with_visual(UiVisual::panel(
5729            color(16, 22, 30),
5730            Some(StrokeStyle::new(color(62, 77, 98), 1.0)),
5731            6.0,
5732        ))
5733        .with_accessibility(
5734            AccessibilityMeta::new(AccessibilityRole::Group).label("Diagnostics sample"),
5735        ),
5736    );
5737    sample.add_child(
5738        card,
5739        UiNode::container(
5740            "diagnostics.sample.preview",
5741            LayoutStyle::new().with_width(160.0).with_height(38.0),
5742        )
5743        .with_input(InputBehavior::BUTTON)
5744        .with_visual(UiVisual::panel(
5745            color(52, 112, 180),
5746            Some(StrokeStyle::new(color(116, 183, 255), 1.0)),
5747            5.0,
5748        ))
5749        .with_accessibility(
5750            AccessibilityMeta::new(AccessibilityRole::Button)
5751                .label("Preview action")
5752                .focusable(),
5753        )
5754        .with_animation(
5755            AnimationMachine::new(
5756                vec![
5757                    AnimationState::new(
5758                        "idle",
5759                        AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0),
5760                    ),
5761                    AnimationState::new(
5762                        "hot",
5763                        AnimatedValues::new(0.92, UiPoint::new(18.0, 0.0), 1.08),
5764                    ),
5765                ],
5766                vec![AnimationTransition::when(
5767                    "idle",
5768                    "hot",
5769                    AnimationCondition::bool("active", true),
5770                    0.18,
5771                )],
5772                "idle",
5773            )
5774            .expect("sample animation")
5775            .with_number_input("hover", hover)
5776            .with_blend_binding(AnimationBlendBinding::new("hover", "idle", "hot"))
5777            .with_bool_input("active", active)
5778            .with_trigger_input("pulse"),
5779        ),
5780    );
5781    widgets::label(
5782        &mut sample,
5783        card,
5784        "diagnostics.sample.label",
5785        "Sample node",
5786        text(12.0, color(198, 210, 226)),
5787        LayoutStyle::new().with_width_percent(1.0),
5788    );
5789    sample
5790        .compute_layout(UiSize::new(320.0, 180.0), &mut ApproxTextMeasurer)
5791        .expect("sample layout");
5792    DebugInspectorSnapshot::from_document(&sample, &mut ApproxTextMeasurer)
5793}
5794
5795fn diagnostics_command_registry() -> CommandRegistry {
5796    let mut registry = CommandRegistry::new();
5797    registry
5798        .register(
5799            CommandMeta::new("diagnostics.palette", "Open command palette")
5800                .description("Show command search")
5801                .category("Debug"),
5802        )
5803        .expect("command");
5804    registry
5805        .register(
5806            CommandMeta::new("diagnostics.inspect", "Inspect selected node")
5807                .description("Focus the layout inspector")
5808                .category("Debug"),
5809        )
5810        .expect("command");
5811    registry
5812        .register(
5813            CommandMeta::new("diagnostics.record", "Start interaction recording")
5814                .description("Capture replay steps")
5815                .category("Testing"),
5816        )
5817        .expect("command");
5818    registry
5819        .register(CommandMeta::new(
5820            "diagnostics.export_theme",
5821            "Export theme patch",
5822        ))
5823        .expect("command");
5824    registry
5825        .bind_shortcut(
5826            CommandScope::Global,
5827            Shortcut::ctrl('k'),
5828            "diagnostics.palette",
5829        )
5830        .expect("shortcut");
5831    registry
5832        .bind_shortcut(
5833            CommandScope::Panel,
5834            Shortcut::ctrl('i'),
5835            "diagnostics.inspect",
5836        )
5837        .expect("shortcut");
5838    registry
5839        .bind_shortcut(
5840            CommandScope::Panel,
5841            Shortcut::ctrl('r'),
5842            "diagnostics.record",
5843        )
5844        .expect("shortcut");
5845    registry
5846        .disable("diagnostics.export_theme", "No changes to export")
5847        .expect("disable");
5848    registry
5849}
5850
5851fn tree_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5852    let body = section(ui, parent, "trees", "Tree view");
5853    ext_widgets::tree_view(
5854        ui,
5855        body,
5856        "trees.tree_view",
5857        &tree_items(),
5858        &state.tree,
5859        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.tree"),
5860    );
5861    ext_widgets::outliner(
5862        ui,
5863        body,
5864        "trees.outliner",
5865        &tree_items(),
5866        &state.outliner,
5867        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.outliner"),
5868    );
5869    let virtual_state = ext_widgets::TreeViewState::expanded(["root"]);
5870    let virtual_nodes = ext_widgets::virtualized_tree_view(
5871        ui,
5872        body,
5873        "trees.virtual",
5874        &virtual_tree_items(),
5875        &virtual_state,
5876        ext_widgets::VirtualTreeViewSpec::new(24.0, 112.0)
5877            .scroll_offset(state.tree_virtual_scroll)
5878            .overscan_rows(1),
5879        ext_widgets::TreeViewOptions::default().with_row_action_prefix("trees.virtual"),
5880    );
5881    ui.node_mut(virtual_nodes.body)
5882        .set_action("trees.virtual.scroll");
5883    tree_table_widgets(ui, body, state);
5884}
5885
5886fn tree_table_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5887    let tree_state = ext_widgets::TreeViewState::expanded(["root", "branch-a"]);
5888    let rows = tree_state.visible_items(&tree_table_items());
5889    let columns = [
5890        ext_widgets::DataTableColumn::new("name", "Name", 220.0),
5891        ext_widgets::DataTableColumn::new("kind", "Kind", 84.0),
5892        ext_widgets::DataTableColumn::new("status", "Status", 92.0),
5893    ];
5894    let mut options = ext_widgets::DataTableOptions::default()
5895        .with_row_action_prefix("trees.table")
5896        .with_cell_action_prefix("trees.table");
5897    options.layout = LayoutStyle::column()
5898        .with_width_percent(1.0)
5899        .with_height(132.0)
5900        .with_flex_shrink(0.0);
5901    ext_widgets::virtualized_data_table(
5902        ui,
5903        parent,
5904        "trees.table",
5905        &columns,
5906        ext_widgets::VirtualDataTableSpec {
5907            row_count: rows.len(),
5908            row_height: 24.0,
5909            viewport_width: 396.0,
5910            viewport_height: 96.0,
5911            scroll_offset: UiPoint::new(0.0, state.tree_virtual_scroll),
5912            overscan_rows: 1,
5913        },
5914        options,
5915        |ui, cell_parent, cell| {
5916            let value = rows
5917                .get(cell.row)
5918                .map(|item| tree_table_cell_value(item, cell.column))
5919                .unwrap_or_default();
5920            widgets::label(
5921                ui,
5922                cell_parent,
5923                format!("trees.table.cell.{}.{}.label", cell.row, cell.column),
5924                value,
5925                text(12.0, color(220, 228, 238)),
5926                LayoutStyle::new().with_width_percent(1.0),
5927            );
5928        },
5929    );
5930}
5931
5932fn tree_table_cell_value(item: &ext_widgets::TreeVisibleItem, column: usize) -> String {
5933    match column {
5934        0 => format!("{}{}", "  ".repeat(item.depth), item.label),
5935        1 => {
5936            if item.has_children() {
5937                "Folder".to_owned()
5938            } else {
5939                "File".to_owned()
5940            }
5941        }
5942        _ => {
5943            if item.disabled {
5944                "Locked".to_owned()
5945            } else if item.expanded {
5946                "Expanded".to_owned()
5947            } else {
5948                "Ready".to_owned()
5949            }
5950        }
5951    }
5952}
5953
5954fn tab_split_dock_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5955    let body = section_with_min_viewport(
5956        ui,
5957        parent,
5958        "layout_widgets",
5959        "Dock workspace",
5960        UiSize::new(546.0, 360.0),
5961    );
5962    let shell = ui.add_child(
5963        body,
5964        UiNode::container(
5965            "layout_widgets.dock_shell",
5966            LayoutStyle::column()
5967                .with_width_percent(1.0)
5968                .with_height(360.0)
5969                .with_flex_shrink(0.0),
5970        )
5971        .with_visual(UiVisual::panel(
5972            color(13, 17, 23),
5973            Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
5974            0.0,
5975        )),
5976    );
5977
5978    let mut panels = base_layout_dock_panels();
5979    state.layout_dock.apply_order_to_panels(&mut panels);
5980    state.layout_dock.apply_visibility_to_panels(&mut panels);
5981
5982    let mut drawer_options = ext_widgets::DockDrawerRailOptions::default();
5983    drawer_options.layout = LayoutStyle::row()
5984        .with_width_percent(1.0)
5985        .with_height(34.0)
5986        .with_padding(4.0)
5987        .with_gap(4.0);
5988    ext_widgets::dock_drawer_rail(
5989        ui,
5990        shell,
5991        "layout_widgets.dock.drawers",
5992        &[
5993            ext_widgets::DockDrawerDescriptor::new(
5994                "inspector",
5995                "Inspector",
5996                "inspector",
5997                ext_widgets::DockSide::Left,
5998            )
5999            .open(!state.layout_dock.is_hidden("inspector"))
6000            .with_action("layout_widgets.drawer.inspector"),
6001            ext_widgets::DockDrawerDescriptor::new(
6002                "assets",
6003                "Assets",
6004                "assets",
6005                ext_widgets::DockSide::Right,
6006            )
6007            .open(!state.layout_dock.is_hidden("assets"))
6008            .with_action("layout_widgets.drawer.assets"),
6009        ],
6010        drawer_options,
6011    );
6012
6013    let mut options = ext_widgets::DockWorkspaceOptions::default();
6014    options.layout = LayoutStyle::column()
6015        .with_width_percent(1.0)
6016        .with_height(0.0)
6017        .with_flex_grow(1.0);
6018    options.show_titles = false;
6019    options.panel_visual = UiVisual::panel(
6020        color(18, 22, 29),
6021        Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
6022        0.0,
6023    );
6024    options.center_visual = UiVisual::panel(
6025        color(15, 19, 25),
6026        Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
6027        0.0,
6028    );
6029
6030    ext_widgets::dock_workspace(
6031        ui,
6032        shell,
6033        "layout_widgets.dock",
6034        &panels,
6035        options,
6036        |ui, parent, panel| match panel.id.as_str() {
6037            "inspector" => egui_panel_contents(
6038                ui,
6039                parent,
6040                "layout.inspector",
6041                "Inspector",
6042                state.layout_inspector_scroll,
6043            ),
6044            "assets" => egui_panel_contents(
6045                ui,
6046                parent,
6047                "layout.assets",
6048                "Assets",
6049                state.layout_assets_scroll,
6050            ),
6051            _ => dock_document_panel(ui, parent, state),
6052        },
6053    );
6054
6055    if let Some(floating) = state.layout_dock.floating_panel("inspector") {
6056        let floating_panel = ui.add_child(
6057            shell,
6058            UiNode::container(
6059                "layout_widgets.floating.inspector",
6060                operad::layout::absolute(
6061                    floating.rect.x,
6062                    floating.rect.y,
6063                    floating.rect.width,
6064                    floating.rect.height,
6065                ),
6066            )
6067            .with_visual(UiVisual::panel(
6068                color(18, 22, 29),
6069                Some(StrokeStyle::new(color(86, 102, 124), 1.0)),
6070                4.0,
6071            )),
6072        );
6073        egui_panel_contents(
6074            ui,
6075            floating_panel,
6076            "layout.inspector_floating",
6077            "Inspector",
6078            state.layout_inspector_scroll,
6079        );
6080    }
6081}
6082
6083fn base_layout_dock_panels() -> Vec<ext_widgets::DockPanelDescriptor> {
6084    vec![
6085        ext_widgets::DockPanelDescriptor::new(
6086            "inspector",
6087            "Inspector",
6088            ext_widgets::DockSide::Left,
6089            120.0,
6090        )
6091        .with_min_size(104.0)
6092        .resizable(true),
6093        ext_widgets::DockPanelDescriptor::center("document", "Document"),
6094        ext_widgets::DockPanelDescriptor::new(
6095            "assets",
6096            "Assets",
6097            ext_widgets::DockSide::Right,
6098            104.0,
6099        )
6100        .with_min_size(94.0)
6101        .resizable(true),
6102    ]
6103}
6104
6105fn dock_document_panel(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6106    let content = ui.add_child(
6107        parent,
6108        UiNode::container(
6109            "layout_widgets.document.content",
6110            LayoutStyle::column()
6111                .with_width_percent(1.0)
6112                .with_height_percent(1.0)
6113                .with_padding(8.0)
6114                .with_gap(8.0),
6115        ),
6116    );
6117
6118    let controls = wrapping_row(ui, content, "layout_widgets.dock.controls", 8.0);
6119    let (action, label) = if state.layout_dock.is_floating("inspector") {
6120        ("layout_widgets.dock_inspector", "Dock inspector")
6121    } else {
6122        ("layout_widgets.float_inspector", "Float inspector")
6123    };
6124    let mut float_button = widgets::ButtonOptions::new(
6125        LayoutStyle::new()
6126            .with_width(132.0)
6127            .with_height(28.0)
6128            .with_flex_shrink(0.0),
6129    )
6130    .with_action(action);
6131    float_button.visual = button_visual(40, 52, 68);
6132    float_button.hovered_visual = Some(button_visual(54, 70, 92));
6133    float_button.text_style = text(12.0, color(232, 238, 248));
6134    widgets::button(
6135        ui,
6136        controls,
6137        "layout_widgets.dock.float_inspector",
6138        label,
6139        float_button,
6140    );
6141
6142    let mut before_button = widgets::ButtonOptions::new(
6143        LayoutStyle::new()
6144            .with_width(136.0)
6145            .with_height(28.0)
6146            .with_flex_shrink(0.0),
6147    )
6148    .with_action("layout_widgets.reorder.assets.before.inspector");
6149    before_button.visual = button_visual(34, 44, 58);
6150    before_button.hovered_visual = Some(button_visual(48, 64, 84));
6151    before_button.text_style = text(12.0, color(232, 238, 248));
6152    widgets::button(
6153        ui,
6154        controls,
6155        "layout_widgets.dock.assets_before_inspector",
6156        "Assets before",
6157        before_button,
6158    );
6159
6160    let mut after_button = widgets::ButtonOptions::new(
6161        LayoutStyle::new()
6162            .with_width(126.0)
6163            .with_height(28.0)
6164            .with_flex_shrink(0.0),
6165    )
6166    .with_action("layout_widgets.reorder.assets.after.inspector");
6167    after_button.visual = button_visual(34, 44, 58);
6168    after_button.hovered_visual = Some(button_visual(48, 64, 84));
6169    after_button.text_style = text(12.0, color(232, 238, 248));
6170    widgets::button(
6171        ui,
6172        controls,
6173        "layout_widgets.dock.assets_after_inspector",
6174        "Assets after",
6175        after_button,
6176    );
6177
6178    let zones = ext_widgets::dock_workspace::dock_workspace_drop_zones(
6179        "layout_widgets.dock",
6180        UiRect::new(0.0, 0.0, 520.0, 340.0),
6181        ext_widgets::DockWorkspaceDragOptions::default()
6182            .allowed_sides([
6183                ext_widgets::DockSide::Left,
6184                ext_widgets::DockSide::Right,
6185                ext_widgets::DockSide::Center,
6186            ])
6187            .edge_thickness(44.0),
6188    );
6189    let targets = wrapping_row(ui, content, "layout_widgets.dock.targets", 6.0);
6190    for zone in zones {
6191        dock_drop_target_chip(ui, targets, &zone);
6192    }
6193
6194    let mut panels = base_layout_dock_panels();
6195    state.layout_dock.apply_order_to_panels(&mut panels);
6196    let reorder_targets: Vec<_> = [
6197        ext_widgets::DockSide::Left,
6198        ext_widgets::DockSide::Right,
6199        ext_widgets::DockSide::Center,
6200    ]
6201    .into_iter()
6202    .flat_map(|side| {
6203        ext_widgets::dock_workspace::dock_panel_reorder_drop_targets(
6204            "layout_widgets.dock",
6205            &panels,
6206            side,
6207            UiRect::new(0.0, 0.0, 180.0, 120.0),
6208            ext_widgets::DockWorkspaceReorderOptions::default().target_thickness(20.0),
6209        )
6210    })
6211    .collect();
6212    let reorder_row = wrapping_row(ui, content, "layout_widgets.dock.reorder_targets", 6.0);
6213    for target in reorder_targets {
6214        dock_reorder_target_chip(ui, reorder_row, &target);
6215    }
6216
6217    let tabs = [
6218        ext_widgets::TabItem::new("preview", "Preview"),
6219        ext_widgets::TabItem::new("log", "Output").dirty(),
6220        ext_widgets::TabItem::new("settings", "Settings").closable(),
6221    ];
6222    let mut tab_options = ext_widgets::TabGroupOptions::default();
6223    tab_options.layout = LayoutStyle::column()
6224        .with_width_percent(1.0)
6225        .with_height(0.0)
6226        .with_flex_grow(1.0);
6227    tab_options.tab_strip_height = 30.0;
6228    tab_options.min_tab_width = 92.0;
6229    tab_options.text_style = text(12.0, color(226, 234, 246));
6230    tab_options.muted_text_style = text(12.0, color(150, 162, 178));
6231    ext_widgets::tab_group(
6232        ui,
6233        content,
6234        "layout_widgets.document.tabs",
6235        &tabs,
6236        ext_widgets::TabGroupState::selected(0),
6237        tab_options,
6238        |ui, panel, _index| {
6239            widgets::label(
6240                ui,
6241                panel,
6242                "layout_widgets.document.tabs.preview.body",
6243                "Workspace preview",
6244                text(12.0, color(190, 202, 218)),
6245                LayoutStyle::new().with_width_percent(1.0).with_height(26.0),
6246            );
6247        },
6248    );
6249}
6250
6251fn dock_drop_target_chip(
6252    ui: &mut UiDocument,
6253    parent: UiNodeId,
6254    zone: &ext_widgets::DockWorkspaceDropZone,
6255) -> UiNodeId {
6256    let chip = ui.add_child(
6257        parent,
6258        UiNode::container(
6259            format!("{}.chip", zone.target.id.as_str()),
6260            LayoutStyle::row()
6261                .with_width(78.0)
6262                .with_height(26.0)
6263                .with_padding(6.0)
6264                .with_flex_shrink(0.0),
6265        )
6266        .with_input(InputBehavior::BUTTON)
6267        .with_visual(UiVisual::panel(
6268            color(24, 32, 42),
6269            Some(StrokeStyle::new(color(78, 94, 116), 1.0)),
6270            4.0,
6271        ))
6272        .with_accessibility(zone.target.accessibility_meta()),
6273    );
6274    widgets::label(
6275        ui,
6276        chip,
6277        format!("{}.label", zone.target.id.as_str()),
6278        dock_drop_target_short_label(zone.placement),
6279        text(11.0, color(206, 216, 230)),
6280        LayoutStyle::new().with_width_percent(1.0),
6281    );
6282    chip
6283}
6284
6285fn dock_reorder_target_chip(
6286    ui: &mut UiDocument,
6287    parent: UiNodeId,
6288    target: &ext_widgets::DockPanelReorderTarget,
6289) -> UiNodeId {
6290    let chip = ui.add_child(
6291        parent,
6292        UiNode::container(
6293            format!("{}.chip", target.target.id.as_str()),
6294            LayoutStyle::row()
6295                .with_width(104.0)
6296                .with_height(26.0)
6297                .with_padding(6.0)
6298                .with_flex_shrink(0.0),
6299        )
6300        .with_input(InputBehavior::BUTTON)
6301        .with_visual(UiVisual::panel(
6302            color(22, 34, 42),
6303            Some(StrokeStyle::new(color(80, 112, 128), 1.0)),
6304            4.0,
6305        ))
6306        .with_accessibility(target.target.accessibility_meta()),
6307    );
6308    widgets::label(
6309        ui,
6310        chip,
6311        format!("{}.label", target.target.id.as_str()),
6312        dock_reorder_target_short_label(target),
6313        text(11.0, color(206, 216, 230)),
6314        LayoutStyle::new().with_width_percent(1.0),
6315    );
6316    chip
6317}
6318
6319fn dock_drop_target_short_label(placement: ext_widgets::DockDropPlacement) -> &'static str {
6320    match placement {
6321        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Left) => "Left",
6322        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Right) => "Right",
6323        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Center) => "Center",
6324        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Top) => "Top",
6325        ext_widgets::DockDropPlacement::Dock(ext_widgets::DockSide::Bottom) => "Bottom",
6326        ext_widgets::DockDropPlacement::Floating => "Float",
6327    }
6328}
6329
6330fn dock_reorder_target_short_label(target: &ext_widgets::DockPanelReorderTarget) -> String {
6331    let placement = match target.placement {
6332        ext_widgets::DockPanelReorderPlacement::Before => "Before",
6333        ext_widgets::DockPanelReorderPlacement::After => "After",
6334    };
6335    format!("{placement} {}", target.panel_id)
6336}
6337
6338fn container_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6339    let body = section(ui, parent, "containers", "Containers");
6340
6341    let frame = widgets::frame(
6342        ui,
6343        body,
6344        "containers.frame",
6345        widgets::FrameOptions::default().with_layout(
6346            LayoutStyle::column()
6347                .with_width_percent(1.0)
6348                .with_height(64.0)
6349                .with_padding(8.0)
6350                .with_gap(6.0),
6351        ),
6352    );
6353    widgets::strong_label(
6354        ui,
6355        frame,
6356        "containers.frame.title",
6357        "Frame",
6358        LayoutStyle::new().with_width_percent(1.0),
6359    );
6360    widgets::weak_label(
6361        ui,
6362        frame,
6363        "containers.frame.body",
6364        "Default framed surface with padding, stroke, and clipping.",
6365        LayoutStyle::new().with_width_percent(1.0),
6366    );
6367
6368    let group = widgets::group(ui, body, "containers.group");
6369    widgets::label(
6370        ui,
6371        group,
6372        "containers.group.label",
6373        "Group helper",
6374        text(12.0, color(220, 228, 238)),
6375        LayoutStyle::new().with_width_percent(1.0),
6376    );
6377    let generic_panel = widgets::panel(
6378        ui,
6379        body,
6380        "containers.panel",
6381        widgets::PanelOptions::group().with_layout(
6382            LayoutStyle::column()
6383                .with_width_percent(1.0)
6384                .with_height(44.0)
6385                .with_padding(8.0),
6386        ),
6387    );
6388    widgets::label(
6389        ui,
6390        generic_panel,
6391        "containers.panel.label",
6392        "Generic panel",
6393        text(12.0, color(220, 228, 238)),
6394        LayoutStyle::new().with_width_percent(1.0),
6395    );
6396    let group_panel = widgets::group_panel(ui, body, "containers.group_panel");
6397    widgets::label(
6398        ui,
6399        group_panel,
6400        "containers.group_panel.label",
6401        "Group panel",
6402        text(12.0, color(220, 228, 238)),
6403        LayoutStyle::new().with_width_percent(1.0),
6404    );
6405
6406    widgets::separator(
6407        ui,
6408        body,
6409        "containers.separator",
6410        widgets::SeparatorOptions::default(),
6411    );
6412    widgets::spacer(
6413        ui,
6414        body,
6415        "containers.spacer",
6416        LayoutStyle::new()
6417            .with_width_percent(1.0)
6418            .with_height(8.0)
6419            .with_flex_shrink(0.0),
6420    );
6421
6422    let grid = widgets::grid::grid(
6423        ui,
6424        body,
6425        "containers.grid",
6426        widgets::grid::GridOptions::default().with_layout(
6427            LayoutStyle::column()
6428                .with_width_percent(1.0)
6429                .with_height(78.0)
6430                .with_gap(4.0),
6431        ),
6432    );
6433    for row_index in 0..2 {
6434        let row = widgets::grid::grid_row(
6435            ui,
6436            grid,
6437            format!("containers.grid.row.{row_index}"),
6438            widgets::grid::GridRowOptions::default(),
6439        );
6440        for column_index in 0..3 {
6441            widgets::grid::grid_text_cell(
6442                ui,
6443                row,
6444                format!("containers.grid.row.{row_index}.cell.{column_index}"),
6445                format!("R{} C{}", row_index + 1, column_index + 1),
6446                widgets::grid::GridCellOptions {
6447                    text_style: text(12.0, color(214, 224, 238)),
6448                    ..Default::default()
6449                },
6450            );
6451        }
6452    }
6453
6454    widgets::sides(
6455        ui,
6456        body,
6457        "containers.sides",
6458        widgets::SidesOptions::default()
6459            .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
6460            .with_gap(8.0)
6461            .with_visual(UiVisual::panel(
6462                color(20, 25, 32),
6463                Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
6464                4.0,
6465            )),
6466        |ui, left| {
6467            widgets::label(
6468                ui,
6469                left,
6470                "containers.sides.left.label",
6471                "Left side",
6472                text(12.0, color(220, 228, 238)),
6473                LayoutStyle::new().with_width_percent(1.0),
6474            );
6475        },
6476        |ui, right| {
6477            widgets::label(
6478                ui,
6479                right,
6480                "containers.sides.right.label",
6481                "Right side",
6482                text(12.0, color(220, 228, 238)),
6483                LayoutStyle::new().with_width_percent(1.0),
6484            );
6485        },
6486    );
6487
6488    widgets::columns(
6489        ui,
6490        body,
6491        "containers.columns",
6492        3,
6493        widgets::ColumnsOptions::default()
6494            .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
6495            .with_gap(8.0),
6496        |ui, column, index| {
6497            widgets::label(
6498                ui,
6499                column,
6500                format!("containers.columns.{index}.label"),
6501                format!("Column {}", index + 1),
6502                text(12.0, color(220, 228, 238)),
6503                LayoutStyle::new().with_width_percent(1.0),
6504            );
6505        },
6506    );
6507
6508    let indented = widgets::indented_section(
6509        ui,
6510        body,
6511        "containers.indented",
6512        widgets::IndentOptions::default().with_amount(24.0),
6513    );
6514    widgets::label(
6515        ui,
6516        indented,
6517        "containers.indented.label",
6518        "Indented section",
6519        text(12.0, color(196, 210, 230)),
6520        LayoutStyle::new().with_width_percent(1.0),
6521    );
6522
6523    widgets::resize_container(
6524        ui,
6525        body,
6526        "containers.resize_container",
6527        widgets::ResizeContainerOptions::default().with_layout(
6528            LayoutStyle::column()
6529                .with_width_percent(1.0)
6530                .with_height(92.0)
6531                .with_flex_shrink(0.0),
6532        ),
6533        |ui, content| {
6534            widgets::label(
6535                ui,
6536                content,
6537                "containers.resize_container.label",
6538                "Resize container",
6539                text(12.0, color(220, 228, 238)),
6540                LayoutStyle::new().with_width_percent(1.0),
6541            );
6542        },
6543    );
6544    widgets::container::resize_handle(
6545        ui,
6546        body,
6547        "containers.resize_handle",
6548        widgets::container::ResizeHandleOptions::default()
6549            .with_layout(LayoutStyle::size(20.0, 20.0))
6550            .accessibility_label("Inline resize handle"),
6551    );
6552
6553    widgets::scene(
6554        ui,
6555        body,
6556        "containers.scene",
6557        vec![
6558            ScenePrimitive::Rect(
6559                PaintRect::solid(UiRect::new(8.0, 12.0, 108.0, 46.0), color(48, 112, 184))
6560                    .stroke(AlignedStroke::inside(StrokeStyle::new(
6561                        color(132, 174, 222),
6562                        1.0,
6563                    )))
6564                    .corner_radii(CornerRadii::uniform(6.0)),
6565            ),
6566            ScenePrimitive::Circle {
6567                center: UiPoint::new(150.0, 35.0),
6568                radius: 22.0,
6569                fill: color(111, 203, 159),
6570                stroke: Some(StrokeStyle::new(color(176, 236, 206), 1.0)),
6571            },
6572            ScenePrimitive::Line {
6573                from: UiPoint::new(188.0, 18.0),
6574                to: UiPoint::new(238.0, 52.0),
6575                stroke: StrokeStyle::new(color(232, 186, 88), 3.0),
6576            },
6577        ],
6578        widgets::SceneOptions::default()
6579            .with_layout(LayoutStyle::new().with_width(260.0).with_height(70.0))
6580            .accessibility_label("Scene primitives"),
6581    );
6582
6583    let panel_shell = widgets::frame(
6584        ui,
6585        body,
6586        "containers.panels",
6587        widgets::FrameOptions::default().with_layout(
6588            LayoutStyle::column()
6589                .with_width_percent(1.0)
6590                .with_height(160.0)
6591                .with_padding(0.0)
6592                .with_gap(0.0),
6593        ),
6594    );
6595    let top = widgets::top_panel(ui, panel_shell, "containers.panels.top", 28.0);
6596    widgets::label(
6597        ui,
6598        top,
6599        "containers.panels.top.label",
6600        "Top panel",
6601        text(12.0, color(220, 228, 238)),
6602        LayoutStyle::new().with_width_percent(1.0),
6603    );
6604    let middle = row(ui, panel_shell, "containers.panels.middle", 0.0);
6605    let left = widgets::side_panel(
6606        ui,
6607        middle,
6608        "containers.panels.side",
6609        widgets::SidePanelSide::Left,
6610        90.0,
6611    );
6612    widgets::label(
6613        ui,
6614        left,
6615        "containers.panels.side.label",
6616        "Side",
6617        text(12.0, color(220, 228, 238)),
6618        LayoutStyle::new().with_width_percent(1.0),
6619    );
6620    let left = widgets::left_panel(ui, middle, "containers.panels.left", 90.0);
6621    widgets::label(
6622        ui,
6623        left,
6624        "containers.panels.left.label",
6625        "Left",
6626        text(12.0, color(220, 228, 238)),
6627        LayoutStyle::new().with_width_percent(1.0),
6628    );
6629    let center = widgets::central_panel(ui, middle, "containers.panels.center");
6630    widgets::label(
6631        ui,
6632        center,
6633        "containers.panels.center.label",
6634        "Central panel",
6635        text(12.0, color(220, 228, 238)),
6636        LayoutStyle::new().with_width_percent(1.0),
6637    );
6638    let right = widgets::right_panel(ui, middle, "containers.panels.right", 110.0);
6639    widgets::label(
6640        ui,
6641        right,
6642        "containers.panels.right.label",
6643        "Right",
6644        text(12.0, color(220, 228, 238)),
6645        LayoutStyle::new().with_width_percent(1.0),
6646    );
6647    let bottom = widgets::bottom_panel(ui, panel_shell, "containers.panels.bottom", 28.0);
6648    widgets::label(
6649        ui,
6650        bottom,
6651        "containers.panels.bottom.label",
6652        "Bottom panel",
6653        text(12.0, color(220, 228, 238)),
6654        LayoutStyle::new().with_width_percent(1.0),
6655    );
6656
6657    widgets::scroll_container(
6658        ui,
6659        body,
6660        "containers.scroll_area_with_bars",
6661        state.containers_scroll,
6662        widgets::ScrollContainerOptions::default()
6663            .with_axes(ScrollAxes::BOTH)
6664            .with_layout(LayoutStyle::column().with_width(300.0).with_height(116.0)),
6665        |ui, viewport| {
6666            for index in 0..5 {
6667                widgets::label(
6668                    ui,
6669                    viewport,
6670                    format!("containers.scroll_area_with_bars.row.{index}"),
6671                    format!("Scrollable row {}", index + 1),
6672                    text(12.0, color(200, 212, 228)),
6673                    LayoutStyle::new()
6674                        .with_width(420.0)
6675                        .with_height(28.0)
6676                        .with_flex_shrink(0.0),
6677                );
6678            }
6679        },
6680    );
6681
6682    let area_host = ui.add_child(
6683        body,
6684        UiNode::container(
6685            "containers.area.host",
6686            LayoutStyle::new()
6687                .with_width_percent(1.0)
6688                .with_height(82.0)
6689                .with_flex_shrink(0.0),
6690        )
6691        .with_visual(UiVisual::panel(
6692            color(17, 20, 25),
6693            Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
6694            4.0,
6695        )),
6696    );
6697    widgets::container::area(
6698        ui,
6699        area_host,
6700        "containers.area",
6701        widgets::container::AreaOptions::new(UiRect::new(14.0, 14.0, 180.0, 44.0))
6702            .with_visual(UiVisual::panel(color(39, 72, 109), None, 4.0))
6703            .accessibility_label("Absolute positioned area"),
6704        |ui, area| {
6705            widgets::label(
6706                ui,
6707                area,
6708                "containers.area.label",
6709                "Area",
6710                text(12.0, color(238, 244, 252)),
6711                LayoutStyle::new().with_width_percent(1.0),
6712            );
6713        },
6714    );
6715}
Source

pub fn with_vertical_scrollbar(self, options: ScrollbarOptions) -> Self

Examples found in repository?
examples/showcase.rs (lines 3080-3083)
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}
Source

pub fn with_horizontal_scrollbar(self, options: ScrollbarOptions) -> Self

Source

pub const fn with_scrollbar_thickness(self, thickness: f32) -> Self

Examples found in repository?
examples/showcase.rs (line 3077)
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}
Source

pub const fn with_gap(self, gap: f32) -> Self

Examples found in repository?
examples/showcase.rs (line 3078)
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}
Source

pub const fn with_scrollbar_visibility( self, visibility: ScrollbarVisibility, ) -> Self

Source

pub fn with_action_prefix(self, prefix: impl Into<String>) -> Self

Examples found in repository?
examples/showcase.rs (line 3079)
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}
Source

pub const fn without_actions(self) -> Self

Source

pub fn with_accessibility_label(self, label: impl Into<String>) -> Self

Trait Implementations§

Source§

impl Clone for ScrollContainerOptions

Source§

fn clone(&self) -> ScrollContainerOptions

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 ScrollContainerOptions

Source§

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

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

impl Default for ScrollContainerOptions

Source§

fn default() -> Self

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

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