pub enum LayoutGridTrack {
Auto,
Points(f32),
Fraction(f32),
MinMaxPointsFraction {
min: f32,
max_fraction: f32,
},
}Variants§
Implementations§
Source§impl LayoutGridTrack
impl LayoutGridTrack
pub const fn auto() -> Self
Sourcepub const fn points(value: f32) -> Self
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}pub const fn fraction(value: f32) -> Self
Sourcepub const fn minmax_points_fraction(min: f32, max_fraction: f32) -> Self
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
impl Clone for LayoutGridTrack
Source§fn clone(&self) -> LayoutGridTrack
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)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for LayoutGridTrack
impl Debug for LayoutGridTrack
Source§impl PartialEq for LayoutGridTrack
impl PartialEq for LayoutGridTrack
Source§fn eq(&self, other: &LayoutGridTrack) -> bool
fn eq(&self, other: &LayoutGridTrack) -> bool
Tests for
self and other values to be equal, and is used by ==.impl Copy for LayoutGridTrack
impl StructuralPartialEq for LayoutGridTrack
Auto Trait Implementations§
impl Freeze for LayoutGridTrack
impl RefUnwindSafe for LayoutGridTrack
impl Send for LayoutGridTrack
impl Sync for LayoutGridTrack
impl Unpin for LayoutGridTrack
impl UnsafeUnpin for LayoutGridTrack
impl UnwindSafe for LayoutGridTrack
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.