Skip to main content

ImageContent

Struct ImageContent 

Source
pub struct ImageContent {
    pub key: String,
    pub tint: Option<ColorRgba>,
}

Fields§

§key: String§tint: Option<ColorRgba>

Implementations§

Source§

impl ImageContent

Source

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

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

Source§

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)

Performs copy-assignment from source. Read more
Source§

impl Debug for ImageContent

Source§

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

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

impl PartialEq for ImageContent

Source§

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

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

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

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

impl Eq for ImageContent

Source§

impl StructuralPartialEq for ImageContent

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

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

Source§

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

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

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

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

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

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

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

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

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

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

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

fn in_current_span(self) -> Instrumented<Self>

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

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

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

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

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

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

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

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

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

Source§

impl<T> WithSubscriber for T

Source§

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

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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

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

Source§

impl<T> WasmNotSendSync for T

Source§

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