New runtime implementation for the box layout

Currently only horizontal layout is supported
This commit is contained in:
Olivier Goffart 2020-10-26 12:29:10 +01:00
parent 211fa69ef5
commit aeade826fe
6 changed files with 332 additions and 60 deletions

View file

@ -187,10 +187,7 @@ pub mod re_exports {
};
pub use sixtyfps_corelib::items::*;
pub use sixtyfps_corelib::layout::LayoutInfo;
pub use sixtyfps_corelib::layout::{
grid_layout_info, solve_grid_layout, solve_path_layout, GridLayoutCellData, GridLayoutData,
Padding, PathLayoutData, PathLayoutItemData,
};
pub use sixtyfps_corelib::layout::*;
pub use sixtyfps_corelib::model::*;
pub use sixtyfps_corelib::properties::{Property, PropertyTracker};
pub use sixtyfps_corelib::signals::Signal;

View file

@ -1497,6 +1497,61 @@ impl crate::layout::gen::Language for CppLanguageLayoutGen {
cell_ref_variable,
}
}
fn box_layout_tree_item<'a, 'b>(
layout_tree: &'b mut Vec<crate::layout::gen::LayoutTreeItem<'a, Self>>,
box_layout: &'a crate::layout::BoxLayout,
component: &Rc<Component>,
) -> crate::layout::gen::LayoutTreeItem<'a, Self> {
let cells: Vec<_> = box_layout
.elems
.iter()
.map(|cell| {
let layout_info =
get_layout_info_ref(&cell.item, &cell.constraints, layout_tree, component);
let lay_rect = cell.item.rect();
let get_property_ref = |p: &Option<NamedReference>| match p {
Some(nr) => format!("&{}", access_named_reference(nr, component, "self")),
None => "nullptr".to_owned(),
};
format!(
" {{ {li}, {x}, {y}, {w}, {h} }},",
li = layout_info,
x = get_property_ref(&lay_rect.x_reference),
y = get_property_ref(&lay_rect.y_reference),
w = get_property_ref(&lay_rect.width_reference),
h = get_property_ref(&lay_rect.height_reference)
)
})
.collect();
let cell_ref_variable = format!("cells_{}", layout_tree.len()).to_owned();
let mut creation_code = cells;
creation_code.insert(
0,
format!(" sixtyfps::GridLayoutCellData {}_data[] = {{", cell_ref_variable,),
);
creation_code.push(" };".to_owned());
creation_code.push(format!(
" const sixtyfps::Slice<sixtyfps::GridLayoutCellData> {cv}{{{cv}_data, std::size({cv}_data)}};",
cv = cell_ref_variable
));
let (padding, spacing) = generate_layout_padding_and_spacing(
&mut creation_code,
&box_layout.geometry,
&layout_tree,
component,
);
LayoutTreeItem::BoxLayout {
geometry: &box_layout.geometry,
spacing,
padding,
var_creation_code: creation_code.join("\n"),
cell_ref_variable,
}
}
}
type LayoutTreeItem<'a> = crate::layout::gen::LayoutTreeItem<'a, CppLanguageLayoutGen>;
@ -1515,6 +1570,10 @@ fn get_layout_info_ref<'a, 'b>(
"sixtyfps::grid_layout_info(&{}, {}, &{})",
cell_ref_variable, spacing, padding
),
LayoutTreeItem::BoxLayout { spacing, cell_ref_variable, padding, .. } => format!(
"sixtyfps::box_layout_info(&{}, {}, &{})",
cell_ref_variable, spacing, padding
),
LayoutTreeItem::PathLayout(_) => todo!(),
}
});
@ -1623,11 +1682,28 @@ impl<'a> LayoutTreeItem<'a> {
s = spacing,
p = padding,
));
code_stream.push(format!(" {cv}", cv = cell_ref_variable).to_owned());
code_stream.push(format!(" {cv}", cv = cell_ref_variable));
code_stream.push(" };".to_owned());
code_stream.push(" sixtyfps::solve_grid_layout(&grid);".to_owned());
code_stream.push(" } ".into());
}
LayoutTreeItem::BoxLayout { geometry, spacing, cell_ref_variable, padding, .. } => {
code_stream.push(" { ".into());
code_stream.push(" sixtyfps::BoxLayoutData box { ".into());
code_stream.push(format!(
" {w}, {h}, {x}, {y}, {s}, &{p},",
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,
));
code_stream.push(format!(" {cv}", cv = cell_ref_variable));
code_stream.push(" };".to_owned());
code_stream.push(" sixtyfps::solve_box_layout(&box);".to_owned());
code_stream.push(" } ".into());
}
LayoutTreeItem::PathLayout(path_layout) => {
code_stream.push("{".to_owned());
@ -1762,6 +1838,7 @@ fn compute_layout(
res.extend(inverse_layout_tree.iter().filter_map(|layout| match layout {
LayoutTreeItem::GridLayout { var_creation_code, .. } => Some(var_creation_code.clone()),
LayoutTreeItem::BoxLayout { var_creation_code, .. } => Some(var_creation_code.clone()),
LayoutTreeItem::PathLayout(_) => None,
}));

View file

@ -1371,6 +1371,54 @@ impl crate::layout::gen::Language for RustLanguageLayoutGen {
}
.into()
}
fn box_layout_tree_item<'a, 'b>(
layout_tree: &'b mut Vec<crate::layout::gen::LayoutTreeItem<'a, Self>>,
box_layout: &'a crate::layout::BoxLayout,
component: &Rc<Component>,
) -> crate::layout::gen::LayoutTreeItem<'a, Self> {
let cells: Vec<_> = box_layout
.elems
.iter()
.map(|cell| {
let get_property_ref = |p: &Option<NamedReference>| match p {
Some(nr) => {
let p = access_named_reference(nr, component, quote!(_self));
quote!(Some(#p.get_ref()))
}
None => quote!(None),
};
let lay_rect = cell.item.rect();
let width = get_property_ref(&lay_rect.width_reference);
let height = get_property_ref(&lay_rect.height_reference);
let x = get_property_ref(&lay_rect.x_reference);
let y = get_property_ref(&lay_rect.y_reference);
let layout_info =
get_layout_info_ref(&cell.item, &cell.constraints, layout_tree, component);
quote!(BoxLayoutCellData {
x: #x,
y: #y,
width: #width,
height: #height,
constraint: #layout_info,
})
})
.collect();
let cell_ref_variable = format_ident!("cells_{}", layout_tree.len());
let cell_creation_code = quote!(let #cell_ref_variable
= [#( #cells ),*];);
let (padding, spacing, spacing_creation_code) =
generate_layout_padding_and_spacing(&layout_tree, &box_layout.geometry, component);
LayoutTreeItem::BoxLayout {
geometry: &box_layout.geometry,
var_creation_code: quote!(#cell_creation_code #spacing_creation_code),
cell_ref_variable: quote!(#cell_ref_variable),
spacing,
padding,
}
.into()
}
}
type LayoutTreeItem<'a> = crate::layout::gen::LayoutTreeItem<'a, RustLanguageLayoutGen>;
@ -1382,15 +1430,18 @@ fn get_layout_info_ref<'a, 'b>(
component: &Rc<Component>,
) -> TokenStream {
let layout_info = item.layout.as_ref().map(|l|{
let layout_tree_item =
crate::layout::gen::collect_layouts_recursively(layout_tree, l, component);
match layout_tree_item {
LayoutTreeItem::GridLayout { cell_ref_variable, spacing, padding, .. } => {
quote!(grid_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding))
}
LayoutTreeItem::PathLayout(_) => todo!(),
let layout_tree_item =
crate::layout::gen::collect_layouts_recursively(layout_tree, l, component);
match layout_tree_item {
LayoutTreeItem::GridLayout { cell_ref_variable, spacing, padding, .. } => {
quote!(grid_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding))
}
});
LayoutTreeItem::BoxLayout { cell_ref_variable, spacing, padding, .. } => {
quote!(box_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding))
}
LayoutTreeItem::PathLayout(_) => todo!(),
}
});
let elem_info = item.element.as_ref().map(|elem| {
let e = format_ident!("{}", elem.borrow().id);
quote!(Self::FIELD_OFFSETS.#e.apply_pin(self).layouting_info(&window))
@ -1488,6 +1539,24 @@ impl<'a> LayoutTreeItem<'a> {
});
});
}
LayoutTreeItem::BoxLayout { geometry, cell_ref_variable, spacing, padding, .. } => {
let x_pos = layout_prop(&geometry.rect.x_reference);
let y_pos = layout_prop(&geometry.rect.y_reference);
let width = layout_prop(&geometry.rect.width_reference);
let height = layout_prop(&geometry.rect.height_reference);
code_stream.push(quote! {
solve_box_layout(&BoxLayoutData {
width: #width,
height: #height,
x: #x_pos as _,
y: #y_pos as _,
cells: Slice::from_slice(&#cell_ref_variable),
spacing: #spacing,
padding: #padding,
});
});
}
LayoutTreeItem::PathLayout(path_layout) => {
let path_layout_item_data =
|elem: &ElementRc, elem_rs: TokenStream, component_rust: TokenStream| {
@ -1615,6 +1684,7 @@ fn compute_layout(
layouts.extend(inverse_layout_tree.iter().filter_map(|layout| match layout {
LayoutTreeItem::GridLayout { var_creation_code, .. } => Some(var_creation_code.clone()),
LayoutTreeItem::BoxLayout { var_creation_code, .. } => Some(var_creation_code.clone()),
LayoutTreeItem::PathLayout(_) => None,
}));

View file

@ -339,6 +339,12 @@ pub mod gen {
cells: Vec<Self::CompiledCode>,
component: &Rc<Component>,
) -> LayoutTreeItem<'a, Self>;
/// Returns a LayoutTree:BoxLayout
fn box_layout_tree_item<'a, 'b>(
layout_tree: &'b mut Vec<LayoutTreeItem<'a, Self>>,
box_layout: &'a BoxLayout,
component: &Rc<Component>,
) -> LayoutTreeItem<'a, Self>;
}
#[derive(derive_more::From)]
@ -350,6 +356,14 @@ pub mod gen {
var_creation_code: L::CompiledCode,
cell_ref_variable: L::CompiledCode,
},
BoxLayout {
geometry: &'a LayoutGeometry,
spacing: L::CompiledCode,
padding: L::CompiledCode,
var_creation_code: L::CompiledCode,
cell_ref_variable: L::CompiledCode,
},
#[from]
PathLayout(&'a PathLayout),
}
@ -382,27 +396,7 @@ pub mod gen {
layout_tree.push(i);
}
Layout::BoxLayout(box_layout) => {
let cells: Vec<_> = box_layout
.elems
.iter()
.enumerate()
.map(|(idx, cell)| {
let (c, r) = if box_layout.is_horizontal { (idx, 0) } else { (0, idx) };
L::make_grid_layout_cell_data(
&cell.item,
&cell.constraints,
c as u16,
r as u16,
1,
1,
layout_tree,
component,
)
})
.collect();
let i =
L::grid_layout_tree_item(layout_tree, &box_layout.geometry, cells, component);
let i = L::box_layout_tree_item(layout_tree, box_layout, component);
layout_tree.push(i);
}
Layout::PathLayout(layout) => layout_tree.push(layout.into()),

View file

@ -47,7 +47,7 @@ impl LayoutInfo {
}
}
mod internal {
mod grid_internal {
use super::*;
#[derive(Debug, Clone)]
@ -226,8 +226,8 @@ pub extern "C" fn solve_grid_layout(data: &GridLayoutData) {
return;
}
let mut row_layout_data = vec![internal::LayoutData::default(); num_row as usize];
let mut col_layout_data = vec![internal::LayoutData::default(); num_col as usize];
let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize];
let mut col_layout_data = vec![grid_internal::LayoutData::default(); num_col as usize];
for cell in data.cells.iter() {
let row_max = cell.constraint.max_height / (cell.rowspan as f32);
let row_min = cell.constraint.min_height / (cell.rowspan as f32);
@ -252,13 +252,13 @@ pub extern "C" fn solve_grid_layout(data: &GridLayoutData) {
}
}
internal::layout_items(
grid_internal::layout_items(
&mut row_layout_data,
data.y + data.padding.top,
data.height - (data.padding.top + data.padding.bottom),
data.spacing,
);
internal::layout_items(
grid_internal::layout_items(
&mut col_layout_data,
data.x + data.padding.left,
data.width - (data.padding.left + data.padding.right),
@ -302,8 +302,8 @@ pub extern "C" fn grid_layout_info<'a>(
return LayoutInfo { min_width: 0., max_width: 0., min_height: 0., max_height: 0. };
};
let mut row_layout_data = vec![internal::LayoutData::default(); num_row as usize];
let mut col_layout_data = vec![internal::LayoutData::default(); num_col as usize];
let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize];
let mut col_layout_data = vec![grid_internal::LayoutData::default(); num_col as usize];
for cell in cells.iter() {
let rdata = &mut row_layout_data[cell.row as usize];
let cdata = &mut col_layout_data[cell.col as usize];
@ -320,8 +320,8 @@ pub extern "C" fn grid_layout_info<'a>(
let min_height = row_layout_data.iter().map(|data| data.min).sum::<Coord>()
+ spacing_h
+ padding.left
+ padding.right;
+ padding.top
+ padding.bottom;
let max_height = row_layout_data.iter().map(|data| data.max).sum::<Coord>()
+ spacing_h
+ padding.top
@ -332,9 +332,135 @@ pub extern "C" fn grid_layout_info<'a>(
+ padding.right;
let max_width = col_layout_data.iter().map(|data| data.max).sum::<Coord>()
+ spacing_w
+ padding.left
+ padding.right;
LayoutInfo { min_width, max_width, min_height, max_height }
}
#[repr(C)]
#[derive(Debug)]
/// The BoxLayoutData is used to represent both a Horizontal and Vertical layout.
/// The width/height x/y corrspond to that of a horizontal layout.
/// For vertical layout, they are inverted
pub struct BoxLayoutData<'a> {
pub width: Coord,
pub height: Coord,
pub x: Coord,
pub y: Coord,
pub spacing: Coord,
pub padding: &'a Padding,
pub cells: Slice<'a, BoxLayoutCellData<'a>>,
}
#[repr(C)]
#[derive(Default, Debug)]
pub struct BoxLayoutCellData<'a> {
pub constraint: LayoutInfo,
pub x: Option<&'a Property<Coord>>,
pub y: Option<&'a Property<Coord>>,
pub width: Option<&'a Property<Coord>>,
pub height: Option<&'a Property<Coord>>,
}
/// Solve the horizontal BoxLayout
#[no_mangle]
pub extern "C" fn solve_box_layout(data: &BoxLayoutData) {
use stretch::geometry::*;
use stretch::number::*;
use stretch::style::*;
let mut stretch = stretch::Stretch::new();
let box_style = stretch::style::Style {
size: Size { width: Dimension::Percent(1.), height: Dimension::Percent(1.) },
flex_grow: 1.,
display: Display::Flex,
flex_direction: FlexDirection::Row,
flex_basis: Dimension::Percent(1.),
..Default::default()
};
let flex_box = stretch.new_node(box_style, vec![]).unwrap();
for (index, cell) in data.cells.iter().enumerate() {
let min = if cell.constraint.min_width == 0.0 {
Dimension::Undefined
} else {
Dimension::Points(cell.constraint.min_height)
};
let max = if cell.constraint.max_width == f32::MAX {
Dimension::Undefined
} else {
Dimension::Points(cell.constraint.max_width)
};
let mut margin = Rect::default();
if index != 0 {
margin.start = Dimension::Points(data.spacing / 2.);
}
if index != data.cells.len() - 1 {
margin.end = Dimension::Points(data.spacing / 2.);
}
let cell_style = Style {
min_size: Size { width: min, height: Dimension::Auto },
max_size: Size { width: max, height: Dimension::Auto },
size: Size { width: min, height: Dimension::Auto },
flex_grow: 1.,
margin,
..Default::default()
};
let cell_item = stretch.new_node(cell_style, vec![]).unwrap();
stretch.add_child(flex_box, cell_item).unwrap();
}
stretch
.compute_layout(
flex_box,
Size {
width: Number::Defined(data.width - (data.padding.left + data.padding.right)),
height: Number::Undefined,
},
)
.unwrap();
let start_pos = data.x + data.padding.left;
for (cell, layout) in data.cells.iter().zip(
stretch.children(flex_box).unwrap().iter().map(|child| stretch.layout(*child).unwrap()),
) {
cell.x.map(|p| p.set(start_pos + layout.location.x));
cell.y.map(|p| p.set(data.y + data.padding.top));
cell.width.map(|p| p.set(layout.size.width));
cell.height.map(|p| p.set(data.height - (data.padding.top + data.padding.bottom)));
}
}
#[no_mangle]
/// Return the LayoutInfo for a Horizontal BoxLayout with the given cells.
/// Transpose everything for vertical
pub extern "C" fn box_layout_info<'a>(
cells: &Slice<'a, BoxLayoutCellData<'a>>,
spacing: Coord,
padding: &Padding,
) -> LayoutInfo {
let count = cells.len();
if count < 1 {
return LayoutInfo { min_width: 0., max_width: 0., min_height: 0., max_height: 0. };
};
let extra_w = padding.left + padding.right + spacing * (count - 1) as Coord;
let order_float = |a: &Coord, b: &Coord| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal);
let min_height = cells.iter().map(|c| c.constraint.min_height).max_by(order_float).unwrap()
+ padding.top
+ padding.bottom;
let max_height = cells.iter().map(|c| c.constraint.max_height).min_by(order_float).unwrap()
+ 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;
LayoutInfo { min_width, max_width, min_height, max_height }
}

View file

@ -919,16 +919,17 @@ fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const () {
use sixtyfps_corelib::layout::*;
pub struct GridLayoutWithCells<'a> {
pub struct LayoutWithCells<'a, C> {
geometry: &'a sixtyfps_compilerlib::layout::LayoutGeometry,
cells: Vec<GridLayoutCellData<'a>>,
cells: Vec<C>,
spacing: f32,
padding: Padding,
}
#[derive(derive_more::From)]
enum LayoutTreeItem<'a> {
GridLayout(GridLayoutWithCells<'a>),
GridLayout(LayoutWithCells<'a, GridLayoutCellData<'a>>),
BoxLayout(LayoutWithCells<'a, BoxLayoutCellData<'a>>),
PathLayout(&'a PathLayout),
}
@ -940,6 +941,11 @@ impl<'a> LayoutTreeItem<'a> {
grid_layout.spacing,
&grid_layout.padding,
),
LayoutTreeItem::BoxLayout(box_layout) => box_layout_info(
&Slice::from(box_layout.cells.as_slice()),
box_layout.spacing,
&box_layout.padding,
),
LayoutTreeItem::PathLayout(_) => todo!(),
}
}
@ -1034,32 +1040,24 @@ fn collect_layouts_recursively<'a, 'b>(
bottom: grid_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval),
};
layout_tree.push(
GridLayoutWithCells { geometry: &grid_layout.geometry, cells, spacing, padding }
.into(),
LayoutWithCells { geometry: &grid_layout.geometry, cells, spacing, padding }.into(),
);
}
Layout::BoxLayout(box_layout) => {
let cells = box_layout
.elems
.iter()
.enumerate()
.map(|(idx, cell)| {
.map(|cell| {
let mut layout_info =
get_layout_info(&cell.item, component, layout_tree, window);
fill_layout_info_constraints(&mut layout_info, &cell.constraints, &expr_eval);
let (col, row) =
if box_layout.is_horizontal { (idx as u16, 0) } else { (0, idx as u16) };
let rect = cell.item.rect();
GridLayoutCellData {
BoxLayoutCellData {
x: assume_property_f32(&rect.x_reference),
y: assume_property_f32(&rect.y_reference),
width: assume_property_f32(&rect.width_reference),
height: assume_property_f32(&rect.height_reference),
col,
row,
colspan: 1,
rowspan: 1,
constraint: layout_info,
}
})
@ -1072,8 +1070,7 @@ fn collect_layouts_recursively<'a, 'b>(
bottom: box_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval),
};
layout_tree.push(
GridLayoutWithCells { geometry: &box_layout.geometry, cells, spacing, padding }
.into(),
LayoutWithCells { geometry: &box_layout.geometry, cells, spacing, padding }.into(),
);
}
Layout::PathLayout(layout) => layout_tree.push(layout.into()),
@ -1104,6 +1101,17 @@ impl<'a> LayoutTreeItem<'a> {
cells: Slice::from(grid_layout.cells.as_slice()),
});
}
Self::BoxLayout(box_layout) => {
solve_box_layout(&BoxLayoutData {
width: resolve_prop_ref(&box_layout.geometry.rect.width_reference),
height: resolve_prop_ref(&box_layout.geometry.rect.height_reference),
x: resolve_prop_ref(&box_layout.geometry.rect.x_reference),
y: resolve_prop_ref(&box_layout.geometry.rect.y_reference),
spacing: box_layout.spacing,
padding: &box_layout.padding,
cells: Slice::from(box_layout.cells.as_slice()),
});
}
Self::PathLayout(path_layout) => {
use sixtyfps_corelib::layout::*;