Skip to main content

LayoutGridTrack

Enum LayoutGridTrack 

Source
pub enum LayoutGridTrack {
    Auto,
    Points(f32),
    Fraction(f32),
    MinMaxPointsFraction {
        min: f32,
        max_fraction: f32,
    },
}

Variants§

§

Auto

§

Points(f32)

§

Fraction(f32)

§

MinMaxPointsFraction

Fields

§min: f32
§max_fraction: f32

Implementations§

Source§

impl LayoutGridTrack

Source

pub const fn auto() -> Self

Source

pub const fn points(value: f32) -> Self

Examples found in repository?
examples/showcase.rs (line 12488)
12470fn styling_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
12471    let preview_scene_size = style_preview_scene_size(state.styling);
12472    let preview_min_width = preview_scene_size.width + 16.0;
12473    let preview_min_height = preview_scene_size.height + 16.0;
12474    let body_min_width = STYLING_CONTROLS_WIDTH + 1.0 + preview_min_width + 20.0;
12475    let body = section_with_min_viewport(
12476        ui,
12477        parent,
12478        "styling",
12479        "Styling",
12480        UiSize::new(body_min_width, preview_min_height),
12481    );
12482    let grid_layout = operad::layout::with_grid_template_columns(
12483        Layout::grid()
12484            .size(LayoutSize::percent(1.0, 1.0))
12485            .gap(LayoutGap::points(10.0, 10.0))
12486            .to_layout_style(),
12487        [
12488            LayoutGridTrack::points(STYLING_CONTROLS_WIDTH),
12489            LayoutGridTrack::points(1.0),
12490            LayoutGridTrack::minmax_points_fraction(preview_min_width, 1.0),
12491        ],
12492    );
12493    let grid = ui.add_child(body, UiNode::container("styling.grid", grid_layout));
12494    let controls = ui.add_child(
12495        grid,
12496        UiNode::container(
12497            "styling.controls",
12498            LayoutStyle::column()
12499                .with_width(STYLING_CONTROLS_WIDTH)
12500                .with_height_percent(1.0)
12501                .with_flex_shrink(0.0)
12502                .gap(6.0),
12503        ),
12504    );
12505    style_edge_group(
12506        ui,
12507        controls,
12508        "styling.inner",
12509        "Inner margin",
12510        "styling.inner_same",
12511        state.styling.inner_same,
12512        [
12513            ("Left", "styling.inner", state.styling.inner_margin),
12514            ("Right", "styling.inner_right", state.styling.inner_right),
12515            ("Top", "styling.inner_top", state.styling.inner_top),
12516            ("Bottom", "styling.inner_bottom", state.styling.inner_bottom),
12517        ],
12518        0.0..32.0,
12519    );
12520    style_edge_group(
12521        ui,
12522        controls,
12523        "styling.outer",
12524        "Outer margin",
12525        "styling.outer_same",
12526        state.styling.outer_same,
12527        [
12528            ("Left", "styling.outer", state.styling.outer_margin),
12529            ("Right", "styling.outer_right", state.styling.outer_right),
12530            ("Top", "styling.outer_top", state.styling.outer_top),
12531            ("Bottom", "styling.outer_bottom", state.styling.outer_bottom),
12532        ],
12533        0.0..40.0,
12534    );
12535    style_edge_group(
12536        ui,
12537        controls,
12538        "styling.radius",
12539        "Corner radius",
12540        "styling.radius_same",
12541        state.styling.radius_same,
12542        [
12543            ("NW", "styling.radius", state.styling.corner_radius),
12544            ("NE", "styling.radius_ne", state.styling.corner_ne),
12545            ("SW", "styling.radius_sw", state.styling.corner_sw),
12546            ("SE", "styling.radius_se", state.styling.corner_se),
12547        ],
12548        0.0..28.0,
12549    );
12550    style_fill_group(ui, controls, state);
12551    style_stroke_group(ui, controls, state);
12552    style_shadow_group(ui, controls, state);
12553    widgets::separator(
12554        ui,
12555        grid,
12556        "styling.preview.separator",
12557        widgets::SeparatorOptions::vertical().with_layout(
12558            LayoutStyle::new()
12559                .with_width(1.0)
12560                .with_height_percent(1.0)
12561                .with_flex_shrink(0.0),
12562        ),
12563    );
12564
12565    let preview = ui.add_child(
12566        grid,
12567        UiNode::container(
12568            "styling.preview",
12569            operad::layout::with_min_size(
12570                LayoutStyle::column()
12571                    .with_width_percent(1.0)
12572                    .with_height_percent(1.0)
12573                    .with_flex_shrink(0.0)
12574                    .padding(8.0),
12575                operad::layout::px(preview_min_width),
12576                operad::layout::px(preview_min_height),
12577            ),
12578        )
12579        .with_visual(UiVisual::panel(color(17, 20, 25), None, 0.0)),
12580    );
12581    style_preview(ui, preview, state.styling);
12582}
12583
12584#[allow(clippy::too_many_arguments)]
12585fn style_edge_group(
12586    ui: &mut UiDocument,
12587    parent: UiNodeId,
12588    name: &'static str,
12589    title: &'static str,
12590    same_action: &'static str,
12591    same: bool,
12592    values: [(&'static str, &'static str, f32); 4],
12593    range: std::ops::Range<f32>,
12594) {
12595    let group = style_control_group(ui, parent, format!("{name}.group"));
12596    style_group_title(ui, group, format!("{name}.title"), title);
12597    let fields = ui.add_child(
12598        group,
12599        UiNode::container(
12600            format!("{name}.fields"),
12601            LayoutStyle::column()
12602                .with_width(138.0)
12603                .with_flex_shrink(0.0)
12604                .gap(3.0),
12605        ),
12606    );
12607    style_compact_checkbox(ui, fields, same_action, "same", same);
12608    if same {
12609        style_number_row(ui, fields, values[0].1, "All", values[0].2, range, 0);
12610    } else {
12611        for (label, action, value) in values {
12612            style_number_row(ui, fields, action, label, value, range.clone(), 0);
12613        }
12614    }
12615}
12616
12617fn style_fill_group(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
12618    let group = style_control_group(ui, parent, "styling.fill.group");
12619    style_group_title(ui, group, "styling.fill.title", "Fill");
12620    let fields = style_group_fields(
12621        ui,
12622        group,
12623        "styling.fill.fields",
12624        STYLING_WIDE_FIELDS_WIDTH,
12625        4.0,
12626    );
12627    style_color_button_row(
12628        ui,
12629        fields,
12630        "styling.fill_color_button",
12631        "",
12632        state.styling.fill_color(),
12633        "Pick fill color",
12634    );
12635    if state.styling_fill_picker_open {
12636        ext_widgets::color_picker(
12637            ui,
12638            fields,
12639            "styling.fill_picker",
12640            &state.styling_fill_picker,
12641            ext_widgets::ColorPickerOptions::default()
12642                .with_label("Fill")
12643                .with_action_prefix("styling.fill_picker"),
12644        );
12645    }
12646}
12647
12648fn style_stroke_group(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
12649    let group = style_control_group(ui, parent, "styling.stroke.group");
12650    style_group_title(ui, group, "styling.stroke.title", "Stroke");
12651    let fields = style_group_fields(
12652        ui,
12653        group,
12654        "styling.stroke.fields",
12655        STYLING_WIDE_FIELDS_WIDTH,
12656        4.0,
12657    );
12658    let width_row = row(ui, fields, "styling.stroke.row", 6.0);
12659    style_inline_number(
12660        ui,
12661        width_row,
12662        "styling.stroke",
12663        "width",
12664        state.styling.stroke_width,
12665        0.0..STYLING_STROKE_MAX,
12666        1,
12667    );
12668    let mut options = widgets::SliderOptions::default()
12669        .with_layout(
12670            LayoutStyle::new()
12671                .with_width(60.0)
12672                .with_height(20.0)
12673                .with_flex_shrink(0.0),
12674        )
12675        .with_value_edit_action("styling.stroke");
12676    options.fill_color = color(120, 170, 230);
12677    widgets::slider(
12678        ui,
12679        width_row,
12680        "styling.stroke.slider",
12681        (state.styling.stroke_width / STYLING_STROKE_MAX).clamp(0.0, 1.0),
12682        0.0..1.0,
12683        options,
12684    );
12685    style_color_button_row(
12686        ui,
12687        fields,
12688        "styling.stroke_color_button",
12689        "",
12690        state.styling.stroke_color(),
12691        "Pick stroke color",
12692    );
12693    if state.styling_stroke_picker_open {
12694        ext_widgets::color_picker(
12695            ui,
12696            fields,
12697            "styling.stroke_picker",
12698            &state.styling_stroke_picker,
12699            ext_widgets::ColorPickerOptions::default()
12700                .with_label("Stroke color")
12701                .with_action_prefix("styling.stroke_picker"),
12702        );
12703    }
12704}
12705
12706fn style_shadow_group(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
12707    let group = style_control_group(ui, parent, "styling.shadow.group");
12708    style_group_title(ui, group, "styling.shadow.title", "Shadow");
12709    let fields = style_group_fields(
12710        ui,
12711        group,
12712        "styling.shadow.fields",
12713        STYLING_WIDE_FIELDS_WIDTH,
12714        4.0,
12715    );
12716    let offsets = row(ui, fields, "styling.shadow.offsets", 6.0);
12717    style_inline_number(
12718        ui,
12719        offsets,
12720        "styling.shadow_x",
12721        "x",
12722        state.styling.shadow_x,
12723        -24.0..24.0,
12724        0,
12725    );
12726    style_inline_number(
12727        ui,
12728        offsets,
12729        "styling.shadow_y",
12730        "y",
12731        state.styling.shadow_y,
12732        -24.0..24.0,
12733        0,
12734    );
12735    let spread = row(ui, fields, "styling.shadow.blur_spread", 6.0);
12736    style_inline_number(
12737        ui,
12738        spread,
12739        "styling.shadow",
12740        "blur",
12741        state.styling.shadow_blur,
12742        0.0..32.0,
12743        0,
12744    );
12745    style_inline_number(
12746        ui,
12747        spread,
12748        "styling.shadow_spread",
12749        "spread",
12750        state.styling.shadow_spread,
12751        0.0..16.0,
12752        0,
12753    );
12754    style_color_button_row(
12755        ui,
12756        fields,
12757        "styling.shadow_color_button",
12758        "",
12759        state.styling.shadow_color(),
12760        "Pick shadow color",
12761    );
12762    if state.styling_shadow_picker_open {
12763        ext_widgets::color_picker(
12764            ui,
12765            fields,
12766            "styling.shadow_picker",
12767            &state.styling_shadow_picker,
12768            ext_widgets::ColorPickerOptions::default()
12769                .with_label("Shadow color")
12770                .with_action_prefix("styling.shadow_picker"),
12771        );
12772    }
12773}
12774
12775fn style_control_group(ui: &mut UiDocument, parent: UiNodeId, name: impl Into<String>) -> UiNodeId {
12776    ui.add_child(
12777        parent,
12778        UiNode::container(
12779            name,
12780            LayoutStyle::row()
12781                .with_width_percent(1.0)
12782                .with_flex_shrink(0.0)
12783                .padding(4.0)
12784                .gap(8.0),
12785        )
12786        .with_visual(UiVisual::panel(color(23, 27, 33), None, 2.0)),
12787    )
12788}
12789
12790fn style_group_fields(
12791    ui: &mut UiDocument,
12792    parent: UiNodeId,
12793    name: impl Into<String>,
12794    width: f32,
12795    gap: f32,
12796) -> UiNodeId {
12797    ui.add_child(
12798        parent,
12799        UiNode::container(
12800            name,
12801            LayoutStyle::column()
12802                .with_width(width)
12803                .with_flex_shrink(0.0)
12804                .gap(gap),
12805        ),
12806    )
12807}
12808
12809fn style_group_title(
12810    ui: &mut UiDocument,
12811    parent: UiNodeId,
12812    name: impl Into<String>,
12813    label: &'static str,
12814) {
12815    widgets::label(
12816        ui,
12817        parent,
12818        name,
12819        label,
12820        text(12.0, color(166, 176, 190)),
12821        LayoutStyle::new()
12822            .with_width(88.0)
12823            .with_flex_shrink(0.0)
12824            .with_height(22.0),
12825    );
12826}
12827
12828fn style_color_button_row(
12829    ui: &mut UiDocument,
12830    parent: UiNodeId,
12831    action: &'static str,
12832    label: &'static str,
12833    value: ColorRgba,
12834    accessibility_label: &'static str,
12835) {
12836    let row = row(ui, parent, format!("{action}.row"), 8.0);
12837    if !label.is_empty() {
12838        widgets::label(
12839            ui,
12840            row,
12841            format!("{action}.label"),
12842            label,
12843            text(12.0, color(166, 176, 190)),
12844            LayoutStyle::new()
12845                .with_width(86.0)
12846                .with_flex_shrink(0.0)
12847                .with_height(24.0),
12848        );
12849    }
12850    ext_widgets::color_edit_button(
12851        ui,
12852        row,
12853        action,
12854        value,
12855        color_mini_button_options(action)
12856            .with_format(ext_widgets::ColorValueFormat::Rgba)
12857            .accessibility_label(accessibility_label),
12858    );
12859    widgets::label(
12860        ui,
12861        row,
12862        format!("{action}.value"),
12863        ext_widgets::color_picker::format_hex_color(value, value.a < 255),
12864        text(12.0, color(226, 232, 242)),
12865        LayoutStyle::new().with_width(96.0).with_height(24.0),
12866    );
12867}
12868
12869fn style_number_row(
12870    ui: &mut UiDocument,
12871    parent: UiNodeId,
12872    name: &'static str,
12873    label: &'static str,
12874    value: f32,
12875    range: std::ops::Range<f32>,
12876    decimals: u8,
12877) {
12878    let row = row(ui, parent, format!("{name}.row"), 6.0);
12879    widgets::label(
12880        ui,
12881        row,
12882        format!("{name}.label"),
12883        label,
12884        text(12.0, color(166, 176, 190)),
12885        LayoutStyle::new().with_width(48.0).with_height(22.0),
12886    );
12887    style_value_input(ui, row, name, value, range, decimals);
12888}
12889
12890fn style_inline_number(
12891    ui: &mut UiDocument,
12892    parent: UiNodeId,
12893    name: &'static str,
12894    label: &'static str,
12895    value: f32,
12896    range: std::ops::Range<f32>,
12897    decimals: u8,
12898) {
12899    let row = compact_row(ui, parent, format!("{name}.inline"), 3.0);
12900    widgets::label(
12901        ui,
12902        row,
12903        format!("{name}.inline_label"),
12904        format!("{label}:"),
12905        text(12.0, color(166, 176, 190)),
12906        LayoutStyle::new()
12907            .with_width(if label.len() > 1 { 42.0 } else { 16.0 })
12908            .with_height(22.0),
12909    );
12910    style_value_input(ui, row, name, value, range, decimals);
12911}
12912
12913fn style_value_input(
12914    ui: &mut UiDocument,
12915    parent: UiNodeId,
12916    name: &'static str,
12917    value: f32,
12918    range: std::ops::Range<f32>,
12919    decimals: u8,
12920) {
12921    let mut options = widgets::DragValueOptions::default()
12922        .with_layout(
12923            LayoutStyle::row()
12924                .with_width(STYLING_VALUE_INPUT_WIDTH)
12925                .with_height(22.0)
12926                .with_flex_shrink(0.0)
12927                .with_align_items(taffy::prelude::AlignItems::Center)
12928                .with_justify_content(taffy::prelude::JustifyContent::Center)
12929                .with_padding(4.0),
12930        )
12931        .with_range(ext_widgets::NumericRange::new(
12932            f64::from(range.start),
12933            f64::from(range.end),
12934        ))
12935        .with_precision(ext_widgets::NumericPrecision::decimals(decimals))
12936        .with_action(name);
12937    options.text_style = text(12.0, color(226, 232, 242));
12938    widgets::drag_value_input(ui, parent, name, f64::from(value), options);
12939}
12940
12941fn style_compact_checkbox(
12942    ui: &mut UiDocument,
12943    parent: UiNodeId,
12944    name: &'static str,
12945    label: &'static str,
12946    checked: bool,
12947) {
12948    let mut options = widgets::CheckboxOptions::default().with_action(name);
12949    options.layout = LayoutStyle::new().with_width(92.0).with_height(22.0);
12950    options.text_style = text(12.0, color(220, 228, 238));
12951    widgets::checkbox(ui, parent, name, label, checked, options);
12952}
12953
12954fn compact_row(
12955    ui: &mut UiDocument,
12956    parent: UiNodeId,
12957    name: impl Into<String>,
12958    gap: f32,
12959) -> UiNodeId {
12960    ui.add_child(
12961        parent,
12962        UiNode::container(
12963            name,
12964            LayoutStyle::row()
12965                .with_height(22.0)
12966                .with_flex_shrink(0.0)
12967                .with_align_items(taffy::prelude::AlignItems::Center)
12968                .gap(gap),
12969        ),
12970    )
12971}
12972
12973fn color_mini_button_options(action: &'static str) -> ext_widgets::ColorButtonOptions {
12974    ext_widgets::ColorButtonOptions::default()
12975        .with_layout(LayoutStyle::size(28.0, 24.0).with_flex_shrink(0.0))
12976        .with_swatch_size(UiSize::new(22.0, 18.0))
12977        .with_action(action)
12978        .show_label(false)
12979}
12980
12981fn style_preview(ui: &mut UiDocument, parent: UiNodeId, styling: StylingState) {
12982    let (frame, text_rect) = style_preview_rects(styling);
12983    let scene_size = style_preview_scene_size(styling);
12984    ui.add_child(
12985        parent,
12986        UiNode::scene(
12987            "styling.preview.scene",
12988            vec![
12989                ScenePrimitive::Rect(
12990                    PaintRect::solid(frame, styling.fill_color())
12991                        .stroke(AlignedStroke::inside(StrokeStyle::new(
12992                            styling.stroke_color(),
12993                            styling.stroke_width,
12994                        )))
12995                        .corner_radii(styling.radii())
12996                        .effect(PaintEffect::shadow(
12997                            styling.shadow_color(),
12998                            UiPoint::new(styling.shadow_x, styling.shadow_y),
12999                            styling.shadow_blur,
13000                            styling.shadow_spread,
13001                        )),
13002                ),
13003                ScenePrimitive::Text(
13004                    PaintText::new("Content", text_rect, text(13.0, color(255, 255, 255)))
13005                        .horizontal_align(TextHorizontalAlign::Center)
13006                        .vertical_align(TextVerticalAlign::Center)
13007                        .multiline(false),
13008                ),
13009            ],
13010            operad::layout::with_min_size(
13011                LayoutStyle::new()
13012                    .with_width_percent(1.0)
13013                    .with_height(180.0)
13014                    .with_flex_shrink(0.0),
13015                operad::layout::px(scene_size.width),
13016                operad::layout::px(scene_size.height),
13017            ),
13018        ),
13019    );
13020}
13021
13022fn style_preview_rects(styling: StylingState) -> (UiRect, UiRect) {
13023    let outer = styling.outer_edges();
13024    let inner = styling.inner_edges();
13025    let frame = UiRect::new(
13026        22.0 + outer[0],
13027        28.0 + outer[2],
13028        108.0 + inner[0] + inner[1],
13029        40.0 + inner[2] + inner[3],
13030    );
13031    let text_rect = UiRect::new(
13032        frame.x + inner[0],
13033        frame.y + inner[2],
13034        (frame.width - inner[0] - inner[1]).max(1.0),
13035        (frame.height - inner[2] - inner[3]).max(1.0),
13036    );
13037    (frame, text_rect)
13038}
13039
13040fn style_preview_scene_size(styling: StylingState) -> UiSize {
13041    let (frame, text_rect) = style_preview_rects(styling);
13042    let shadow_outset = styling.shadow_blur.max(0.0) + styling.shadow_spread.max(0.0);
13043    let shadow_bounds = UiRect::new(
13044        frame.x + styling.shadow_x - shadow_outset,
13045        frame.y + styling.shadow_y - shadow_outset,
13046        frame.width + shadow_outset * 2.0,
13047        frame.height + shadow_outset * 2.0,
13048    );
13049    let right = frame
13050        .right()
13051        .max(text_rect.right())
13052        .max(shadow_bounds.right());
13053    let bottom = frame
13054        .bottom()
13055        .max(text_rect.bottom())
13056        .max(shadow_bounds.bottom())
13057        .max(180.0);
13058    UiSize::new(right.ceil().max(1.0), bottom.ceil().max(1.0))
13059}
13060
13061fn slider_options(state: &ShowcaseState, width: f32) -> widgets::SliderOptions {
13062    let mut options = widgets::SliderOptions::default().with_layout(
13063        LayoutStyle::new()
13064            .with_width(width)
13065            .with_height(24.0)
13066            .with_flex_shrink(0.0),
13067    );
13068    options.fill_color = if state.slider_trailing_color {
13069        state.slider_trailing_picker.value()
13070    } else {
13071        color(42, 49, 58)
13072    };
13073    options.thumb_shape = match state.slider_thumb_shape {
13074        SliderThumbChoice::Circle => widgets::slider::SliderThumbShape::Circle,
13075        SliderThumbChoice::Square => widgets::slider::SliderThumbShape::Square,
13076        SliderThumbChoice::Rectangle => widgets::slider::SliderThumbShape::Rectangle,
13077    };
13078    options.thumb_visual = UiVisual::panel(
13079        state.slider_thumb_picker.value(),
13080        Some(StrokeStyle::new(color(79, 93, 113), 1.0)),
13081        6.0,
13082    );
13083    options
13084}
13085
13086#[allow(clippy::field_reassign_with_default)]
13087fn slider_number_input(
13088    ui: &mut UiDocument,
13089    parent: UiNodeId,
13090    name: &'static str,
13091    input: &TextInputState,
13092    focused: FocusedTextInput,
13093    state: &ShowcaseState,
13094    width: f32,
13095) {
13096    let mut options = TextInputOptions::default();
13097    options.layout = LayoutStyle::new().with_width(width).with_height(28.0);
13098    options.text_style = text(12.0, color(230, 236, 246));
13099    options.placeholder_style = text(12.0, color(144, 156, 174));
13100    options.edit_action = Some(format!("{name}.edit").into());
13101    options.focused = state.focused_text == Some(focused);
13102    options.caret_visible = caret_visible(state.caret_phase);
13103    widgets::text_input(ui, parent, name, input, options);
13104}
13105
13106fn form_status_chip(
13107    ui: &mut UiDocument,
13108    parent: UiNodeId,
13109    name: &'static str,
13110    label: &'static str,
13111    active: bool,
13112) {
13113    let chip = ui.add_child(
13114        parent,
13115        UiNode::container(
13116            name,
13117            LayoutStyle::new()
13118                .with_width(82.0)
13119                .with_height(24.0)
13120                .with_padding(4.0)
13121                .with_flex_shrink(0.0),
13122        )
13123        .with_visual(UiVisual::panel(
13124            if active {
13125                color(35, 74, 54)
13126            } else {
13127                color(28, 34, 43)
13128            },
13129            Some(StrokeStyle::new(
13130                if active {
13131                    color(90, 160, 112)
13132                } else {
13133                    color(60, 72, 88)
13134                },
13135                1.0,
13136            )),
13137            4.0,
13138        )),
13139    );
13140    widgets::label(
13141        ui,
13142        chip,
13143        format!("{name}.label"),
13144        label,
13145        text(11.0, color(218, 228, 240)),
13146        LayoutStyle::new()
13147            .with_width_percent(1.0)
13148            .with_height_percent(1.0),
13149    );
13150}
13151
13152fn profile_form_summary(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
13153    let has_errors = widgets::form_has_errors(&state.form);
13154    let title = profile_form_summary_title(state, has_errors);
13155    let detail = format!(
13156        "{} | {} | {}",
13157        profile_summary_value(state.form_name_text.text(), "No name"),
13158        profile_summary_value(state.form_email_text.text(), "No email"),
13159        profile_summary_value(state.form_role_text.text(), "No role"),
13160    );
13161    let hint = profile_form_summary_hint(state, has_errors);
13162    let stroke = if has_errors {
13163        color(196, 94, 104)
13164    } else if state.form.dirty {
13165        color(205, 160, 71)
13166    } else if state.form.submitted {
13167        color(91, 164, 119)
13168    } else {
13169        color(60, 72, 88)
13170    };
13171    let summary = ui.add_child(
13172        parent,
13173        UiNode::container(
13174            "forms.profile.summary",
13175            LayoutStyle::column()
13176                .with_width_percent(1.0)
13177                .with_padding(10.0)
13178                .with_gap(4.0)
13179                .with_flex_shrink(0.0),
13180        )
13181        .with_visual(UiVisual::panel(
13182            color(20, 25, 32),
13183            Some(StrokeStyle::new(stroke, 1.0)),
13184            4.0,
13185        ))
13186        .with_accessibility(
13187            AccessibilityMeta::new(AccessibilityRole::Group)
13188                .label("Live profile summary")
13189                .value(format!("{title}. {detail}. {hint}")),
13190        ),
13191    );
13192    widgets::label(
13193        ui,
13194        summary,
13195        "forms.profile.summary.title",
13196        title,
13197        text(13.0, color(232, 240, 250)),
13198        LayoutStyle::new().with_width_percent(1.0),
13199    );
13200    widgets::label(
13201        ui,
13202        summary,
13203        "forms.profile.summary.detail",
13204        detail,
13205        text(12.0, color(186, 198, 216)),
13206        LayoutStyle::new().with_width_percent(1.0),
13207    );
13208    widgets::label(
13209        ui,
13210        summary,
13211        "forms.profile.summary.hint",
13212        hint,
13213        text(11.0, color(154, 166, 184)),
13214        LayoutStyle::new().with_width_percent(1.0),
13215    );
13216}
13217
13218fn profile_form_summary_title(state: &ShowcaseState, has_errors: bool) -> &'static str {
13219    if has_errors {
13220        "Profile needs fixes"
13221    } else if state.form.submitted {
13222        "Profile submitted"
13223    } else if state.form.dirty {
13224        "Profile draft"
13225    } else {
13226        "Profile saved"
13227    }
13228}
13229
13230fn profile_form_summary_hint(state: &ShowcaseState, has_errors: bool) -> &'static str {
13231    if has_errors {
13232        "Fix validation errors before applying or submitting."
13233    } else if state.form.dirty {
13234        "Apply saves the draft; Submit saves and marks it submitted."
13235    } else if state.form.submitted {
13236        "Submission completed. Apply stays disabled until something changes."
13237    } else {
13238        "No pending changes. Submit marks the saved profile submitted."
13239    }
13240}
13241
13242fn profile_summary_value<'a>(value: &'a str, empty: &'static str) -> &'a str {
13243    let value = value.trim();
13244    if value.is_empty() {
13245        empty
13246    } else {
13247        value
13248    }
13249}
13250
13251#[allow(clippy::field_reassign_with_default)]
13252fn form_text_field(
13253    ui: &mut UiDocument,
13254    parent: UiNodeId,
13255    name: &'static str,
13256    input: &TextInputState,
13257    focused: FocusedTextInput,
13258    state: &ShowcaseState,
13259) {
13260    let mut options = TextInputOptions::default();
13261    options.layout = LayoutStyle::new().with_width_percent(1.0).with_height(30.0);
13262    options.text_style = text(12.0, color(230, 236, 246));
13263    options.placeholder_style = text(12.0, color(144, 156, 174));
13264    options.placeholder = "Required".to_string();
13265    options.edit_action = Some(format!("{name}.edit").into());
13266    options.focused = state.focused_text == Some(focused);
13267    options.caret_visible = caret_visible(state.caret_phase);
13268    widgets::text_input(ui, parent, name, input, options);
13269}
13270
13271fn profile_email_valid(email: &str) -> bool {
13272    let email = email.trim();
13273    let Some((local, domain)) = email.split_once('@') else {
13274        return false;
13275    };
13276    !local.is_empty() && domain.contains('.') && !domain.ends_with('.')
13277}
13278
13279fn drag_source_layout() -> LayoutStyle {
13280    LayoutStyle::row()
13281        .with_width(128.0)
13282        .with_height(40.0)
13283        .with_padding(8.0)
13284        .with_gap(6.0)
13285        .with_flex_shrink(0.0)
13286}
13287
13288fn drop_zone_layout() -> LayoutStyle {
13289    LayoutStyle::column()
13290        .with_width(128.0)
13291        .with_height(78.0)
13292        .with_padding(10.0)
13293        .with_gap(6.0)
13294        .with_flex_shrink(0.0)
13295}
13296
13297fn dnd_operation_chip(
13298    ui: &mut UiDocument,
13299    parent: UiNodeId,
13300    name: &'static str,
13301    label: &'static str,
13302) {
13303    let chip = ui.add_child(
13304        parent,
13305        UiNode::container(
13306            name,
13307            LayoutStyle::new()
13308                .with_width(58.0)
13309                .with_height(22.0)
13310                .with_padding(3.0)
13311                .with_flex_shrink(0.0),
13312        )
13313        .with_visual(UiVisual::panel(
13314            color(26, 32, 42),
13315            Some(StrokeStyle::new(color(62, 76, 94), 1.0)),
13316            3.0,
13317        )),
13318    );
13319    widgets::label(
13320        ui,
13321        chip,
13322        format!("{name}.label"),
13323        label,
13324        text(11.0, color(190, 204, 222)),
13325        LayoutStyle::new()
13326            .with_width_percent(1.0)
13327            .with_height_percent(1.0),
13328    );
13329}
13330
13331fn media_preview_image_layout() -> LayoutStyle {
13332    LayoutStyle::size(46.0, 46.0).with_flex_shrink(0.0)
13333}
13334
13335fn media_icon_columns(state: &ShowcaseState) -> usize {
13336    let theme = state.app_theme();
13337    let options = showcase_desktop_options(state.last_desktop_size, &theme);
13338    let window_width = state
13339        .desktop
13340        .size("media", default_window_size("media"))
13341        .width;
13342    let content_width = (window_width - options.content_padding * 2.0).max(MEDIA_ICON_TILE_WIDTH);
13343    let pitch = MEDIA_ICON_TILE_WIDTH + MEDIA_ICON_GRID_GAP;
13344    (((content_width + MEDIA_ICON_GRID_GAP) / pitch).floor() as usize).clamp(1, MEDIA_ICON_COLUMNS)
13345}
13346
13347fn media_icon_grid_width(columns: usize) -> f32 {
13348    let columns = columns.max(1);
13349    columns as f32 * MEDIA_ICON_TILE_WIDTH + columns.saturating_sub(1) as f32 * MEDIA_ICON_GRID_GAP
13350}
13351
13352fn media_icon_grid_height(columns: usize, item_count: usize) -> f32 {
13353    let columns = columns.max(1);
13354    let rows = item_count.div_ceil(columns).max(1);
13355    rows as f32 * MEDIA_ICON_TILE_HEIGHT + rows.saturating_sub(1) as f32 * MEDIA_ICON_GRID_GAP
13356}
13357
13358fn media_icon_grid(
13359    ui: &mut UiDocument,
13360    parent: UiNodeId,
13361    name: impl Into<String>,
13362    columns: usize,
13363    item_count: usize,
13364) -> UiNodeId {
13365    let columns = columns.clamp(1, MEDIA_ICON_COLUMNS);
13366    let rows = item_count.div_ceil(columns).max(1);
13367    let width = media_icon_grid_width(columns);
13368    let height = media_icon_grid_height(columns, item_count);
13369    let layout = operad::layout::with_grid_template_rows(
13370        operad::layout::with_grid_template_columns(
13371            Layout::grid()
13372                .size(LayoutSize::points(width, height))
13373                .gap(LayoutGap::points(MEDIA_ICON_GRID_GAP, MEDIA_ICON_GRID_GAP))
13374                .flex(0.0, 0.0, LayoutDimension::Auto)
13375                .to_layout_style(),
13376            (0..columns).map(|_| LayoutGridTrack::points(MEDIA_ICON_TILE_WIDTH)),
13377        ),
13378        (0..rows).map(|_| LayoutGridTrack::points(MEDIA_ICON_TILE_HEIGHT)),
13379    );
13380    ui.add_child(parent, UiNode::container(name, layout))
13381}
Source

pub const fn fraction(value: f32) -> Self

Source

pub const fn minmax_points_fraction(min: f32, max_fraction: f32) -> Self

Examples found in repository?
examples/showcase.rs (line 12490)
12470fn styling_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
12471    let preview_scene_size = style_preview_scene_size(state.styling);
12472    let preview_min_width = preview_scene_size.width + 16.0;
12473    let preview_min_height = preview_scene_size.height + 16.0;
12474    let body_min_width = STYLING_CONTROLS_WIDTH + 1.0 + preview_min_width + 20.0;
12475    let body = section_with_min_viewport(
12476        ui,
12477        parent,
12478        "styling",
12479        "Styling",
12480        UiSize::new(body_min_width, preview_min_height),
12481    );
12482    let grid_layout = operad::layout::with_grid_template_columns(
12483        Layout::grid()
12484            .size(LayoutSize::percent(1.0, 1.0))
12485            .gap(LayoutGap::points(10.0, 10.0))
12486            .to_layout_style(),
12487        [
12488            LayoutGridTrack::points(STYLING_CONTROLS_WIDTH),
12489            LayoutGridTrack::points(1.0),
12490            LayoutGridTrack::minmax_points_fraction(preview_min_width, 1.0),
12491        ],
12492    );
12493    let grid = ui.add_child(body, UiNode::container("styling.grid", grid_layout));
12494    let controls = ui.add_child(
12495        grid,
12496        UiNode::container(
12497            "styling.controls",
12498            LayoutStyle::column()
12499                .with_width(STYLING_CONTROLS_WIDTH)
12500                .with_height_percent(1.0)
12501                .with_flex_shrink(0.0)
12502                .gap(6.0),
12503        ),
12504    );
12505    style_edge_group(
12506        ui,
12507        controls,
12508        "styling.inner",
12509        "Inner margin",
12510        "styling.inner_same",
12511        state.styling.inner_same,
12512        [
12513            ("Left", "styling.inner", state.styling.inner_margin),
12514            ("Right", "styling.inner_right", state.styling.inner_right),
12515            ("Top", "styling.inner_top", state.styling.inner_top),
12516            ("Bottom", "styling.inner_bottom", state.styling.inner_bottom),
12517        ],
12518        0.0..32.0,
12519    );
12520    style_edge_group(
12521        ui,
12522        controls,
12523        "styling.outer",
12524        "Outer margin",
12525        "styling.outer_same",
12526        state.styling.outer_same,
12527        [
12528            ("Left", "styling.outer", state.styling.outer_margin),
12529            ("Right", "styling.outer_right", state.styling.outer_right),
12530            ("Top", "styling.outer_top", state.styling.outer_top),
12531            ("Bottom", "styling.outer_bottom", state.styling.outer_bottom),
12532        ],
12533        0.0..40.0,
12534    );
12535    style_edge_group(
12536        ui,
12537        controls,
12538        "styling.radius",
12539        "Corner radius",
12540        "styling.radius_same",
12541        state.styling.radius_same,
12542        [
12543            ("NW", "styling.radius", state.styling.corner_radius),
12544            ("NE", "styling.radius_ne", state.styling.corner_ne),
12545            ("SW", "styling.radius_sw", state.styling.corner_sw),
12546            ("SE", "styling.radius_se", state.styling.corner_se),
12547        ],
12548        0.0..28.0,
12549    );
12550    style_fill_group(ui, controls, state);
12551    style_stroke_group(ui, controls, state);
12552    style_shadow_group(ui, controls, state);
12553    widgets::separator(
12554        ui,
12555        grid,
12556        "styling.preview.separator",
12557        widgets::SeparatorOptions::vertical().with_layout(
12558            LayoutStyle::new()
12559                .with_width(1.0)
12560                .with_height_percent(1.0)
12561                .with_flex_shrink(0.0),
12562        ),
12563    );
12564
12565    let preview = ui.add_child(
12566        grid,
12567        UiNode::container(
12568            "styling.preview",
12569            operad::layout::with_min_size(
12570                LayoutStyle::column()
12571                    .with_width_percent(1.0)
12572                    .with_height_percent(1.0)
12573                    .with_flex_shrink(0.0)
12574                    .padding(8.0),
12575                operad::layout::px(preview_min_width),
12576                operad::layout::px(preview_min_height),
12577            ),
12578        )
12579        .with_visual(UiVisual::panel(color(17, 20, 25), None, 0.0)),
12580    );
12581    style_preview(ui, preview, state.styling);
12582}

Trait Implementations§

Source§

impl Clone for LayoutGridTrack

Source§

fn clone(&self) -> LayoutGridTrack

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 LayoutGridTrack

Source§

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

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

impl PartialEq for LayoutGridTrack

Source§

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

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

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

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

impl Copy for LayoutGridTrack

Source§

impl StructuralPartialEq for LayoutGridTrack

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 + Sync + Send>

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

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

fn in_current_span(self) -> Instrumented<Self>

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

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

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

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

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

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

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

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

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

Source§

impl<T> WithSubscriber for T

Source§

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

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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

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

Source§

impl<T> WasmNotSendSync for T

Source§

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