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 Copy for LayoutGridTrack

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

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

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

Inequality operator !=. Read more
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 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> Downcast<T> for T

Source§

fn downcast(&self) -> &T

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> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

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

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