mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
Layout alignment
This commit is contained in:
parent
fa077662c1
commit
4a182ef4d0
9 changed files with 152 additions and 19 deletions
|
@ -283,6 +283,7 @@ using cbindgen_private::solve_box_layout;
|
|||
using cbindgen_private::box_layout_info;
|
||||
using cbindgen_private::solve_path_layout;
|
||||
using cbindgen_private::Rect;
|
||||
using cbindgen_private::LayoutAlignment;
|
||||
|
||||
inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const {
|
||||
// Note: This "logic" is duplicated from LayoutInfo::merge in layout.rs.
|
||||
|
|
|
@ -187,6 +187,8 @@ they will be computed by the layout respecting the minimum and maximum sizes and
|
|||
* **`padding`** (*length*): the padding within the layout.
|
||||
* **`padding_left`**, **`padding_right`**, **`padding_top`** and **`padding_bottom`** (*length*):
|
||||
override the padding in specific sides.
|
||||
* **`alignment`** (*FIXME enum*): Can be one of `stretch`, `center`, `start`, `end`,
|
||||
`space_between`, `space_around`. Defaults to `stretch`. Matches the CSS flex.
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
@ -1679,11 +1679,18 @@ impl crate::layout::gen::Language for CppLanguageLayoutGen {
|
|||
component,
|
||||
);
|
||||
|
||||
let alignment = if let Some(nr) = &box_layout.geometry.alignment {
|
||||
format!("{}.get()", access_named_reference(nr, component, "self"))
|
||||
} else {
|
||||
"{}".into()
|
||||
};
|
||||
|
||||
LayoutTreeItem::BoxLayout {
|
||||
is_horizontal: box_layout.is_horizontal,
|
||||
geometry: &box_layout.geometry,
|
||||
spacing,
|
||||
padding,
|
||||
alignment,
|
||||
var_creation_code: creation_code.join("\n"),
|
||||
cell_ref_variable,
|
||||
}
|
||||
|
@ -1703,11 +1710,12 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
spacing,
|
||||
cell_ref_variable,
|
||||
padding,
|
||||
alignment,
|
||||
is_horizontal,
|
||||
..
|
||||
} => format!(
|
||||
"sixtyfps::box_layout_info(&{}, {}, &{}, {})",
|
||||
cell_ref_variable, spacing, padding, is_horizontal
|
||||
"sixtyfps::box_layout_info(&{}, {}, &{}, {}, {})",
|
||||
cell_ref_variable, spacing, padding, alignment, is_horizontal
|
||||
),
|
||||
LayoutTreeItem::PathLayout(_) => "{/*layout_info for path not implemented*/}".into(),
|
||||
}
|
||||
|
@ -1822,19 +1830,21 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
spacing,
|
||||
cell_ref_variable,
|
||||
padding,
|
||||
alignment,
|
||||
is_horizontal,
|
||||
..
|
||||
} => {
|
||||
code_stream.push(" { ".into());
|
||||
code_stream.push(" sixtyfps::BoxLayoutData box { ".into());
|
||||
code_stream.push(format!(
|
||||
" {w}, {h}, {x}, {y}, {s}, &{p},",
|
||||
" {w}, {h}, {x}, {y}, {s}, &{p}, {a},",
|
||||
w = layout_prop(&geometry.rect.width_reference),
|
||||
h = layout_prop(&geometry.rect.height_reference),
|
||||
x = layout_prop(&geometry.rect.x_reference),
|
||||
y = layout_prop(&geometry.rect.y_reference),
|
||||
s = spacing,
|
||||
p = padding,
|
||||
a = alignment
|
||||
));
|
||||
code_stream.push(format!(" {cv}", cv = cell_ref_variable));
|
||||
code_stream.push(" };".to_owned());
|
||||
|
|
|
@ -47,6 +47,10 @@ fn rust_type(
|
|||
let inner = rust_type(&o, span)?;
|
||||
Ok(quote!(sixtyfps::re_exports::ModelHandle<#inner>))
|
||||
}
|
||||
Type::Enumeration(e) => {
|
||||
let e = format_ident!("{}", e.name);
|
||||
Ok(quote!(sixtyfps::re_exports::#e))
|
||||
}
|
||||
_ => Err(CompilerDiagnostic {
|
||||
message: format!("Cannot map property type {} to Rust", ty),
|
||||
span: span.clone(),
|
||||
|
@ -1513,6 +1517,13 @@ impl crate::layout::gen::Language for RustLanguageLayoutGen {
|
|||
let (padding, spacing, spacing_creation_code) =
|
||||
generate_layout_padding_and_spacing(&layout_tree, &box_layout.geometry, component);
|
||||
|
||||
let alignment = if let Some(expr) = &box_layout.geometry.alignment {
|
||||
let p = access_named_reference(expr, component, quote!(_self));
|
||||
quote!(#p.get())
|
||||
} else {
|
||||
quote!(::core::default::Default::default())
|
||||
};
|
||||
|
||||
LayoutTreeItem::BoxLayout {
|
||||
is_horizontal: box_layout.is_horizontal,
|
||||
geometry: &box_layout.geometry,
|
||||
|
@ -1520,6 +1531,7 @@ impl crate::layout::gen::Language for RustLanguageLayoutGen {
|
|||
cell_ref_variable: quote!(#cell_ref_variable),
|
||||
spacing,
|
||||
padding,
|
||||
alignment,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -1537,10 +1549,11 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
cell_ref_variable,
|
||||
spacing,
|
||||
padding,
|
||||
alignment,
|
||||
is_horizontal,
|
||||
..
|
||||
} => {
|
||||
quote!(box_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding, #is_horizontal))
|
||||
quote!(box_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding, #alignment, #is_horizontal))
|
||||
}
|
||||
LayoutTreeItem::PathLayout(_) => quote!(todo!("layout_info for PathLayout in rust.rs")),
|
||||
}
|
||||
|
@ -1629,7 +1642,7 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
let p = access_named_reference(nr, component, quote!(_self));
|
||||
quote!(#p.get())
|
||||
} else {
|
||||
quote!(0.)
|
||||
quote!(::core::default::Default::default())
|
||||
}
|
||||
};
|
||||
match self {
|
||||
|
@ -1645,8 +1658,8 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
solve_grid_layout(&GridLayoutData {
|
||||
width: #width,
|
||||
height: #height,
|
||||
x: #x_pos as _,
|
||||
y: #y_pos as _,
|
||||
x: #x_pos,
|
||||
y: #y_pos,
|
||||
cells: Slice::from_slice(&#cell_ref_variable),
|
||||
spacing: #spacing,
|
||||
padding: #padding,
|
||||
|
@ -1658,6 +1671,7 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
cell_ref_variable,
|
||||
spacing,
|
||||
padding,
|
||||
alignment,
|
||||
is_horizontal,
|
||||
..
|
||||
} => {
|
||||
|
@ -1670,11 +1684,12 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
solve_box_layout(&BoxLayoutData {
|
||||
width: #width,
|
||||
height: #height,
|
||||
x: #x_pos as _,
|
||||
y: #y_pos as _,
|
||||
x: #x_pos,
|
||||
y: #y_pos,
|
||||
cells: Slice::from_slice(&#cell_ref_variable),
|
||||
spacing: #spacing,
|
||||
padding: #padding,
|
||||
alignment: #alignment
|
||||
}, #is_horizontal);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -214,6 +214,7 @@ impl Padding {
|
|||
pub struct LayoutGeometry {
|
||||
pub rect: LayoutRect,
|
||||
pub spacing: Option<NamedReference>,
|
||||
pub alignment: Option<NamedReference>,
|
||||
pub padding: Padding,
|
||||
}
|
||||
|
||||
|
@ -221,6 +222,7 @@ impl LayoutGeometry {
|
|||
fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
|
||||
self.rect.visit_named_references(visitor);
|
||||
self.spacing.as_mut().map(|e| visitor(&mut *e));
|
||||
self.alignment.as_mut().map(|e| visitor(&mut *e));
|
||||
self.padding.visit_named_references(visitor);
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +256,7 @@ impl LayoutGeometry {
|
|||
pub fn new(rect: LayoutRect, layout_element: &ElementRc) -> Self {
|
||||
let padding = || binding_reference(layout_element, "padding");
|
||||
let spacing = binding_reference(layout_element, "spacing");
|
||||
let alignment = binding_reference(layout_element, "alignment");
|
||||
|
||||
init_fake_property(layout_element, "width", || rect.width_reference.clone());
|
||||
init_fake_property(layout_element, "height", || rect.height_reference.clone());
|
||||
|
@ -271,7 +274,7 @@ impl LayoutGeometry {
|
|||
bottom: binding_reference(layout_element, "padding_bottom").or_else(padding),
|
||||
};
|
||||
|
||||
Self { rect, spacing, padding }
|
||||
Self { rect, spacing, padding, alignment }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,6 +380,7 @@ pub mod gen {
|
|||
geometry: &'a LayoutGeometry,
|
||||
spacing: L::CompiledCode,
|
||||
padding: L::CompiledCode,
|
||||
alignment: L::CompiledCode,
|
||||
var_creation_code: L::CompiledCode,
|
||||
cell_ref_variable: L::CompiledCode,
|
||||
is_horizontal: bool,
|
||||
|
|
|
@ -96,6 +96,10 @@ impl TypeRegister {
|
|||
declare_enum("TextHorizontalAlignment", &["align_left", "align_center", "align_right"]);
|
||||
let text_vertical_alignment =
|
||||
declare_enum("TextVerticalAlignment", &["align_top", "align_center", "align_bottom"]);
|
||||
let layout_alignment = declare_enum(
|
||||
"LayoutAlignment",
|
||||
&["stretch", "center", "start", "end", "space_between", "space_around"],
|
||||
);
|
||||
|
||||
let native_class_with_member_functions =
|
||||
|tr: &mut TypeRegister,
|
||||
|
@ -296,9 +300,15 @@ impl TypeRegister {
|
|||
let mut horizontal_layout =
|
||||
BuiltinElement::new(Rc::new(NativeClass::new("HorizontalLayout")));
|
||||
horizontal_layout.properties.insert("spacing".to_owned(), Type::Length);
|
||||
horizontal_layout
|
||||
.properties
|
||||
.insert("alignment".to_owned(), Type::Enumeration(layout_alignment.clone()));
|
||||
register.insert_type(Type::Builtin(Rc::new(horizontal_layout)));
|
||||
let mut vertical_layout = BuiltinElement::new(Rc::new(NativeClass::new("VerticalLayout")));
|
||||
vertical_layout.properties.insert("spacing".to_owned(), Type::Length);
|
||||
vertical_layout
|
||||
.properties
|
||||
.insert("alignment".to_owned(), Type::Enumeration(layout_alignment));
|
||||
register.insert_type(Type::Builtin(Rc::new(vertical_layout)));
|
||||
|
||||
let mut path_class = NativeClass::new("Path");
|
||||
|
|
|
@ -382,6 +382,51 @@ pub extern "C" fn grid_layout_info<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum representing the alignment property of a BoxLayout or HorizontalLayout
|
||||
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::EnumString, strum_macros::Display)]
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum LayoutAlignment {
|
||||
stretch,
|
||||
center,
|
||||
start,
|
||||
end,
|
||||
space_between,
|
||||
space_around,
|
||||
}
|
||||
|
||||
impl Default for LayoutAlignment {
|
||||
fn default() -> Self {
|
||||
Self::stretch
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutAlignment> for stretch::style::JustifyContent {
|
||||
fn from(a: LayoutAlignment) -> Self {
|
||||
match a {
|
||||
LayoutAlignment::stretch => Self::FlexStart,
|
||||
LayoutAlignment::center => Self::Center,
|
||||
LayoutAlignment::start => Self::FlexStart,
|
||||
LayoutAlignment::end => Self::FlexEnd,
|
||||
LayoutAlignment::space_between => Self::SpaceBetween,
|
||||
LayoutAlignment::space_around => Self::SpaceAround,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutAlignment> for stretch::style::AlignContent {
|
||||
fn from(a: LayoutAlignment) -> Self {
|
||||
match a {
|
||||
LayoutAlignment::stretch => Self::Stretch,
|
||||
LayoutAlignment::center => Self::Center,
|
||||
LayoutAlignment::start => Self::FlexStart,
|
||||
LayoutAlignment::end => Self::FlexEnd,
|
||||
LayoutAlignment::space_between => Self::SpaceBetween,
|
||||
LayoutAlignment::space_around => Self::SpaceAround,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
/// The BoxLayoutData is used to represent both a Horizontal and Vertical layout.
|
||||
|
@ -394,6 +439,7 @@ pub struct BoxLayoutData<'a> {
|
|||
pub y: Coord,
|
||||
pub spacing: Coord,
|
||||
pub padding: &'a Padding,
|
||||
pub alignment: LayoutAlignment,
|
||||
pub cells: Slice<'a, BoxLayoutCellData<'a>>,
|
||||
}
|
||||
|
||||
|
@ -418,10 +464,10 @@ pub extern "C" fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
|
|||
|
||||
let box_style = stretch::style::Style {
|
||||
size: Size { width: Dimension::Percent(1.), height: Dimension::Percent(1.) },
|
||||
flex_grow: 1.,
|
||||
display: Display::Flex,
|
||||
flex_direction: if is_horizontal { FlexDirection::Row } else { FlexDirection::Column },
|
||||
flex_basis: Dimension::Percent(1.),
|
||||
justify_content: data.alignment.into(),
|
||||
align_content: data.alignment.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -469,7 +515,13 @@ pub extern "C" fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
|
|||
let cell_style = Style {
|
||||
min_size,
|
||||
max_size,
|
||||
flex_grow: if let Some(s) = smaller_strecth { stretch_factor(cell) / s } else { 1. },
|
||||
flex_grow: if data.alignment != LayoutAlignment::stretch {
|
||||
0.
|
||||
} else if let Some(s) = smaller_strecth {
|
||||
stretch_factor(cell) / s
|
||||
} else {
|
||||
1.
|
||||
},
|
||||
flex_basis: if is_horizontal { min_size.width } else { min_size.height },
|
||||
margin,
|
||||
align_self: AlignSelf::Stretch,
|
||||
|
@ -509,6 +561,7 @@ pub extern "C" fn box_layout_info<'a>(
|
|||
cells: &Slice<'a, BoxLayoutCellData<'a>>,
|
||||
spacing: Coord,
|
||||
padding: &Padding,
|
||||
alignment: LayoutAlignment,
|
||||
is_horizontal: bool,
|
||||
) -> LayoutInfo {
|
||||
let count = cells.len();
|
||||
|
@ -516,6 +569,7 @@ pub extern "C" fn box_layout_info<'a>(
|
|||
return LayoutInfo { max_width: 0., max_height: 0., ..LayoutInfo::default() };
|
||||
};
|
||||
let order_float = |a: &Coord, b: &Coord| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal);
|
||||
let is_stretch = alignment == LayoutAlignment::stretch;
|
||||
|
||||
if is_horizontal {
|
||||
let extra_w = padding.left + padding.right + spacing * (count - 1) as Coord;
|
||||
|
@ -527,7 +581,11 @@ pub extern "C" fn box_layout_info<'a>(
|
|||
+ padding.top
|
||||
+ padding.bottom;
|
||||
let min_width = cells.iter().map(|c| c.constraint.min_width).sum::<Coord>() + extra_w;
|
||||
let max_width = cells.iter().map(|c| c.constraint.max_width).sum::<Coord>() + extra_w;
|
||||
let max_width = if is_stretch {
|
||||
cells.iter().map(|c| c.constraint.max_width).sum::<Coord>() + extra_w
|
||||
} else {
|
||||
f32::MAX
|
||||
};
|
||||
let horizontal_stretch = cells.iter().map(|c| c.constraint.horizontal_stretch).sum::<f32>();
|
||||
let vertical_stretch =
|
||||
cells.iter().map(|c| c.constraint.vertical_stretch).min_by(order_float).unwrap();
|
||||
|
@ -549,7 +607,11 @@ pub extern "C" fn box_layout_info<'a>(
|
|||
+ padding.left
|
||||
+ padding.right;
|
||||
let min_height = cells.iter().map(|c| c.constraint.min_height).sum::<Coord>() + extra_h;
|
||||
let max_height = cells.iter().map(|c| c.constraint.max_height).sum::<Coord>() + extra_h;
|
||||
let max_height = if is_stretch {
|
||||
cells.iter().map(|c| c.constraint.max_height).sum::<Coord>() + extra_h
|
||||
} else {
|
||||
f32::MAX
|
||||
};
|
||||
let horizontal_stretch =
|
||||
cells.iter().map(|c| c.constraint.horizontal_stretch).min_by(order_float).unwrap();
|
||||
let vertical_stretch = cells.iter().map(|c| c.constraint.vertical_stretch).sum::<f32>();
|
||||
|
|
|
@ -593,7 +593,7 @@ fn generate_component<'id>(
|
|||
if decl.is_alias.is_some() {
|
||||
continue;
|
||||
}
|
||||
let (prop, type_info) = match decl.property_type {
|
||||
let (prop, type_info) = match &decl.property_type {
|
||||
Type::Float32 => animated_property_info::<f32>(),
|
||||
Type::Int32 => animated_property_info::<i32>(),
|
||||
Type::String => property_info::<SharedString>(),
|
||||
|
@ -611,6 +611,16 @@ fn generate_component<'id>(
|
|||
Type::Object { .. } => property_info::<eval::Value>(),
|
||||
Type::Array(_) => property_info::<eval::Value>(),
|
||||
Type::Percent => property_info::<f32>(),
|
||||
Type::Enumeration(e) => match e.name.as_ref() {
|
||||
"LayoutAlignment" => property_info::<sixtyfps_corelib::layout::LayoutAlignment>(),
|
||||
"TextHorizontalAlignment" => {
|
||||
property_info::<sixtyfps_corelib::items::TextHorizontalAlignment>()
|
||||
}
|
||||
"TextVerticalAlignment" => {
|
||||
property_info::<sixtyfps_corelib::items::TextVerticalAlignment>()
|
||||
}
|
||||
_ => panic!("unkown enum"),
|
||||
},
|
||||
_ => panic!("bad type"),
|
||||
};
|
||||
custom_properties.insert(
|
||||
|
@ -970,7 +980,11 @@ impl<'a> BoxLayoutCellTmpData<'a> {
|
|||
#[derive(derive_more::From)]
|
||||
enum LayoutTreeItem<'a> {
|
||||
GridLayout(LayoutWithCells<'a, GridLayoutCellData<'a>>),
|
||||
BoxLayout(LayoutWithCells<'a, BoxLayoutCellTmpData<'a>>, bool),
|
||||
BoxLayout(
|
||||
LayoutWithCells<'a, BoxLayoutCellTmpData<'a>>,
|
||||
bool,
|
||||
sixtyfps_corelib::layout::LayoutAlignment,
|
||||
),
|
||||
PathLayout(&'a PathLayout),
|
||||
}
|
||||
|
||||
|
@ -982,12 +996,13 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
grid_layout.spacing,
|
||||
&grid_layout.padding,
|
||||
),
|
||||
LayoutTreeItem::BoxLayout(box_layout, is_horizontal) => {
|
||||
LayoutTreeItem::BoxLayout(box_layout, is_horizontal, alignment) => {
|
||||
let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells);
|
||||
box_layout_info(
|
||||
&Slice::from(cells.as_slice()),
|
||||
box_layout.spacing,
|
||||
&box_layout.padding,
|
||||
*alignment,
|
||||
*is_horizontal,
|
||||
)
|
||||
}
|
||||
|
@ -1154,9 +1169,21 @@ fn collect_layouts_recursively<'a, 'b>(
|
|||
top: box_layout.geometry.padding.top.as_ref().map_or(0., expr_eval),
|
||||
bottom: box_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval),
|
||||
};
|
||||
let alignment = box_layout
|
||||
.geometry
|
||||
.alignment
|
||||
.as_ref()
|
||||
.map(|nr| {
|
||||
eval::load_property(component, &nr.element.upgrade().unwrap(), &nr.name)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
layout_tree.push(LayoutTreeItem::BoxLayout(
|
||||
LayoutWithCells { geometry: &box_layout.geometry, cells, spacing, padding },
|
||||
box_layout.is_horizontal,
|
||||
alignment,
|
||||
));
|
||||
}
|
||||
Layout::PathLayout(layout) => layout_tree.push(layout.into()),
|
||||
|
@ -1187,7 +1214,7 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
cells: Slice::from(grid_layout.cells.as_slice()),
|
||||
});
|
||||
}
|
||||
Self::BoxLayout(box_layout, is_horizontal) => {
|
||||
Self::BoxLayout(box_layout, is_horizontal, alignment) => {
|
||||
let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells);
|
||||
solve_box_layout(
|
||||
&BoxLayoutData {
|
||||
|
@ -1198,6 +1225,7 @@ impl<'a> LayoutTreeItem<'a> {
|
|||
spacing: box_layout.spacing,
|
||||
padding: &box_layout.padding,
|
||||
cells: Slice::from(cells.as_slice()),
|
||||
alignment: *alignment,
|
||||
},
|
||||
*is_horizontal,
|
||||
);
|
||||
|
|
|
@ -210,6 +210,7 @@ macro_rules! declare_value_enum_conversion {
|
|||
|
||||
declare_value_enum_conversion!(corelib::items::TextHorizontalAlignment, TextHorizontalAlignment);
|
||||
declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVerticalAlignment);
|
||||
declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum ComponentInstance<'a, 'id> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue