Skip to main content

AlignedStroke

Struct AlignedStroke 

Source
pub struct AlignedStroke {
    pub style: StrokeStyle,
    pub alignment: StrokeAlignment,
}

Fields§

§style: StrokeStyle§alignment: StrokeAlignment

Implementations§

Source§

impl AlignedStroke

Source

pub const fn new(style: StrokeStyle, alignment: StrokeAlignment) -> Self

Source

pub const fn inside(style: StrokeStyle) -> Self

Examples found in repository?
examples/showcase.rs (lines 5189-5192)
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}
6716
6717fn form_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6718    let body = section_with_min_viewport(ui, parent, "forms", "Forms", UiSize::new(390.0, 0.0));
6719    let section = widgets::form_section(
6720        ui,
6721        body,
6722        "forms.profile",
6723        Some("Profile".to_string()),
6724        widgets::FormSectionOptions::default().with_layout(
6725            LayoutStyle::column()
6726                .with_width_percent(1.0)
6727                .with_padding(12.0)
6728                .with_gap(10.0),
6729        ),
6730    );
6731    let status_row = wrapping_row(ui, section.root, "forms.profile.status_flags", 6.0);
6732    form_status_chip(
6733        ui,
6734        status_row,
6735        "forms.profile.status.dirty",
6736        "dirty",
6737        state.form.dirty,
6738    );
6739    form_status_chip(
6740        ui,
6741        status_row,
6742        "forms.profile.status.pending",
6743        "pending",
6744        state.form.pending,
6745    );
6746    form_status_chip(
6747        ui,
6748        status_row,
6749        "forms.profile.status.submitted",
6750        "submitted",
6751        state.form.submitted,
6752    );
6753
6754    let mut name_options = widgets::FormRowOptions::default().required();
6755    if state.form_name_text.text().trim().is_empty() {
6756        name_options = name_options.invalid("Name is required");
6757    }
6758    let name = widgets::form_row(ui, section.root, "forms.profile.name", name_options);
6759    widgets::field_label(
6760        ui,
6761        name,
6762        "forms.profile.name.label",
6763        "Name",
6764        widgets::FieldLabelOptions::default().required(),
6765    );
6766    form_text_field(
6767        ui,
6768        name,
6769        "forms.profile.name.input",
6770        &state.form_name_text,
6771        FocusedTextInput::FormName,
6772        state,
6773    );
6774    if state.form_name_text.text().trim().is_empty() {
6775        widgets::field_validation_message(
6776            ui,
6777            name,
6778            "forms.profile.name.validation",
6779            ValidationMessage::error("Name is required"),
6780            widgets::ValidationMessageOptions::default(),
6781        );
6782    } else {
6783        widgets::field_help_text(
6784            ui,
6785            name,
6786            "forms.profile.name.help",
6787            "Shown in window titles and project lists.",
6788            widgets::FieldHelpOptions::default(),
6789        );
6790    }
6791
6792    let mut email_options = widgets::FormRowOptions::default().required();
6793    if !profile_email_valid(state.form_email_text.text()) {
6794        email_options = email_options.invalid("Use a complete email address");
6795    }
6796    let email = widgets::form_row(ui, section.root, "forms.profile.email", email_options);
6797    widgets::field_label(
6798        ui,
6799        email,
6800        "forms.profile.email.label",
6801        "Email",
6802        widgets::FieldLabelOptions::default().required(),
6803    );
6804    form_text_field(
6805        ui,
6806        email,
6807        "forms.profile.email.input",
6808        &state.form_email_text,
6809        FocusedTextInput::FormEmail,
6810        state,
6811    );
6812    if profile_email_valid(state.form_email_text.text()) {
6813        widgets::field_help_text(
6814            ui,
6815            email,
6816            "forms.profile.email.help",
6817            "Used for workspace invites and notifications.",
6818            widgets::FieldHelpOptions::default(),
6819        );
6820    } else {
6821        widgets::field_validation_message(
6822            ui,
6823            email,
6824            "forms.profile.email.validation",
6825            ValidationMessage::error("Use a complete email address"),
6826            widgets::ValidationMessageOptions::default(),
6827        );
6828    }
6829
6830    let role = widgets::form_row(
6831        ui,
6832        section.root,
6833        "forms.profile.role",
6834        widgets::FormRowOptions::default(),
6835    );
6836    widgets::field_label(
6837        ui,
6838        role,
6839        "forms.profile.role.label",
6840        "Role",
6841        widgets::FieldLabelOptions::default(),
6842    );
6843    form_text_field(
6844        ui,
6845        role,
6846        "forms.profile.role.input",
6847        &state.form_role_text,
6848        FocusedTextInput::FormRole,
6849        state,
6850    );
6851    widgets::field_validation_message(
6852        ui,
6853        role,
6854        "forms.profile.role.help",
6855        if state.form_role_text.text().trim().is_empty() {
6856            ValidationMessage::warning("Role can be added later")
6857        } else {
6858            ValidationMessage::info(
6859                "Form rows compose labels, controls, help, and validation text.",
6860            )
6861        },
6862        widgets::ValidationMessageOptions::default(),
6863    );
6864
6865    let newsletter = widgets::form_row(
6866        ui,
6867        section.root,
6868        "forms.profile.newsletter",
6869        widgets::FormRowOptions::default().with_accessibility_label("Newsletter preference"),
6870    );
6871    let mut newsletter_options =
6872        widgets::CheckboxOptions::default().with_action("forms.profile.newsletter.toggle");
6873    newsletter_options.layout = LayoutStyle::new().with_width_percent(1.0).with_height(30.0);
6874    newsletter_options.text_style = text(12.0, color(220, 228, 238));
6875    widgets::checkbox(
6876        ui,
6877        newsletter,
6878        "forms.profile.newsletter.input",
6879        "Send release notes",
6880        state.form_newsletter,
6881        newsletter_options,
6882    );
6883    widgets::field_help_text(
6884        ui,
6885        newsletter,
6886        "forms.profile.newsletter.help",
6887        "Checkboxes participate in the same form state as text fields.",
6888        widgets::FieldHelpOptions::default(),
6889    );
6890
6891    widgets::form_error_summary(
6892        ui,
6893        section.root,
6894        "forms.profile.errors",
6895        &state.form,
6896        widgets::FormErrorSummaryOptions::default(),
6897    );
6898    let action_layout = Layout::row()
6899        .size(LayoutSize::new(
6900            LayoutDimension::percent(1.0),
6901            LayoutDimension::Auto,
6902        ))
6903        .gap(LayoutGap::points(8.0, 8.0))
6904        .flex_wrap(LayoutFlexWrap::Wrap)
6905        .to_layout_style();
6906    widgets::form_action_buttons(
6907        ui,
6908        section.root,
6909        "forms.profile.actions",
6910        &state.form,
6911        widgets::FormActionButtonsOptions::default()
6912            .with_layout(action_layout)
6913            .include_reset(true)
6914            .with_action_prefix("forms.profile"),
6915    );
6916    widgets::label(
6917        ui,
6918        section.root,
6919        "forms.profile.status",
6920        format!("Status: {}", state.form_status),
6921        text(11.0, color(154, 166, 184)),
6922        LayoutStyle::new().with_width_percent(1.0),
6923    );
6924}
6925
6926fn overlay_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
6927    let body =
6928        section_with_min_viewport(ui, parent, "overlays", "Overlays", UiSize::new(420.0, 0.0));
6929    let header = widgets::collapsing_header(
6930        ui,
6931        body,
6932        "overlays.collapsing",
6933        "Collapsing header",
6934        widgets::CollapsingHeaderOptions::default()
6935            .expanded(state.overlay_expanded)
6936            .with_toggle_action("overlays.collapsing.toggle"),
6937    );
6938    if let Some(panel) = header.body {
6939        widgets::label(
6940            ui,
6941            panel,
6942            "overlays.collapsing.body",
6943            "Expanded content lives under the header and remains part of normal layout.",
6944            text(12.0, color(196, 210, 230)),
6945            LayoutStyle::new().with_width_percent(1.0),
6946        );
6947    }
6948
6949    let controls = wrapping_row(ui, body, "overlays.controls", 8.0);
6950    button(
6951        ui,
6952        controls,
6953        "overlays.popup.toggle",
6954        if state.overlay_popup_open {
6955            "Close popup"
6956        } else {
6957            "Open popup"
6958        },
6959        "overlays.popup.toggle",
6960        button_visual(48, 112, 184),
6961    );
6962    button(
6963        ui,
6964        controls,
6965        "overlays.modal.open",
6966        "Open modal",
6967        "overlays.modal.open",
6968        button_visual(58, 78, 96),
6969    );
6970
6971    let tooltip = TooltipContent::new("Tooltip")
6972        .body("Tooltip boxes are overlay surfaces with title, body, and shortcut text.")
6973        .shortcut_label("Ctrl+K")
6974        .disabled_reason("Disabled reasons can be announced without changing the trigger.");
6975    let mut tooltip_options = widgets::TooltipBoxOptions::default()
6976        .with_layout(
6977            LayoutStyle::column()
6978                .with_width(280.0)
6979                .with_padding(8.0)
6980                .with_gap(4.0),
6981        )
6982        .with_animation(None);
6983    tooltip_options.layer = UiLayer::AppContent;
6984    tooltip_options.z_index = 0;
6985    widgets::tooltip_box(ui, body, "overlays.tooltip", tooltip, tooltip_options);
6986
6987    let tooltip_anchor = row(ui, body, "overlays.tooltip_anchor", 8.0);
6988    widgets::label(
6989        ui,
6990        tooltip_anchor,
6991        "overlays.tooltip_anchor.label",
6992        "Tooltip placement clamps to its viewport.",
6993        text(12.0, color(166, 176, 190)),
6994        LayoutStyle::new().with_width_percent(1.0),
6995    );
6996    let clamped_rect = widgets::tooltip::tooltip_rect(
6997        UiRect::new(328.0, 12.0, 54.0, 24.0),
6998        UiSize::new(176.0, 58.0),
6999        UiRect::new(0.0, 0.0, 420.0, 190.0),
7000        TooltipPlacement::Right,
7001        8.0,
7002        None,
7003    );
7004    let clamped_preview = ui.add_child(
7005        body,
7006        UiNode::container(
7007            "overlays.tooltip_rect.preview",
7008            LayoutStyle::new()
7009                .with_width_percent(1.0)
7010                .with_height(78.0)
7011                .with_flex_shrink(0.0),
7012        )
7013        .with_visual(UiVisual::panel(
7014            color(12, 16, 22),
7015            Some(StrokeStyle::new(color(52, 64, 80), 1.0)),
7016            4.0,
7017        )),
7018    );
7019    ui.add_child(
7020        clamped_preview,
7021        UiNode::scene(
7022            "overlays.tooltip_rect.scene",
7023            vec![
7024                ScenePrimitive::Rect(
7025                    PaintRect::solid(UiRect::new(328.0, 12.0, 54.0, 24.0), color(48, 112, 184))
7026                        .corner_radii(CornerRadii::uniform(3.0)),
7027                ),
7028                ScenePrimitive::Rect(
7029                    PaintRect::solid(clamped_rect, color(24, 29, 38))
7030                        .stroke(AlignedStroke::inside(StrokeStyle::new(
7031                            color(92, 106, 128),
7032                            1.0,
7033                        )))
7034                        .corner_radii(CornerRadii::uniform(4.0)),
7035                ),
7036            ],
7037            LayoutStyle::new()
7038                .with_width_percent(1.0)
7039                .with_height_percent(1.0),
7040        ),
7041    );
7042
7043    if state.overlay_popup_open {
7044        let popup = ext_widgets::popup_panel(
7045            ui,
7046            parent,
7047            "overlays.popup_panel",
7048            UiRect::new(18.0, 150.0, 220.0, 112.0),
7049            ext_widgets::PopupOptions {
7050                z_index: 20,
7051                portal: UiPortalTarget::Parent,
7052                accessibility: Some(
7053                    AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup"),
7054                ),
7055                ..Default::default()
7056            },
7057        );
7058        let popup_body = ui.add_child(
7059            popup,
7060            UiNode::container(
7061                "overlays.popup_panel.body",
7062                LayoutStyle::column()
7063                    .with_width_percent(1.0)
7064                    .with_height_percent(1.0)
7065                    .with_padding(10.0)
7066                    .with_gap(6.0),
7067            ),
7068        );
7069        let popup_header = row(ui, popup_body, "overlays.popup_panel.header", 8.0);
7070        widgets::label(
7071            ui,
7072            popup_header,
7073            "overlays.popup_panel.label",
7074            "Popup panel",
7075            text(12.0, color(220, 228, 238)),
7076            LayoutStyle::new().with_width_percent(1.0),
7077        );
7078        let mut close = widgets::ButtonOptions::new(LayoutStyle::size(26.0, 22.0))
7079            .with_action("overlays.popup.close");
7080        close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
7081        close.hovered_visual = Some(button_visual(54, 70, 92));
7082        close.text_style = text(12.0, color(220, 228, 238));
7083        widgets::button(ui, popup_header, "overlays.popup_panel.close", "x", close);
7084        widgets::label(
7085            ui,
7086            popup_body,
7087            "overlays.popup_panel.body_text",
7088            "Popup content is conditionally rendered.",
7089            text(11.0, color(196, 210, 230)),
7090            LayoutStyle::new().with_width_percent(1.0),
7091        );
7092    }
7093
7094    if state.overlay_modal_open {
7095        let modal = widgets::modal_dialog(
7096            ui,
7097            parent,
7098            "overlays.modal",
7099            "Modal dialog",
7100            widgets::ModalDialogOptions::default()
7101                .with_size(320.0, 180.0)
7102                .with_close_action("overlays.modal.close")
7103                .with_dismissal(ext_widgets::DialogDismissal::MODAL)
7104                .with_focus_restore(FocusRestoreTarget::Previous),
7105        );
7106        widgets::label(
7107            ui,
7108            modal.body,
7109            "overlays.modal.body.text",
7110            "Modal dialogs are portaled to the application overlay, include a scrim, and trap focus.",
7111            text(12.0, color(220, 228, 238)),
7112            LayoutStyle::new().with_width_percent(1.0),
7113        );
7114        button(
7115            ui,
7116            modal.body,
7117            "overlays.modal.body.close",
7118            "Close modal",
7119            "overlays.modal.close",
7120            button_visual(48, 112, 184),
7121        );
7122    }
7123}
7124
7125fn drag_drop_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7126    let body = section_with_min_viewport(
7127        ui,
7128        parent,
7129        "drag_drop",
7130        "Drag and drop",
7131        UiSize::new(420.0, 0.0),
7132    );
7133    widgets::label(
7134        ui,
7135        body,
7136        "drag_drop.sources.label",
7137        "Drag sources",
7138        text(12.0, color(166, 176, 190)),
7139        LayoutStyle::new().with_width_percent(1.0),
7140    );
7141    let sources = wrapping_row(ui, body, "drag_drop.sources", 8.0);
7142    widgets::dnd_drag_source(
7143        ui,
7144        sources,
7145        "drag_drop.text_source",
7146        "Text payload",
7147        DragPayload::text("Operad payload"),
7148        widgets::DragSourceOptions::default()
7149            .with_layout(drag_source_layout())
7150            .with_kind(DragDropSurfaceKind::ListRow)
7151            .with_allowed_operations([DragOperation::Copy, DragOperation::Move])
7152            .with_action("drag_drop.text_source")
7153            .with_accessibility_hint("Start a text drag operation"),
7154    );
7155    widgets::dnd_drag_source(
7156        ui,
7157        sources,
7158        "drag_drop.file_source",
7159        "File payload",
7160        DragPayload::files(["/tmp/showcase.scene"]),
7161        widgets::DragSourceOptions::default()
7162            .with_layout(drag_source_layout())
7163            .with_kind(DragDropSurfaceKind::Asset)
7164            .with_drag_image_policy(widgets::DragImagePolicy::image_key(
7165                BuiltInIcon::Folder.key(),
7166                UiSize::new(120.0, 36.0),
7167                UiPoint::new(10.0, 10.0),
7168            ))
7169            .with_allowed_operations([DragOperation::Copy])
7170            .with_action("drag_drop.file_source"),
7171    );
7172    widgets::dnd_drag_source(
7173        ui,
7174        sources,
7175        "drag_drop.bytes_source",
7176        "Image bytes",
7177        DragPayload::bytes(DragBytes::new("image/png", vec![137, 80, 78, 71]).name("sprite.png")),
7178        widgets::DragSourceOptions::default()
7179            .with_layout(drag_source_layout())
7180            .with_kind(DragDropSurfaceKind::Asset)
7181            .with_action("drag_drop.bytes_source")
7182            .without_drag_image(),
7183    );
7184
7185    widgets::label(
7186        ui,
7187        body,
7188        "drag_drop.zones.label",
7189        "Drop zones",
7190        text(12.0, color(166, 176, 190)),
7191        LayoutStyle::new().with_width_percent(1.0),
7192    );
7193    let zones = wrapping_row(ui, body, "drag_drop.zones", 8.0);
7194    let accepted_options = widgets::DropZoneOptions::default()
7195        .with_layout(drop_zone_layout())
7196        .with_kind(DragDropSurfaceKind::EditorSurface)
7197        .with_accepted_payload(DropPayloadFilter::empty().text())
7198        .with_accepted_operations([DragOperation::Copy, DragOperation::Move])
7199        .with_action("drag_drop.accept_text")
7200        .with_accessibility_hint("Accepts text payloads");
7201    let accepted = widgets::dnd_drop_zone(
7202        ui,
7203        zones,
7204        "drag_drop.accept_text",
7205        "Text accepted",
7206        accepted_options.clone(),
7207    );
7208    widgets::drag_drop::dnd_apply_drop_zone_preview(
7209        ui,
7210        accepted.root,
7211        &accepted_options,
7212        widgets::drag_drop::DropZonePreviewState::Accepted,
7213    );
7214
7215    let rejected_options = widgets::DropZoneOptions::default()
7216        .with_layout(drop_zone_layout())
7217        .with_kind(DragDropSurfaceKind::Asset)
7218        .with_accepted_payload(DropPayloadFilter::empty().files())
7219        .with_action("drag_drop.files_only");
7220    let rejected = widgets::dnd_drop_zone(
7221        ui,
7222        zones,
7223        "drag_drop.files_only",
7224        "Files only",
7225        rejected_options.clone(),
7226    );
7227    widgets::drag_drop::dnd_apply_drop_zone_preview(
7228        ui,
7229        rejected.root,
7230        &rejected_options,
7231        widgets::drag_drop::DropZonePreviewState::Rejected,
7232    );
7233    let image_options = widgets::DropZoneOptions::default()
7234        .with_layout(drop_zone_layout())
7235        .with_kind(DragDropSurfaceKind::Asset)
7236        .with_accepted_payload(DropPayloadFilter::empty().mime_type("image/*"))
7237        .with_accepted_operations([DragOperation::Copy])
7238        .with_action("drag_drop.image_bytes");
7239    let image_zone = widgets::dnd_drop_zone(
7240        ui,
7241        zones,
7242        "drag_drop.image_bytes",
7243        "Image bytes",
7244        image_options.clone(),
7245    );
7246    widgets::drag_drop::dnd_apply_drop_zone_preview(
7247        ui,
7248        image_zone.root,
7249        &image_options,
7250        widgets::drag_drop::DropZonePreviewState::Hovered,
7251    );
7252
7253    let disabled_options = widgets::DropZoneOptions::default()
7254        .with_layout(drop_zone_layout())
7255        .with_kind(DragDropSurfaceKind::EditorSurface)
7256        .with_accepted_payload(DropPayloadFilter::any())
7257        .with_action("drag_drop.disabled")
7258        .disabled();
7259    let disabled_zone = widgets::dnd_drop_zone(
7260        ui,
7261        zones,
7262        "drag_drop.disabled",
7263        "Disabled",
7264        disabled_options.clone(),
7265    );
7266    widgets::drag_drop::dnd_apply_drop_zone_preview(
7267        ui,
7268        disabled_zone.root,
7269        &disabled_options,
7270        widgets::drag_drop::DropZonePreviewState::Disabled,
7271    );
7272
7273    let operation_row = wrapping_row(ui, body, "drag_drop.operations", 6.0);
7274    dnd_operation_chip(ui, operation_row, "drag_drop.operation.copy", "copy");
7275    dnd_operation_chip(ui, operation_row, "drag_drop.operation.move", "move");
7276    dnd_operation_chip(ui, operation_row, "drag_drop.operation.link", "link");
7277    widgets::label(
7278        ui,
7279        body,
7280        "drag_drop.status",
7281        format!("Status: {}", state.drag_drop_status),
7282        text(11.0, color(154, 166, 184)),
7283        LayoutStyle::new().with_width_percent(1.0),
7284    );
7285}
7286
7287fn media_widgets(ui: &mut UiDocument, parent: UiNodeId) {
7288    let body = section_with_min_viewport(ui, parent, "media", "Media", UiSize::new(430.0, 0.0));
7289    widgets::label(
7290        ui,
7291        body,
7292        "media.icons.label",
7293        "Built-in icons",
7294        text(12.0, color(166, 176, 190)),
7295        LayoutStyle::new().with_width_percent(1.0),
7296    );
7297    let icons = wrapping_row(ui, body, "media.icons", 8.0);
7298    for icon in BuiltInIcon::COMMON {
7299        media_icon_tile(ui, icons, icon);
7300    }
7301
7302    widgets::label(
7303        ui,
7304        body,
7305        "media.variants.label",
7306        "Image variants",
7307        text(12.0, color(166, 176, 190)),
7308        LayoutStyle::new().with_width_percent(1.0),
7309    );
7310    let variants = wrapping_row(ui, body, "media.variants", 10.0);
7311    widgets::image(
7312        ui,
7313        variants,
7314        "media.image.untinted",
7315        icon_image(BuiltInIcon::Play),
7316        widgets::ImageOptions::default()
7317            .with_layout(media_preview_image_layout())
7318            .with_accessibility_label("Untinted play icon"),
7319    );
7320    widgets::image(
7321        ui,
7322        variants,
7323        "media.image.warning",
7324        ImageContent::new(BuiltInIcon::Warning.key()).tinted(color(232, 186, 88)),
7325        widgets::ImageOptions::default()
7326            .with_layout(media_preview_image_layout())
7327            .with_accessibility_label("Tinted warning icon"),
7328    );
7329    widgets::image(
7330        ui,
7331        variants,
7332        "media.image.shader",
7333        ImageContent::new(BuiltInIcon::Grid.key()).tinted(color(118, 183, 255)),
7334        widgets::ImageOptions::default()
7335            .with_layout(media_preview_image_layout())
7336            .with_shader(ShaderEffect::new("media.preview.tint").uniform("amount", 0.5))
7337            .with_accessibility_label("Shader-decorated grid icon"),
7338    );
7339    widgets::label(
7340        ui,
7341        body,
7342        "media.image.note",
7343        "Image widgets reference stable resource keys; the host resolves them to textures, vector assets, tinting, or shader-backed resources.",
7344        text(12.0, color(166, 176, 190)),
7345        LayoutStyle::new().with_width_percent(1.0),
7346    );
7347}
7348
7349fn timeline_ruler(ui: &mut UiDocument, parent: UiNodeId) {
7350    let layout = LayoutStyle::column()
7351        .with_width_percent(1.0)
7352        .with_height(40.0)
7353        .with_flex_shrink(0.0);
7354    let layout = operad::layout::with_min_size(layout, operad::length(0.0), operad::length(0.0));
7355    let body = widgets::scroll_area(ui, parent, "timeline", ScrollAxes::BOTH, layout);
7356    ext_widgets::timeline_ruler(
7357        ui,
7358        body,
7359        "timeline.ruler",
7360        ext_widgets::RulerSpec {
7361            range: ext_widgets::TimelineRange::new(0.0, 12.0),
7362            width: 600.0,
7363            major_step: 2.0,
7364            minor_step: 0.5,
7365            label_every: 1,
7366        },
7367        ext_widgets::TimelineRulerOptions::default(),
7368    );
7369}
7370
7371fn toast_controls(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7372    let body = section(ui, parent, "toasts", "Toasts");
7373    let controls = row(ui, body, "toasts.controls", 10.0);
7374    button(
7375        ui,
7376        controls,
7377        "toasts.show",
7378        "Show toast",
7379        "toast.show",
7380        button_visual(48, 112, 184),
7381    );
7382    button(
7383        ui,
7384        controls,
7385        "toasts.hide",
7386        "Hide",
7387        "toast.hide",
7388        button_visual(58, 78, 96),
7389    );
7390    widgets::label(
7391        ui,
7392        body,
7393        "toasts.status",
7394        if state.toast_visible {
7395            "Toast overlay is visible."
7396        } else {
7397            "Toast overlay is hidden."
7398        },
7399        text(12.0, color(196, 210, 230)),
7400        LayoutStyle::new().with_width_percent(1.0),
7401    );
7402    widgets::label(
7403        ui,
7404        body,
7405        "toasts.action_status",
7406        format!("Action: {}", state.toast_action_status),
7407        text(12.0, color(154, 166, 184)),
7408        LayoutStyle::new().with_width_percent(1.0),
7409    );
7410}
7411
7412fn popup_controls(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7413    let body = section(ui, parent, "popup_panel", "Popup panel");
7414    let controls = row(ui, body, "popup_panel.controls", 8.0);
7415    button(
7416        ui,
7417        controls,
7418        "popup_panel.toggle",
7419        if state.popup_open {
7420            "Close popup"
7421        } else {
7422            "Open popup"
7423        },
7424        "popup.toggle",
7425        button_visual(48, 112, 184),
7426    );
7427    if state.popup_open {
7428        let mut close =
7429            widgets::ButtonOptions::new(LayoutStyle::size(30.0, 30.0)).with_action("popup.close");
7430        close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
7431        close.hovered_visual = Some(button_visual(54, 70, 92));
7432        close.text_style = text(13.0, color(220, 228, 238));
7433        widgets::button(ui, controls, "popup_panel.inline_close", "x", close);
7434    }
7435    widgets::label(
7436        ui,
7437        body,
7438        "popup_panel.status",
7439        if state.popup_open {
7440            "Popup overlay is open."
7441        } else {
7442            "Popup overlay is closed."
7443        },
7444        text(12.0, color(196, 210, 230)),
7445        LayoutStyle::new().with_width_percent(1.0),
7446    );
7447    if state.popup_open {
7448        let panel = ext_widgets::popup_panel(
7449            ui,
7450            parent,
7451            "popup_panel.inline_preview",
7452            UiRect::new(0.0, 20.0, 160.0, 104.0),
7453            ext_widgets::PopupOptions {
7454                z_index: 4,
7455                portal: UiPortalTarget::Parent,
7456                accessibility: Some(
7457                    AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup preview"),
7458                ),
7459                ..Default::default()
7460            },
7461        );
7462        let content = ui.add_child(
7463            panel,
7464            UiNode::container(
7465                "popup_panel.inline_preview.body",
7466                LayoutStyle::column()
7467                    .with_width_percent(1.0)
7468                    .with_height_percent(1.0)
7469                    .with_padding(10.0)
7470                    .with_gap(8.0),
7471            ),
7472        );
7473        let header = row(ui, content, "popup_panel.inline_preview.header", 8.0);
7474        widgets::label(
7475            ui,
7476            header,
7477            "popup_panel.inline_preview.title",
7478            "Popup panel",
7479            text(12.0, color(226, 234, 246)),
7480            LayoutStyle::new().with_width_percent(1.0),
7481        );
7482        let mut close =
7483            widgets::ButtonOptions::new(LayoutStyle::size(26.0, 22.0)).with_action("popup.close");
7484        close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
7485        close.hovered_visual = Some(button_visual(54, 70, 92));
7486        close.text_style = text(12.0, color(220, 228, 238));
7487        widgets::button(ui, header, "popup_panel.inline_preview.close", "x", close);
7488        widgets::label(
7489            ui,
7490            content,
7491            "popup_panel.inline_preview.text",
7492            "Overlay content",
7493            text(11.0, color(196, 210, 230)),
7494            LayoutStyle::new().with_width_percent(1.0),
7495        );
7496        widgets::spacer(
7497            ui,
7498            body,
7499            "popup_panel.inline_preview.space",
7500            LayoutStyle::new()
7501                .with_width_percent(1.0)
7502                .with_height(112.0)
7503                .with_flex_shrink(0.0),
7504        );
7505    }
7506}
7507
7508fn styling_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7509    let body = section(ui, parent, "styling", "Styling");
7510    let grid_layout = operad::layout::with_grid_template_columns(
7511        Layout::grid()
7512            .size(LayoutSize::percent(1.0, 1.0))
7513            .gap(LayoutGap::points(10.0, 10.0))
7514            .to_layout_style(),
7515        [
7516            LayoutGridTrack::points(300.0),
7517            LayoutGridTrack::points(1.0),
7518            LayoutGridTrack::points(210.0),
7519        ],
7520    );
7521    let grid = ui.add_child(body, UiNode::container("styling.grid", grid_layout));
7522    let controls = ui.add_child(
7523        grid,
7524        UiNode::container(
7525            "styling.controls",
7526            LayoutStyle::column()
7527                .with_width(300.0)
7528                .with_height_percent(1.0)
7529                .with_flex_shrink(0.0)
7530                .gap(6.0),
7531        ),
7532    );
7533    style_edge_group(
7534        ui,
7535        controls,
7536        "styling.inner",
7537        "Inner margin",
7538        "styling.inner_same",
7539        state.styling.inner_same,
7540        [
7541            ("Left", "styling.inner", state.styling.inner_margin),
7542            ("Right", "styling.inner_right", state.styling.inner_right),
7543            ("Top", "styling.inner_top", state.styling.inner_top),
7544            ("Bottom", "styling.inner_bottom", state.styling.inner_bottom),
7545        ],
7546        0.0..32.0,
7547    );
7548    style_edge_group(
7549        ui,
7550        controls,
7551        "styling.outer",
7552        "Outer margin",
7553        "styling.outer_same",
7554        state.styling.outer_same,
7555        [
7556            ("Left", "styling.outer", state.styling.outer_margin),
7557            ("Right", "styling.outer_right", state.styling.outer_right),
7558            ("Top", "styling.outer_top", state.styling.outer_top),
7559            ("Bottom", "styling.outer_bottom", state.styling.outer_bottom),
7560        ],
7561        0.0..40.0,
7562    );
7563    style_edge_group(
7564        ui,
7565        controls,
7566        "styling.radius",
7567        "Corner radius",
7568        "styling.radius_same",
7569        state.styling.radius_same,
7570        [
7571            ("NW", "styling.radius", state.styling.corner_radius),
7572            ("NE", "styling.radius_ne", state.styling.corner_ne),
7573            ("SW", "styling.radius_sw", state.styling.corner_sw),
7574            ("SE", "styling.radius_se", state.styling.corner_se),
7575        ],
7576        0.0..28.0,
7577    );
7578    style_shadow_group(ui, controls, state);
7579    style_color_button_row(
7580        ui,
7581        controls,
7582        "styling.fill_color_button",
7583        "Fill",
7584        state.styling.fill_color(),
7585        "Pick fill color",
7586    );
7587    if state.styling_fill_picker_open {
7588        ext_widgets::color_picker(
7589            ui,
7590            controls,
7591            "styling.fill_picker",
7592            &state.styling_fill_picker,
7593            ext_widgets::ColorPickerOptions::default()
7594                .with_label("Fill")
7595                .with_action_prefix("styling.fill_picker"),
7596        );
7597    }
7598    style_stroke_row(ui, controls, state);
7599    if state.styling_stroke_picker_open {
7600        ext_widgets::color_picker(
7601            ui,
7602            controls,
7603            "styling.stroke_picker",
7604            &state.styling_stroke_picker,
7605            ext_widgets::ColorPickerOptions::default()
7606                .with_label("Stroke color")
7607                .with_action_prefix("styling.stroke_picker"),
7608        );
7609    }
7610    widgets::separator(
7611        ui,
7612        grid,
7613        "styling.preview.separator",
7614        widgets::SeparatorOptions::vertical().with_layout(
7615            LayoutStyle::new()
7616                .with_width(1.0)
7617                .with_height_percent(1.0)
7618                .with_flex_shrink(0.0),
7619        ),
7620    );
7621
7622    let preview = ui.add_child(
7623        grid,
7624        UiNode::container(
7625            "styling.preview",
7626            LayoutStyle::column()
7627                .with_width(210.0)
7628                .with_height_percent(1.0)
7629                .with_flex_shrink(0.0)
7630                .padding(8.0),
7631        )
7632        .with_visual(UiVisual::panel(color(17, 20, 25), None, 0.0)),
7633    );
7634    style_preview(ui, preview, state.styling);
7635}
7636
7637#[allow(clippy::too_many_arguments)]
7638fn style_edge_group(
7639    ui: &mut UiDocument,
7640    parent: UiNodeId,
7641    name: &'static str,
7642    title: &'static str,
7643    same_action: &'static str,
7644    same: bool,
7645    values: [(&'static str, &'static str, f32); 4],
7646    range: std::ops::Range<f32>,
7647) {
7648    let group = style_control_group(ui, parent, format!("{name}.group"));
7649    style_group_title(ui, group, format!("{name}.title"), title);
7650    let fields = ui.add_child(
7651        group,
7652        UiNode::container(
7653            format!("{name}.fields"),
7654            LayoutStyle::column()
7655                .with_width(138.0)
7656                .with_flex_shrink(0.0)
7657                .gap(3.0),
7658        ),
7659    );
7660    style_compact_checkbox(ui, fields, same_action, "same", same);
7661    if same {
7662        style_number_row(ui, fields, values[0].1, "All", values[0].2, range, 0);
7663    } else {
7664        for (label, action, value) in values {
7665            style_number_row(ui, fields, action, label, value, range.clone(), 0);
7666        }
7667    }
7668}
7669
7670fn style_shadow_group(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7671    let group = style_control_group(ui, parent, "styling.shadow.group");
7672    style_group_title(ui, group, "styling.shadow.title", "Shadow");
7673    let fields = ui.add_child(
7674        group,
7675        UiNode::container(
7676            "styling.shadow.fields",
7677            LayoutStyle::column()
7678                .with_width(174.0)
7679                .with_flex_shrink(0.0)
7680                .gap(4.0),
7681        ),
7682    );
7683    let offsets = row(ui, fields, "styling.shadow.offsets", 6.0);
7684    style_inline_number(
7685        ui,
7686        offsets,
7687        "styling.shadow_x",
7688        "x",
7689        state.styling.shadow_x,
7690        -24.0..24.0,
7691        0,
7692    );
7693    style_inline_number(
7694        ui,
7695        offsets,
7696        "styling.shadow_y",
7697        "y",
7698        state.styling.shadow_y,
7699        -24.0..24.0,
7700        0,
7701    );
7702    let spread = row(ui, fields, "styling.shadow.blur_spread", 6.0);
7703    style_inline_number(
7704        ui,
7705        spread,
7706        "styling.shadow",
7707        "blur",
7708        state.styling.shadow_blur,
7709        0.0..32.0,
7710        0,
7711    );
7712    style_inline_number(
7713        ui,
7714        spread,
7715        "styling.shadow_spread",
7716        "spread",
7717        state.styling.shadow_spread,
7718        0.0..16.0,
7719        0,
7720    );
7721    style_color_button_row(
7722        ui,
7723        fields,
7724        "styling.shadow_color_button",
7725        "",
7726        state.styling.shadow_color(),
7727        "Pick shadow color",
7728    );
7729    if state.styling_shadow_picker_open {
7730        ext_widgets::color_picker(
7731            ui,
7732            fields,
7733            "styling.shadow_picker",
7734            &state.styling_shadow_picker,
7735            ext_widgets::ColorPickerOptions::default()
7736                .with_label("Shadow color")
7737                .with_action_prefix("styling.shadow_picker"),
7738        );
7739    }
7740}
7741
7742fn style_stroke_row(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
7743    let row = row(ui, parent, "styling.stroke.row", 8.0);
7744    widgets::label(
7745        ui,
7746        row,
7747        "styling.stroke.label",
7748        "Stroke",
7749        text(12.0, color(166, 176, 190)),
7750        LayoutStyle::new().with_width(86.0).with_flex_shrink(0.0),
7751    );
7752    style_value_input(
7753        ui,
7754        row,
7755        "styling.stroke",
7756        state.styling.stroke_width,
7757        0.0..4.0,
7758        1,
7759    );
7760    ext_widgets::color_edit_button(
7761        ui,
7762        row,
7763        "styling.stroke_color_button",
7764        state.styling.stroke_color(),
7765        color_mini_button_options("styling.stroke_color_button")
7766            .with_format(ext_widgets::ColorValueFormat::Rgba)
7767            .accessibility_label("Pick stroke color"),
7768    );
7769    let mut options = widgets::SliderOptions::default()
7770        .with_layout(
7771            LayoutStyle::new()
7772                .with_width(60.0)
7773                .with_height(20.0)
7774                .with_flex_shrink(0.0),
7775        )
7776        .with_value_edit_action("styling.stroke");
7777    options.fill_color = color(120, 170, 230);
7778    widgets::slider(
7779        ui,
7780        row,
7781        "styling.stroke.slider",
7782        (state.styling.stroke_width / 4.0).clamp(0.0, 1.0),
7783        0.0..1.0,
7784        options,
7785    );
7786}
7787
7788fn style_control_group(ui: &mut UiDocument, parent: UiNodeId, name: impl Into<String>) -> UiNodeId {
7789    ui.add_child(
7790        parent,
7791        UiNode::container(
7792            name,
7793            LayoutStyle::row()
7794                .with_width_percent(1.0)
7795                .with_flex_shrink(0.0)
7796                .padding(4.0)
7797                .gap(8.0),
7798        )
7799        .with_visual(UiVisual::panel(color(23, 27, 33), None, 2.0)),
7800    )
7801}
7802
7803fn style_group_title(
7804    ui: &mut UiDocument,
7805    parent: UiNodeId,
7806    name: impl Into<String>,
7807    label: &'static str,
7808) {
7809    widgets::label(
7810        ui,
7811        parent,
7812        name,
7813        label,
7814        text(12.0, color(166, 176, 190)),
7815        LayoutStyle::new()
7816            .with_width(88.0)
7817            .with_flex_shrink(0.0)
7818            .with_height(22.0),
7819    );
7820}
7821
7822fn style_color_button_row(
7823    ui: &mut UiDocument,
7824    parent: UiNodeId,
7825    action: &'static str,
7826    label: &'static str,
7827    value: ColorRgba,
7828    accessibility_label: &'static str,
7829) {
7830    let row = row(ui, parent, format!("{action}.row"), 8.0);
7831    if !label.is_empty() {
7832        widgets::label(
7833            ui,
7834            row,
7835            format!("{action}.label"),
7836            label,
7837            text(12.0, color(166, 176, 190)),
7838            LayoutStyle::new()
7839                .with_width(86.0)
7840                .with_flex_shrink(0.0)
7841                .with_height(24.0),
7842        );
7843    }
7844    ext_widgets::color_edit_button(
7845        ui,
7846        row,
7847        action,
7848        value,
7849        color_mini_button_options(action)
7850            .with_format(ext_widgets::ColorValueFormat::Rgba)
7851            .accessibility_label(accessibility_label),
7852    );
7853    widgets::label(
7854        ui,
7855        row,
7856        format!("{action}.value"),
7857        ext_widgets::color_picker::format_hex_color(value, value.a < 255),
7858        text(12.0, color(226, 232, 242)),
7859        LayoutStyle::new().with_width(96.0).with_height(24.0),
7860    );
7861}
7862
7863fn style_number_row(
7864    ui: &mut UiDocument,
7865    parent: UiNodeId,
7866    name: &'static str,
7867    label: &'static str,
7868    value: f32,
7869    range: std::ops::Range<f32>,
7870    decimals: u8,
7871) {
7872    let row = row(ui, parent, format!("{name}.row"), 6.0);
7873    widgets::label(
7874        ui,
7875        row,
7876        format!("{name}.label"),
7877        label,
7878        text(12.0, color(166, 176, 190)),
7879        LayoutStyle::new().with_width(48.0).with_height(22.0),
7880    );
7881    style_value_input(ui, row, name, value, range, decimals);
7882}
7883
7884fn style_inline_number(
7885    ui: &mut UiDocument,
7886    parent: UiNodeId,
7887    name: &'static str,
7888    label: &'static str,
7889    value: f32,
7890    range: std::ops::Range<f32>,
7891    decimals: u8,
7892) {
7893    let row = row(ui, parent, format!("{name}.inline"), 3.0);
7894    widgets::label(
7895        ui,
7896        row,
7897        format!("{name}.inline_label"),
7898        format!("{label}:"),
7899        text(12.0, color(166, 176, 190)),
7900        LayoutStyle::new()
7901            .with_width(if label.len() > 1 { 42.0 } else { 16.0 })
7902            .with_height(22.0),
7903    );
7904    style_value_input(ui, row, name, value, range, decimals);
7905}
7906
7907fn style_value_input(
7908    ui: &mut UiDocument,
7909    parent: UiNodeId,
7910    name: &'static str,
7911    value: f32,
7912    range: std::ops::Range<f32>,
7913    decimals: u8,
7914) {
7915    let mut options = widgets::DragValueOptions::default()
7916        .with_layout(
7917            LayoutStyle::new()
7918                .with_width(42.0)
7919                .with_height(22.0)
7920                .with_flex_shrink(0.0),
7921        )
7922        .with_range(ext_widgets::NumericRange::new(
7923            f64::from(range.start),
7924            f64::from(range.end),
7925        ))
7926        .with_precision(ext_widgets::NumericPrecision::decimals(decimals))
7927        .with_action(name);
7928    options.text_style = text(12.0, color(226, 232, 242));
7929    widgets::drag_value_input(ui, parent, name, f64::from(value), options);
7930}
7931
7932fn style_compact_checkbox(
7933    ui: &mut UiDocument,
7934    parent: UiNodeId,
7935    name: &'static str,
7936    label: &'static str,
7937    checked: bool,
7938) {
7939    let mut options = widgets::CheckboxOptions::default().with_action(name);
7940    options.layout = LayoutStyle::new().with_width(92.0).with_height(22.0);
7941    options.text_style = text(12.0, color(220, 228, 238));
7942    widgets::checkbox(ui, parent, name, label, checked, options);
7943}
7944
7945fn color_mini_button_options(action: &'static str) -> ext_widgets::ColorButtonOptions {
7946    ext_widgets::ColorButtonOptions::default()
7947        .with_layout(LayoutStyle::size(28.0, 24.0).with_flex_shrink(0.0))
7948        .with_swatch_size(UiSize::new(22.0, 18.0))
7949        .with_action(action)
7950        .show_label(false)
7951}
7952
7953fn style_preview(ui: &mut UiDocument, parent: UiNodeId, styling: StylingState) {
7954    let outer = styling.outer_edges();
7955    let inner = styling.inner_edges();
7956    let frame = UiRect::new(
7957        22.0 + outer[0],
7958        28.0 + outer[2],
7959        108.0 + inner[0] + inner[1],
7960        40.0 + inner[2] + inner[3],
7961    );
7962    let text_rect = UiRect::new(
7963        frame.x + inner[0],
7964        frame.y + inner[2],
7965        (frame.width - inner[0] - inner[1]).max(1.0),
7966        (frame.height - inner[2] - inner[3]).max(1.0),
7967    );
7968    ui.add_child(
7969        parent,
7970        UiNode::scene(
7971            "styling.preview.scene",
7972            vec![
7973                ScenePrimitive::Rect(
7974                    PaintRect::solid(frame, styling.fill_color())
7975                        .stroke(AlignedStroke::inside(StrokeStyle::new(
7976                            styling.stroke_color(),
7977                            styling.stroke_width,
7978                        )))
7979                        .corner_radii(styling.radii())
7980                        .effect(PaintEffect::shadow(
7981                            styling.shadow_color(),
7982                            UiPoint::new(styling.shadow_x, styling.shadow_y),
7983                            styling.shadow_blur,
7984                            styling.shadow_spread,
7985                        )),
7986                ),
7987                ScenePrimitive::Text(
7988                    PaintText::new("Content", text_rect, text(13.0, color(255, 255, 255)))
7989                        .horizontal_align(TextHorizontalAlign::Center)
7990                        .vertical_align(TextVerticalAlign::Center)
7991                        .multiline(false),
7992                ),
7993            ],
7994            LayoutStyle::new()
7995                .with_width_percent(1.0)
7996                .with_height(180.0)
7997                .with_flex_shrink(0.0),
7998        ),
7999    );
8000}
Source

pub const fn center(style: StrokeStyle) -> Self

Source

pub const fn outside(style: StrokeStyle) -> Self

Trait Implementations§

Source§

impl Clone for AlignedStroke

Source§

fn clone(&self) -> AlignedStroke

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 AlignedStroke

Source§

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

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

impl From<StrokeStyle> for AlignedStroke

Source§

fn from(style: StrokeStyle) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for AlignedStroke

Source§

fn eq(&self, other: &AlignedStroke) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for AlignedStroke

Source§

impl StructuralPartialEq for AlignedStroke

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> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
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
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,