pub struct ImageContent {
pub key: String,
pub tint: Option<ColorRgba>,
}Fields§
§key: String§tint: Option<ColorRgba>Implementations§
Source§impl ImageContent
impl ImageContent
Sourcepub fn new(key: impl Into<String>) -> Self
pub fn new(key: impl Into<String>) -> Self
Examples found in repository?
examples/showcase.rs (line 7324)
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}
8001
8002fn slider_options(state: &ShowcaseState, width: f32) -> widgets::SliderOptions {
8003 let mut options = widgets::SliderOptions::default().with_layout(
8004 LayoutStyle::new()
8005 .with_width(width)
8006 .with_height(24.0)
8007 .with_flex_shrink(0.0),
8008 );
8009 options.fill_color = if state.slider_trailing_color {
8010 state.slider_trailing_picker.value()
8011 } else {
8012 color(42, 49, 58)
8013 };
8014 options.thumb_shape = match state.slider_thumb_shape {
8015 SliderThumbChoice::Circle => widgets::slider::SliderThumbShape::Circle,
8016 SliderThumbChoice::Square => widgets::slider::SliderThumbShape::Square,
8017 SliderThumbChoice::Rectangle => widgets::slider::SliderThumbShape::Rectangle,
8018 };
8019 options
8020}
8021
8022#[allow(clippy::field_reassign_with_default)]
8023fn slider_number_input(
8024 ui: &mut UiDocument,
8025 parent: UiNodeId,
8026 name: &'static str,
8027 input: &TextInputState,
8028 focused: FocusedTextInput,
8029 state: &ShowcaseState,
8030 width: f32,
8031) {
8032 let mut options = TextInputOptions::default();
8033 options.layout = LayoutStyle::new().with_width(width).with_height(28.0);
8034 options.text_style = text(12.0, color(230, 236, 246));
8035 options.placeholder_style = text(12.0, color(144, 156, 174));
8036 options.edit_action = Some(format!("{name}.edit").into());
8037 options.focused = state.focused_text == Some(focused);
8038 options.caret_visible = caret_visible(state.caret_phase);
8039 widgets::text_input(ui, parent, name, input, options);
8040}
8041
8042fn form_status_chip(
8043 ui: &mut UiDocument,
8044 parent: UiNodeId,
8045 name: &'static str,
8046 label: &'static str,
8047 active: bool,
8048) {
8049 let chip = ui.add_child(
8050 parent,
8051 UiNode::container(
8052 name,
8053 LayoutStyle::new()
8054 .with_width(82.0)
8055 .with_height(24.0)
8056 .with_padding(4.0)
8057 .with_flex_shrink(0.0),
8058 )
8059 .with_visual(UiVisual::panel(
8060 if active {
8061 color(35, 74, 54)
8062 } else {
8063 color(28, 34, 43)
8064 },
8065 Some(StrokeStyle::new(
8066 if active {
8067 color(90, 160, 112)
8068 } else {
8069 color(60, 72, 88)
8070 },
8071 1.0,
8072 )),
8073 4.0,
8074 )),
8075 );
8076 widgets::label(
8077 ui,
8078 chip,
8079 format!("{name}.label"),
8080 label,
8081 text(11.0, color(218, 228, 240)),
8082 LayoutStyle::new()
8083 .with_width_percent(1.0)
8084 .with_height_percent(1.0),
8085 );
8086}
8087
8088#[allow(clippy::field_reassign_with_default)]
8089fn form_text_field(
8090 ui: &mut UiDocument,
8091 parent: UiNodeId,
8092 name: &'static str,
8093 input: &TextInputState,
8094 focused: FocusedTextInput,
8095 state: &ShowcaseState,
8096) {
8097 let mut options = TextInputOptions::default();
8098 options.layout = LayoutStyle::new().with_width_percent(1.0).with_height(30.0);
8099 options.text_style = text(12.0, color(230, 236, 246));
8100 options.placeholder_style = text(12.0, color(144, 156, 174));
8101 options.placeholder = "Required".to_string();
8102 options.edit_action = Some(format!("{name}.edit").into());
8103 options.focused = state.focused_text == Some(focused);
8104 options.caret_visible = caret_visible(state.caret_phase);
8105 widgets::text_input(ui, parent, name, input, options);
8106}
8107
8108fn profile_email_valid(email: &str) -> bool {
8109 let email = email.trim();
8110 let Some((local, domain)) = email.split_once('@') else {
8111 return false;
8112 };
8113 !local.is_empty() && domain.contains('.') && !domain.ends_with('.')
8114}
8115
8116fn drag_source_layout() -> LayoutStyle {
8117 LayoutStyle::row()
8118 .with_width(128.0)
8119 .with_height(40.0)
8120 .with_padding(8.0)
8121 .with_gap(6.0)
8122 .with_flex_shrink(0.0)
8123}
8124
8125fn drop_zone_layout() -> LayoutStyle {
8126 LayoutStyle::column()
8127 .with_width(128.0)
8128 .with_height(78.0)
8129 .with_padding(10.0)
8130 .with_gap(6.0)
8131 .with_flex_shrink(0.0)
8132}
8133
8134fn dnd_operation_chip(
8135 ui: &mut UiDocument,
8136 parent: UiNodeId,
8137 name: &'static str,
8138 label: &'static str,
8139) {
8140 let chip = ui.add_child(
8141 parent,
8142 UiNode::container(
8143 name,
8144 LayoutStyle::new()
8145 .with_width(58.0)
8146 .with_height(22.0)
8147 .with_padding(3.0)
8148 .with_flex_shrink(0.0),
8149 )
8150 .with_visual(UiVisual::panel(
8151 color(26, 32, 42),
8152 Some(StrokeStyle::new(color(62, 76, 94), 1.0)),
8153 3.0,
8154 )),
8155 );
8156 widgets::label(
8157 ui,
8158 chip,
8159 format!("{name}.label"),
8160 label,
8161 text(11.0, color(190, 204, 222)),
8162 LayoutStyle::new()
8163 .with_width_percent(1.0)
8164 .with_height_percent(1.0),
8165 );
8166}
8167
8168fn media_preview_image_layout() -> LayoutStyle {
8169 LayoutStyle::size(46.0, 46.0).with_flex_shrink(0.0)
8170}
8171
8172fn media_icon_tile(ui: &mut UiDocument, parent: UiNodeId, icon: BuiltInIcon) {
8173 let name = icon.key().replace('.', "_").replace('-', "_");
8174 let tile = ui.add_child(
8175 parent,
8176 UiNode::container(
8177 format!("media.icon_tile.{name}"),
8178 LayoutStyle::column()
8179 .with_width(70.0)
8180 .with_height(78.0)
8181 .with_padding(6.0)
8182 .with_gap(4.0)
8183 .with_flex_shrink(0.0),
8184 )
8185 .with_visual(UiVisual::panel(
8186 color(17, 22, 30),
8187 Some(StrokeStyle::new(color(50, 62, 78), 1.0)),
8188 4.0,
8189 )),
8190 );
8191 widgets::image(
8192 ui,
8193 tile,
8194 format!("media.icon.{name}"),
8195 icon_image(icon),
8196 widgets::ImageOptions::default()
8197 .with_layout(LayoutStyle::size(28.0, 28.0))
8198 .with_accessibility_label(icon.label()),
8199 );
8200 widgets::label(
8201 ui,
8202 tile,
8203 format!("media.icon_label.{name}"),
8204 icon.label(),
8205 text(9.0, color(180, 194, 214)),
8206 LayoutStyle::new().with_width_percent(1.0).with_height(30.0),
8207 );
8208}
8209
8210fn slider_checkbox(
8211 ui: &mut UiDocument,
8212 parent: UiNodeId,
8213 name: &'static str,
8214 label: &'static str,
8215 checked: bool,
8216) {
8217 slider_checkbox_with_layout(
8218 ui,
8219 parent,
8220 name,
8221 label,
8222 checked,
8223 LayoutStyle::new().with_width_percent(1.0).with_height(30.0),
8224 );
8225}
8226
8227fn slider_checkbox_with_layout(
8228 ui: &mut UiDocument,
8229 parent: UiNodeId,
8230 name: &'static str,
8231 label: &'static str,
8232 checked: bool,
8233 layout: LayoutStyle,
8234) {
8235 let mut options = widgets::CheckboxOptions::default().with_action(name);
8236 options.layout = layout;
8237 options.text_style = text(12.0, color(220, 228, 238));
8238 widgets::checkbox(ui, parent, name, label, checked, options);
8239}
8240
8241fn choice_button(
8242 ui: &mut UiDocument,
8243 parent: UiNodeId,
8244 name: &'static str,
8245 label: &'static str,
8246 selected: bool,
8247) {
8248 let mut options =
8249 widgets::ButtonOptions::new(LayoutStyle::new().with_width(78.0).with_height(28.0))
8250 .with_action(name);
8251 options.visual = if selected {
8252 button_visual(48, 112, 184)
8253 } else {
8254 button_visual(38, 46, 58)
8255 };
8256 options.hovered_visual = Some(button_visual(65, 86, 106));
8257 options.pressed_visual = Some(button_visual(34, 54, 84));
8258 options.text_style = text(12.0, color(238, 244, 252));
8259 widgets::button(ui, parent, name, label, options);
8260}
8261
8262fn divider(ui: &mut UiDocument, parent: UiNodeId, name: &'static str) {
8263 ui.add_child(
8264 parent,
8265 UiNode::container(
8266 name,
8267 LayoutStyle::new()
8268 .with_width_percent(1.0)
8269 .with_height(1.0)
8270 .with_flex_shrink(0.0),
8271 )
8272 .with_visual(UiVisual::panel(color(48, 58, 72), None, 0.0)),
8273 );
8274}
8275
8276fn canvas(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
8277 let body = section(ui, parent, "canvas", "Canvas");
8278 let mut options = widgets::CanvasOptions::default()
8279 .with_accessibility_label("Shader canvas")
8280 .with_action("canvas.rotate")
8281 .with_aspect_ratio(16.0 / 9.0);
8282 options.layout = LayoutStyle::new()
8283 .with_width_percent(1.0)
8284 .with_height_percent(1.0)
8285 .with_flex_grow(1.0)
8286 .with_flex_shrink(1.0);
8287 options.visual = UiVisual::panel(
8288 color(18, 22, 28),
8289 Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
8290 4.0,
8291 );
8292 widgets::canvas(
8293 ui,
8294 body,
8295 "canvas.shader",
8296 CanvasContent::new("canvas.shader").program(showcase_canvas_program(state.cube)),
8297 options,
8298 );
8299}
8300
8301fn showcase_canvas_program(cube: CanvasCubeState) -> CanvasRenderProgram {
8302 CanvasRenderProgram::wgsl(include_str!("shaders/showcase_canvas.wgsl"))
8303 .label("showcase.canvas")
8304 .constant("CUBE_YAW", cube.yaw as f64)
8305 .constant("CUBE_PITCH", cube.pitch as f64)
8306 .clear_color(Some(color(18, 22, 28)))
8307}
8308
8309fn section(
8310 ui: &mut UiDocument,
8311 parent: UiNodeId,
8312 name: impl Into<String>,
8313 _title: impl Into<String>,
8314) -> UiNodeId {
8315 section_with_min_viewport(ui, parent, name, _title, UiSize::ZERO)
8316}
8317
8318fn section_with_min_viewport(
8319 ui: &mut UiDocument,
8320 parent: UiNodeId,
8321 name: impl Into<String>,
8322 _title: impl Into<String>,
8323 min_viewport_size: UiSize,
8324) -> UiNodeId {
8325 let name = name.into();
8326 let layout = Layout::column()
8327 .size(LayoutSize::percent(1.0, 1.0))
8328 .min_size(LayoutSize::points(
8329 min_viewport_size.width.max(0.0),
8330 min_viewport_size.height.max(0.0),
8331 ))
8332 .gap(LayoutGap::points(10.0, 10.0))
8333 .flex(1.0, 1.0, LayoutDimension::Auto)
8334 .to_layout_style();
8335 widgets::scroll_area(
8336 ui,
8337 parent,
8338 format!("{name}.section_scroll"),
8339 ScrollAxes::BOTH,
8340 layout,
8341 )
8342}
8343
8344fn row(ui: &mut UiDocument, parent: UiNodeId, name: impl Into<String>, gap: f32) -> UiNodeId {
8345 ui.add_child(
8346 parent,
8347 UiNode::container(
8348 name,
8349 Layout::row()
8350 .size(LayoutSize::new(
8351 LayoutDimension::percent(1.0),
8352 LayoutDimension::Auto,
8353 ))
8354 .gap(LayoutGap::points(gap, gap))
8355 .to_layout_style(),
8356 ),
8357 )
8358}
8359
8360fn wrapping_row(
8361 ui: &mut UiDocument,
8362 parent: UiNodeId,
8363 name: impl Into<String>,
8364 gap: f32,
8365) -> UiNodeId {
8366 ui.add_child(
8367 parent,
8368 UiNode::container(
8369 name,
8370 Layout::row()
8371 .size(LayoutSize::new(
8372 LayoutDimension::percent(1.0),
8373 LayoutDimension::Auto,
8374 ))
8375 .gap(LayoutGap::points(gap, gap))
8376 .flex_wrap(LayoutFlexWrap::Wrap)
8377 .to_layout_style(),
8378 ),
8379 )
8380}
8381
8382fn egui_panel_contents(
8383 ui: &mut UiDocument,
8384 parent: UiNodeId,
8385 name: &'static str,
8386 title: &'static str,
8387 offset_y: f32,
8388) {
8389 let header = ui.add_child(
8390 parent,
8391 UiNode::container(
8392 format!("{name}.egui_header"),
8393 LayoutStyle::row()
8394 .with_width_percent(1.0)
8395 .with_height(28.0)
8396 .with_padding(6.0)
8397 .with_flex_shrink(0.0),
8398 )
8399 .with_visual(UiVisual::panel(
8400 color(21, 26, 34),
8401 Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
8402 0.0,
8403 )),
8404 );
8405 widgets::label(
8406 ui,
8407 header,
8408 format!("{name}.egui_title"),
8409 title,
8410 text(12.0, color(226, 234, 246)),
8411 LayoutStyle::new().with_width_percent(1.0),
8412 );
8413 let scroll = widgets::scroll_area(
8414 ui,
8415 parent,
8416 format!("{name}.scroll_area"),
8417 ScrollAxes::VERTICAL,
8418 LayoutStyle::column()
8419 .with_width_percent(1.0)
8420 .with_height(0.0)
8421 .with_flex_grow(1.0)
8422 .with_padding(8.0)
8423 .with_gap(6.0),
8424 );
8425 ui.node_mut(scroll).set_action(format!("{name}.scroll"));
8426 if let Some(scroll_state) = ui.node_mut(scroll).scroll_mut() {
8427 scroll_state.set_offset(UiPoint::new(0.0, offset_y));
8428 }
8429 for (index, line) in lorem_lines().iter().take(8).enumerate() {
8430 widgets::label(
8431 ui,
8432 scroll,
8433 format!("{name}.egui_line.{index}"),
8434 *line,
8435 TextStyle {
8436 wrap: TextWrap::None,
8437 ..text(11.0, color(190, 202, 218))
8438 },
8439 LayoutStyle::new()
8440 .with_width_percent(1.0)
8441 .with_height(22.0)
8442 .with_flex_shrink(0.0),
8443 );
8444 }
8445}
8446
8447fn button(
8448 ui: &mut UiDocument,
8449 parent: UiNodeId,
8450 name: impl Into<String>,
8451 label: impl Into<String>,
8452 action: impl Into<String>,
8453 visual: UiVisual,
8454) -> UiNodeId {
8455 let mut options = widgets::ButtonOptions::new(LayoutStyle::new().with_height(32.0))
8456 .with_action(action.into());
8457 options.visual = visual;
8458 options.hovered_visual = Some(adjusted_button_visual(visual, 58));
8459 options.pressed_visual = Some(adjusted_button_visual(visual, -62));
8460 options.pressed_hovered_visual = Some(adjusted_button_visual(visual, 8));
8461 options.text_style = text(13.0, color(246, 249, 252));
8462 widgets::button(ui, parent, name, label, options)
8463}
8464
8465fn button_visual(r: u8, g: u8, b: u8) -> UiVisual {
8466 UiVisual::panel(
8467 color(r, g, b),
8468 Some(StrokeStyle::new(color(86, 102, 124), 1.0)),
8469 4.0,
8470 )
8471}
8472
8473fn color_square_button_options(action: &'static str) -> ext_widgets::ColorButtonOptions {
8474 ext_widgets::ColorButtonOptions::default()
8475 .with_layout(LayoutStyle::size(30.0, 30.0).with_flex_shrink(0.0))
8476 .with_swatch_size(UiSize::new(30.0, 30.0))
8477 .with_action(action)
8478 .show_label(false)
8479}
8480
8481fn color_value_button_options(action: &'static str, width: f32) -> ext_widgets::ColorButtonOptions {
8482 ext_widgets::ColorButtonOptions::default()
8483 .with_layout(
8484 LayoutStyle::new()
8485 .with_width(width)
8486 .with_height(30.0)
8487 .with_flex_shrink(0.0),
8488 )
8489 .with_action(action)
8490}
8491
8492fn icon_image(icon: BuiltInIcon) -> ImageContent {
8493 ImageContent::new(icon.key()).tinted(color(220, 228, 238))
8494}Sourcepub fn tinted(self, tint: ColorRgba) -> Self
pub fn tinted(self, tint: ColorRgba) -> Self
Examples found in repository?
examples/showcase.rs (line 7324)
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}
8001
8002fn slider_options(state: &ShowcaseState, width: f32) -> widgets::SliderOptions {
8003 let mut options = widgets::SliderOptions::default().with_layout(
8004 LayoutStyle::new()
8005 .with_width(width)
8006 .with_height(24.0)
8007 .with_flex_shrink(0.0),
8008 );
8009 options.fill_color = if state.slider_trailing_color {
8010 state.slider_trailing_picker.value()
8011 } else {
8012 color(42, 49, 58)
8013 };
8014 options.thumb_shape = match state.slider_thumb_shape {
8015 SliderThumbChoice::Circle => widgets::slider::SliderThumbShape::Circle,
8016 SliderThumbChoice::Square => widgets::slider::SliderThumbShape::Square,
8017 SliderThumbChoice::Rectangle => widgets::slider::SliderThumbShape::Rectangle,
8018 };
8019 options
8020}
8021
8022#[allow(clippy::field_reassign_with_default)]
8023fn slider_number_input(
8024 ui: &mut UiDocument,
8025 parent: UiNodeId,
8026 name: &'static str,
8027 input: &TextInputState,
8028 focused: FocusedTextInput,
8029 state: &ShowcaseState,
8030 width: f32,
8031) {
8032 let mut options = TextInputOptions::default();
8033 options.layout = LayoutStyle::new().with_width(width).with_height(28.0);
8034 options.text_style = text(12.0, color(230, 236, 246));
8035 options.placeholder_style = text(12.0, color(144, 156, 174));
8036 options.edit_action = Some(format!("{name}.edit").into());
8037 options.focused = state.focused_text == Some(focused);
8038 options.caret_visible = caret_visible(state.caret_phase);
8039 widgets::text_input(ui, parent, name, input, options);
8040}
8041
8042fn form_status_chip(
8043 ui: &mut UiDocument,
8044 parent: UiNodeId,
8045 name: &'static str,
8046 label: &'static str,
8047 active: bool,
8048) {
8049 let chip = ui.add_child(
8050 parent,
8051 UiNode::container(
8052 name,
8053 LayoutStyle::new()
8054 .with_width(82.0)
8055 .with_height(24.0)
8056 .with_padding(4.0)
8057 .with_flex_shrink(0.0),
8058 )
8059 .with_visual(UiVisual::panel(
8060 if active {
8061 color(35, 74, 54)
8062 } else {
8063 color(28, 34, 43)
8064 },
8065 Some(StrokeStyle::new(
8066 if active {
8067 color(90, 160, 112)
8068 } else {
8069 color(60, 72, 88)
8070 },
8071 1.0,
8072 )),
8073 4.0,
8074 )),
8075 );
8076 widgets::label(
8077 ui,
8078 chip,
8079 format!("{name}.label"),
8080 label,
8081 text(11.0, color(218, 228, 240)),
8082 LayoutStyle::new()
8083 .with_width_percent(1.0)
8084 .with_height_percent(1.0),
8085 );
8086}
8087
8088#[allow(clippy::field_reassign_with_default)]
8089fn form_text_field(
8090 ui: &mut UiDocument,
8091 parent: UiNodeId,
8092 name: &'static str,
8093 input: &TextInputState,
8094 focused: FocusedTextInput,
8095 state: &ShowcaseState,
8096) {
8097 let mut options = TextInputOptions::default();
8098 options.layout = LayoutStyle::new().with_width_percent(1.0).with_height(30.0);
8099 options.text_style = text(12.0, color(230, 236, 246));
8100 options.placeholder_style = text(12.0, color(144, 156, 174));
8101 options.placeholder = "Required".to_string();
8102 options.edit_action = Some(format!("{name}.edit").into());
8103 options.focused = state.focused_text == Some(focused);
8104 options.caret_visible = caret_visible(state.caret_phase);
8105 widgets::text_input(ui, parent, name, input, options);
8106}
8107
8108fn profile_email_valid(email: &str) -> bool {
8109 let email = email.trim();
8110 let Some((local, domain)) = email.split_once('@') else {
8111 return false;
8112 };
8113 !local.is_empty() && domain.contains('.') && !domain.ends_with('.')
8114}
8115
8116fn drag_source_layout() -> LayoutStyle {
8117 LayoutStyle::row()
8118 .with_width(128.0)
8119 .with_height(40.0)
8120 .with_padding(8.0)
8121 .with_gap(6.0)
8122 .with_flex_shrink(0.0)
8123}
8124
8125fn drop_zone_layout() -> LayoutStyle {
8126 LayoutStyle::column()
8127 .with_width(128.0)
8128 .with_height(78.0)
8129 .with_padding(10.0)
8130 .with_gap(6.0)
8131 .with_flex_shrink(0.0)
8132}
8133
8134fn dnd_operation_chip(
8135 ui: &mut UiDocument,
8136 parent: UiNodeId,
8137 name: &'static str,
8138 label: &'static str,
8139) {
8140 let chip = ui.add_child(
8141 parent,
8142 UiNode::container(
8143 name,
8144 LayoutStyle::new()
8145 .with_width(58.0)
8146 .with_height(22.0)
8147 .with_padding(3.0)
8148 .with_flex_shrink(0.0),
8149 )
8150 .with_visual(UiVisual::panel(
8151 color(26, 32, 42),
8152 Some(StrokeStyle::new(color(62, 76, 94), 1.0)),
8153 3.0,
8154 )),
8155 );
8156 widgets::label(
8157 ui,
8158 chip,
8159 format!("{name}.label"),
8160 label,
8161 text(11.0, color(190, 204, 222)),
8162 LayoutStyle::new()
8163 .with_width_percent(1.0)
8164 .with_height_percent(1.0),
8165 );
8166}
8167
8168fn media_preview_image_layout() -> LayoutStyle {
8169 LayoutStyle::size(46.0, 46.0).with_flex_shrink(0.0)
8170}
8171
8172fn media_icon_tile(ui: &mut UiDocument, parent: UiNodeId, icon: BuiltInIcon) {
8173 let name = icon.key().replace('.', "_").replace('-', "_");
8174 let tile = ui.add_child(
8175 parent,
8176 UiNode::container(
8177 format!("media.icon_tile.{name}"),
8178 LayoutStyle::column()
8179 .with_width(70.0)
8180 .with_height(78.0)
8181 .with_padding(6.0)
8182 .with_gap(4.0)
8183 .with_flex_shrink(0.0),
8184 )
8185 .with_visual(UiVisual::panel(
8186 color(17, 22, 30),
8187 Some(StrokeStyle::new(color(50, 62, 78), 1.0)),
8188 4.0,
8189 )),
8190 );
8191 widgets::image(
8192 ui,
8193 tile,
8194 format!("media.icon.{name}"),
8195 icon_image(icon),
8196 widgets::ImageOptions::default()
8197 .with_layout(LayoutStyle::size(28.0, 28.0))
8198 .with_accessibility_label(icon.label()),
8199 );
8200 widgets::label(
8201 ui,
8202 tile,
8203 format!("media.icon_label.{name}"),
8204 icon.label(),
8205 text(9.0, color(180, 194, 214)),
8206 LayoutStyle::new().with_width_percent(1.0).with_height(30.0),
8207 );
8208}
8209
8210fn slider_checkbox(
8211 ui: &mut UiDocument,
8212 parent: UiNodeId,
8213 name: &'static str,
8214 label: &'static str,
8215 checked: bool,
8216) {
8217 slider_checkbox_with_layout(
8218 ui,
8219 parent,
8220 name,
8221 label,
8222 checked,
8223 LayoutStyle::new().with_width_percent(1.0).with_height(30.0),
8224 );
8225}
8226
8227fn slider_checkbox_with_layout(
8228 ui: &mut UiDocument,
8229 parent: UiNodeId,
8230 name: &'static str,
8231 label: &'static str,
8232 checked: bool,
8233 layout: LayoutStyle,
8234) {
8235 let mut options = widgets::CheckboxOptions::default().with_action(name);
8236 options.layout = layout;
8237 options.text_style = text(12.0, color(220, 228, 238));
8238 widgets::checkbox(ui, parent, name, label, checked, options);
8239}
8240
8241fn choice_button(
8242 ui: &mut UiDocument,
8243 parent: UiNodeId,
8244 name: &'static str,
8245 label: &'static str,
8246 selected: bool,
8247) {
8248 let mut options =
8249 widgets::ButtonOptions::new(LayoutStyle::new().with_width(78.0).with_height(28.0))
8250 .with_action(name);
8251 options.visual = if selected {
8252 button_visual(48, 112, 184)
8253 } else {
8254 button_visual(38, 46, 58)
8255 };
8256 options.hovered_visual = Some(button_visual(65, 86, 106));
8257 options.pressed_visual = Some(button_visual(34, 54, 84));
8258 options.text_style = text(12.0, color(238, 244, 252));
8259 widgets::button(ui, parent, name, label, options);
8260}
8261
8262fn divider(ui: &mut UiDocument, parent: UiNodeId, name: &'static str) {
8263 ui.add_child(
8264 parent,
8265 UiNode::container(
8266 name,
8267 LayoutStyle::new()
8268 .with_width_percent(1.0)
8269 .with_height(1.0)
8270 .with_flex_shrink(0.0),
8271 )
8272 .with_visual(UiVisual::panel(color(48, 58, 72), None, 0.0)),
8273 );
8274}
8275
8276fn canvas(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
8277 let body = section(ui, parent, "canvas", "Canvas");
8278 let mut options = widgets::CanvasOptions::default()
8279 .with_accessibility_label("Shader canvas")
8280 .with_action("canvas.rotate")
8281 .with_aspect_ratio(16.0 / 9.0);
8282 options.layout = LayoutStyle::new()
8283 .with_width_percent(1.0)
8284 .with_height_percent(1.0)
8285 .with_flex_grow(1.0)
8286 .with_flex_shrink(1.0);
8287 options.visual = UiVisual::panel(
8288 color(18, 22, 28),
8289 Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
8290 4.0,
8291 );
8292 widgets::canvas(
8293 ui,
8294 body,
8295 "canvas.shader",
8296 CanvasContent::new("canvas.shader").program(showcase_canvas_program(state.cube)),
8297 options,
8298 );
8299}
8300
8301fn showcase_canvas_program(cube: CanvasCubeState) -> CanvasRenderProgram {
8302 CanvasRenderProgram::wgsl(include_str!("shaders/showcase_canvas.wgsl"))
8303 .label("showcase.canvas")
8304 .constant("CUBE_YAW", cube.yaw as f64)
8305 .constant("CUBE_PITCH", cube.pitch as f64)
8306 .clear_color(Some(color(18, 22, 28)))
8307}
8308
8309fn section(
8310 ui: &mut UiDocument,
8311 parent: UiNodeId,
8312 name: impl Into<String>,
8313 _title: impl Into<String>,
8314) -> UiNodeId {
8315 section_with_min_viewport(ui, parent, name, _title, UiSize::ZERO)
8316}
8317
8318fn section_with_min_viewport(
8319 ui: &mut UiDocument,
8320 parent: UiNodeId,
8321 name: impl Into<String>,
8322 _title: impl Into<String>,
8323 min_viewport_size: UiSize,
8324) -> UiNodeId {
8325 let name = name.into();
8326 let layout = Layout::column()
8327 .size(LayoutSize::percent(1.0, 1.0))
8328 .min_size(LayoutSize::points(
8329 min_viewport_size.width.max(0.0),
8330 min_viewport_size.height.max(0.0),
8331 ))
8332 .gap(LayoutGap::points(10.0, 10.0))
8333 .flex(1.0, 1.0, LayoutDimension::Auto)
8334 .to_layout_style();
8335 widgets::scroll_area(
8336 ui,
8337 parent,
8338 format!("{name}.section_scroll"),
8339 ScrollAxes::BOTH,
8340 layout,
8341 )
8342}
8343
8344fn row(ui: &mut UiDocument, parent: UiNodeId, name: impl Into<String>, gap: f32) -> UiNodeId {
8345 ui.add_child(
8346 parent,
8347 UiNode::container(
8348 name,
8349 Layout::row()
8350 .size(LayoutSize::new(
8351 LayoutDimension::percent(1.0),
8352 LayoutDimension::Auto,
8353 ))
8354 .gap(LayoutGap::points(gap, gap))
8355 .to_layout_style(),
8356 ),
8357 )
8358}
8359
8360fn wrapping_row(
8361 ui: &mut UiDocument,
8362 parent: UiNodeId,
8363 name: impl Into<String>,
8364 gap: f32,
8365) -> UiNodeId {
8366 ui.add_child(
8367 parent,
8368 UiNode::container(
8369 name,
8370 Layout::row()
8371 .size(LayoutSize::new(
8372 LayoutDimension::percent(1.0),
8373 LayoutDimension::Auto,
8374 ))
8375 .gap(LayoutGap::points(gap, gap))
8376 .flex_wrap(LayoutFlexWrap::Wrap)
8377 .to_layout_style(),
8378 ),
8379 )
8380}
8381
8382fn egui_panel_contents(
8383 ui: &mut UiDocument,
8384 parent: UiNodeId,
8385 name: &'static str,
8386 title: &'static str,
8387 offset_y: f32,
8388) {
8389 let header = ui.add_child(
8390 parent,
8391 UiNode::container(
8392 format!("{name}.egui_header"),
8393 LayoutStyle::row()
8394 .with_width_percent(1.0)
8395 .with_height(28.0)
8396 .with_padding(6.0)
8397 .with_flex_shrink(0.0),
8398 )
8399 .with_visual(UiVisual::panel(
8400 color(21, 26, 34),
8401 Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
8402 0.0,
8403 )),
8404 );
8405 widgets::label(
8406 ui,
8407 header,
8408 format!("{name}.egui_title"),
8409 title,
8410 text(12.0, color(226, 234, 246)),
8411 LayoutStyle::new().with_width_percent(1.0),
8412 );
8413 let scroll = widgets::scroll_area(
8414 ui,
8415 parent,
8416 format!("{name}.scroll_area"),
8417 ScrollAxes::VERTICAL,
8418 LayoutStyle::column()
8419 .with_width_percent(1.0)
8420 .with_height(0.0)
8421 .with_flex_grow(1.0)
8422 .with_padding(8.0)
8423 .with_gap(6.0),
8424 );
8425 ui.node_mut(scroll).set_action(format!("{name}.scroll"));
8426 if let Some(scroll_state) = ui.node_mut(scroll).scroll_mut() {
8427 scroll_state.set_offset(UiPoint::new(0.0, offset_y));
8428 }
8429 for (index, line) in lorem_lines().iter().take(8).enumerate() {
8430 widgets::label(
8431 ui,
8432 scroll,
8433 format!("{name}.egui_line.{index}"),
8434 *line,
8435 TextStyle {
8436 wrap: TextWrap::None,
8437 ..text(11.0, color(190, 202, 218))
8438 },
8439 LayoutStyle::new()
8440 .with_width_percent(1.0)
8441 .with_height(22.0)
8442 .with_flex_shrink(0.0),
8443 );
8444 }
8445}
8446
8447fn button(
8448 ui: &mut UiDocument,
8449 parent: UiNodeId,
8450 name: impl Into<String>,
8451 label: impl Into<String>,
8452 action: impl Into<String>,
8453 visual: UiVisual,
8454) -> UiNodeId {
8455 let mut options = widgets::ButtonOptions::new(LayoutStyle::new().with_height(32.0))
8456 .with_action(action.into());
8457 options.visual = visual;
8458 options.hovered_visual = Some(adjusted_button_visual(visual, 58));
8459 options.pressed_visual = Some(adjusted_button_visual(visual, -62));
8460 options.pressed_hovered_visual = Some(adjusted_button_visual(visual, 8));
8461 options.text_style = text(13.0, color(246, 249, 252));
8462 widgets::button(ui, parent, name, label, options)
8463}
8464
8465fn button_visual(r: u8, g: u8, b: u8) -> UiVisual {
8466 UiVisual::panel(
8467 color(r, g, b),
8468 Some(StrokeStyle::new(color(86, 102, 124), 1.0)),
8469 4.0,
8470 )
8471}
8472
8473fn color_square_button_options(action: &'static str) -> ext_widgets::ColorButtonOptions {
8474 ext_widgets::ColorButtonOptions::default()
8475 .with_layout(LayoutStyle::size(30.0, 30.0).with_flex_shrink(0.0))
8476 .with_swatch_size(UiSize::new(30.0, 30.0))
8477 .with_action(action)
8478 .show_label(false)
8479}
8480
8481fn color_value_button_options(action: &'static str, width: f32) -> ext_widgets::ColorButtonOptions {
8482 ext_widgets::ColorButtonOptions::default()
8483 .with_layout(
8484 LayoutStyle::new()
8485 .with_width(width)
8486 .with_height(30.0)
8487 .with_flex_shrink(0.0),
8488 )
8489 .with_action(action)
8490}
8491
8492fn icon_image(icon: BuiltInIcon) -> ImageContent {
8493 ImageContent::new(icon.key()).tinted(color(220, 228, 238))
8494}Trait Implementations§
Source§impl Clone for ImageContent
impl Clone for ImageContent
Source§fn clone(&self) -> ImageContent
fn clone(&self) -> ImageContent
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 ImageContent
impl Debug for ImageContent
Source§impl PartialEq for ImageContent
impl PartialEq for ImageContent
Source§fn eq(&self, other: &ImageContent) -> bool
fn eq(&self, other: &ImageContent) -> bool
Tests for
self and other values to be equal, and is used by ==.impl Eq for ImageContent
impl StructuralPartialEq for ImageContent
Auto Trait Implementations§
impl Freeze for ImageContent
impl RefUnwindSafe for ImageContent
impl Send for ImageContent
impl Sync for ImageContent
impl Unpin for ImageContent
impl UnsafeUnpin for ImageContent
impl UnwindSafe for ImageContent
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.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
Compare self to
key and return true if they are equal.