form: Add support for multi-column layout (#1250)
| Vertical | Horizontal | | - | - | | <img width="1642" height="1106" alt="SCR-20250916-nnry" src="https://github.com/user-attachments/assets/279eb192-c660-449e-931e-1d7ff79ac69f" /> | <img width="1642" height="1106" alt="image" src="https://github.com/user-attachments/assets/289a6672-d82d-4f6f-a267-592874ccbe42" /> |
This commit is contained in:
parent
717c83957d
commit
731ab958f8
2 changed files with 111 additions and 46 deletions
|
|
@ -26,6 +26,7 @@ pub struct FormStory {
|
|||
date: Entity<DatePickerState>,
|
||||
layout: Axis,
|
||||
size: Size,
|
||||
column: u16,
|
||||
}
|
||||
|
||||
impl super::Story for FormStory {
|
||||
|
|
@ -89,6 +90,7 @@ impl FormStory {
|
|||
subscribe_email: false,
|
||||
layout: Axis::Vertical,
|
||||
size: Size::default(),
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +116,9 @@ impl Focusable for FormStory {
|
|||
|
||||
impl Render for FormStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_multi_column = self.column > 1;
|
||||
let is_horizontal = self.layout.is_horizontal();
|
||||
|
||||
v_flex()
|
||||
.id("form-story")
|
||||
.size_full()
|
||||
|
|
@ -126,17 +131,34 @@ impl Render for FormStory {
|
|||
.flex_wrap()
|
||||
.justify_between()
|
||||
.child(
|
||||
Switch::new("layout")
|
||||
.checked(self.layout.is_horizontal())
|
||||
.label("Horizontal")
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
if *checked {
|
||||
this.layout = Axis::Horizontal;
|
||||
} else {
|
||||
this.layout = Axis::Vertical;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
h_flex()
|
||||
.gap_x_3()
|
||||
.child(
|
||||
Switch::new("layout")
|
||||
.checked(self.layout.is_horizontal())
|
||||
.label("Horizontal")
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
if *checked {
|
||||
this.layout = Axis::Horizontal;
|
||||
} else {
|
||||
this.layout = Axis::Vertical;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Switch::new("column")
|
||||
.checked(self.column > 1)
|
||||
.label("Multi Column")
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
if *checked {
|
||||
this.column = 2;
|
||||
} else {
|
||||
this.column = 1;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
ButtonGroup::new("size")
|
||||
|
|
@ -174,6 +196,8 @@ impl Render for FormStory {
|
|||
v_form()
|
||||
.layout(self.layout)
|
||||
.with_size(self.size)
|
||||
.column(self.column)
|
||||
.label_width(px(if is_multi_column { 100. } else { 140. }))
|
||||
.child(
|
||||
form_field().label_fn(|_, _| "Name").child(
|
||||
h_flex()
|
||||
|
|
@ -211,46 +235,60 @@ impl Render for FormStory {
|
|||
.child(
|
||||
form_field()
|
||||
.no_label_indent()
|
||||
.when(is_multi_column, |this| this.col_span(2))
|
||||
.child("This is a full width form field."),
|
||||
)
|
||||
.child(
|
||||
form_field()
|
||||
.label("Please select your birthday")
|
||||
.child(DatePicker::new(&self.date))
|
||||
.description("Select your birthday, we will send you a gift."),
|
||||
.description("Select your birthday, we will send you a gift.")
|
||||
.when(is_multi_column, |this| this.col_span(2))
|
||||
.child(DatePicker::new(&self.date)),
|
||||
)
|
||||
.child(
|
||||
form_field().child(
|
||||
Switch::new("subscribe-newsletter")
|
||||
.label("Subscribe our newsletter")
|
||||
.checked(self.subscribe_email)
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
this.subscribe_email = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
form_field()
|
||||
.when(is_horizontal && is_multi_column, |this| {
|
||||
this.no_label_indent()
|
||||
})
|
||||
.child(
|
||||
Switch::new("subscribe-newsletter")
|
||||
.label("Subscribe our newsletter")
|
||||
.checked(self.subscribe_email)
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
this.subscribe_email = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
form_field().child(
|
||||
ColorPicker::new(&self.color_state)
|
||||
.small()
|
||||
.label("Theme color"),
|
||||
),
|
||||
form_field()
|
||||
.when(is_horizontal && is_multi_column, |this| {
|
||||
this.no_label_indent()
|
||||
})
|
||||
.child(
|
||||
ColorPicker::new(&self.color_state)
|
||||
.small()
|
||||
.label("Theme color"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
form_field().child(
|
||||
Checkbox::new("use-vertical-layout")
|
||||
.label("Vertical layout")
|
||||
.checked(self.layout.is_vertical())
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
this.layout = if *checked {
|
||||
Axis::Vertical
|
||||
} else {
|
||||
Axis::Horizontal
|
||||
};
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
form_field()
|
||||
.when(is_horizontal && is_multi_column, |this| {
|
||||
this.no_label_indent()
|
||||
})
|
||||
.child(
|
||||
Checkbox::new("use-vertical-layout")
|
||||
.label("Vertical layout")
|
||||
.checked(self.layout.is_vertical())
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
this.layout = if *checked {
|
||||
Axis::Vertical
|
||||
} else {
|
||||
Axis::Horizontal
|
||||
};
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ struct FieldProps {
|
|||
layout: Axis,
|
||||
/// Field gap
|
||||
gap: Option<Pixels>,
|
||||
column: u16,
|
||||
}
|
||||
|
||||
impl Default for FieldProps {
|
||||
|
|
@ -47,6 +48,7 @@ impl Default for FieldProps {
|
|||
layout: Axis::Vertical,
|
||||
size: Size::default(),
|
||||
gap: None,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,6 +106,14 @@ impl Form {
|
|||
self.fields.extend(fields);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the column count for the form.
|
||||
///
|
||||
/// Default is 1.
|
||||
pub fn column(mut self, column: u16) -> Self {
|
||||
self.props.column = column;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for Form {
|
||||
|
|
@ -186,6 +196,7 @@ pub struct FormField {
|
|||
/// Alignment of the form field.
|
||||
align_items: Option<AlignItems>,
|
||||
props: FieldProps,
|
||||
col_span: u16,
|
||||
}
|
||||
|
||||
impl FormField {
|
||||
|
|
@ -202,6 +213,7 @@ impl FormField {
|
|||
focus_handle: None,
|
||||
align_items: None,
|
||||
props: FieldProps::default(),
|
||||
col_span: 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,6 +314,14 @@ impl FormField {
|
|||
self.align_items = Some(AlignItems::Center);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the column span for the form field.
|
||||
///
|
||||
/// Default is 1.
|
||||
pub fn col_span(mut self, col_span: u16) -> Self {
|
||||
self.col_span = col_span;
|
||||
self
|
||||
}
|
||||
}
|
||||
impl ParentElement for FormField {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
|
|
@ -351,6 +371,7 @@ impl RenderOnce for FormField {
|
|||
v_flex()
|
||||
.flex_1()
|
||||
.gap(gap / 2.)
|
||||
.col_span(self.col_span)
|
||||
.child(
|
||||
// This warp for aligning the Label + Input
|
||||
wrap_div(layout)
|
||||
|
|
@ -433,11 +454,17 @@ impl RenderOnce for Form {
|
|||
_ => px(8.),
|
||||
};
|
||||
|
||||
v_flex().w_full().gap(gap).children(
|
||||
self.fields
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, field)| field.props(ix, props)),
|
||||
)
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_x(gap * 3.)
|
||||
.gap_y(gap)
|
||||
.grid()
|
||||
.grid_cols(props.column)
|
||||
.children(
|
||||
self.fields
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, field)| field.props(ix, props)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue