pub struct AlignedStroke {
pub style: StrokeStyle,
pub alignment: StrokeAlignment,
}Fields§
§style: StrokeStyle§alignment: StrokeAlignmentImplementations§
Source§impl AlignedStroke
impl AlignedStroke
pub const fn new(style: StrokeStyle, alignment: StrokeAlignment) -> Self
Sourcepub const fn inside(style: StrokeStyle) -> Self
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 ®istry,
5674 &[CommandScope::Global, CommandScope::Panel],
5675 &ShortcutFormatter::default(),
5676 ext_widgets::CommandDiagnosticsPanelOptions {
5677 label_width: 92.0,
5678 max_command_rows: 3,
5679 max_conflict_rows: 1,
5680 action_prefix: Some("diagnostics.commands".to_owned()),
5681 ..Default::default()
5682 },
5683 );
5684
5685 widgets::label(
5686 ui,
5687 theme_column,
5688 "diagnostics.theme.title",
5689 "Theme editor",
5690 text(14.0, color(222, 230, 240)),
5691 LayoutStyle::new().with_width_percent(1.0),
5692 );
5693 let theme_snapshot = DebugThemeSnapshot::from_theme(&Theme::dark());
5694 ext_widgets::theme_editor_panel(
5695 ui,
5696 theme_column,
5697 "diagnostics.theme",
5698 &theme_snapshot,
5699 ext_widgets::ThemeEditorPanelOptions {
5700 label_width: 92.0,
5701 max_token_rows: 1,
5702 max_component_rows: 1,
5703 action_prefix: Some("diagnostics.theme".to_owned()),
5704 ..Default::default()
5705 },
5706 );
5707}
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}pub const fn center(style: StrokeStyle) -> Self
pub const fn outside(style: StrokeStyle) -> Self
Trait Implementations§
Source§impl Clone for AlignedStroke
impl Clone for AlignedStroke
Source§fn clone(&self) -> AlignedStroke
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)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for AlignedStroke
impl Debug for AlignedStroke
Source§impl From<StrokeStyle> for AlignedStroke
impl From<StrokeStyle> for AlignedStroke
Source§fn from(style: StrokeStyle) -> Self
fn from(style: StrokeStyle) -> Self
Converts to this type from the input type.
Source§impl PartialEq for AlignedStroke
impl PartialEq for AlignedStroke
Source§fn eq(&self, other: &AlignedStroke) -> bool
fn eq(&self, other: &AlignedStroke) -> bool
Tests for
self and other values to be equal, and is used by ==.impl Copy for AlignedStroke
impl StructuralPartialEq for AlignedStroke
Auto Trait Implementations§
impl Freeze for AlignedStroke
impl RefUnwindSafe for AlignedStroke
impl Send for AlignedStroke
impl Sync for AlignedStroke
impl Unpin for AlignedStroke
impl UnsafeUnpin for AlignedStroke
impl UnwindSafe for AlignedStroke
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.