Layout refactoring: C++ part

This commit is contained in:
Olivier Goffart 2021-05-06 14:17:24 +02:00 committed by Olivier Goffart
parent 539a78e807
commit ba1aff84d0
8 changed files with 334 additions and 608 deletions

View file

@ -309,7 +309,6 @@ using cbindgen_private::LayoutAlignment;
using cbindgen_private::LayoutInfo;
using cbindgen_private::Padding;
using cbindgen_private::PathLayoutData;
using cbindgen_private::PathLayoutItemData;
using cbindgen_private::Rect;
using cbindgen_private::sixtyfps_box_layout_info;
using cbindgen_private::sixtyfps_grid_layout_info;
@ -346,6 +345,29 @@ inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const
std::min(vertical_stretch, other.vertical_stretch) };
}
/// FIXME! this should be done by cbindgen
namespace cbindgen_private {
inline bool operator==(const LayoutInfo &a, const LayoutInfo &b)
{
return a.min_width == b.min_width &&
a.max_width == b.max_width &&
a.min_height == b.min_height &&
a.max_height == b.max_height &&
a.min_width_percent == b.min_width_percent &&
a.max_width_percent == b.max_width_percent &&
a.min_height_percent == b.min_height_percent &&
a.max_height_percent == b.max_height_percent &&
a.preferred_width == b.preferred_width &&
a.preferred_height == b.preferred_height &&
a.horizontal_stretch == b.horizontal_stretch &&
a.vertical_stretch == b.vertical_stretch;
}
inline bool operator!=(const LayoutInfo &a, const LayoutInfo &b)
{
return !(a == b);
}
}
// models
struct AbstractRepeaterView
{

View file

@ -133,7 +133,7 @@ public:
return a.inner == b.inner;
}
friend bool operator!=(const VRc &a, const VRc &b) {
return a.inner == b.inner;
return a.inner != b.inner;
}
};
@ -169,6 +169,13 @@ public:
}
VWeak<VTable, Dyn> into_dyn() const { return *reinterpret_cast<const VWeak<VTable, Dyn> *>(this); }
friend bool operator==(const VWeak &a, const VWeak &b) {
return a.inner == b.inner;
}
friend bool operator!=(const VWeak &a, const VWeak &b) {
return a.inner != b.inner;
}
};

View file

@ -221,7 +221,7 @@ use crate::expression_tree::{
BindingExpression, BuiltinFunction, EasingCurve, Expression, NamedReference,
};
use crate::langtype::Type;
use crate::layout::LayoutGeometry;
use crate::layout::{Layout, LayoutGeometry, LayoutRect};
use crate::object_tree::{
Component, Document, Element, ElementRc, PropertyDeclaration, RepeatedElementInfo,
};
@ -244,20 +244,22 @@ impl CppType for Type {
Type::LogicalLength => Some("float".to_owned()),
Type::Percent => Some("float".to_owned()),
Type::Bool => Some("bool".to_owned()),
Type::Struct { fields, name, .. } => {
if let Some(name) = name {
Some(name.clone())
} else {
let elem = fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
// This will produce a tuple
Some(format!("std::tuple<{}>", elem.join(", ")))
}
Type::Struct { name: Some(name), node: Some(_), .. } => Some(name.clone()),
Type::Struct { name: Some(name), node: None, .. } => {
Some(format!("sixtyfps::{}", name))
}
Type::Struct { fields, .. } => {
let elem = fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
Some(format!("std::tuple<{}>", elem.join(", ")))
}
Type::Array(i) => Some(format!("std::shared_ptr<sixtyfps::Model<{}>>", i.cpp_type()?)),
Type::Image => Some("sixtyfps::ImageReference".to_owned()),
Type::Builtin(elem) => elem.native_class.cpp_type.clone(),
Type::Enumeration(enumeration) => Some(format!("sixtyfps::{}", enumeration.name)),
Type::Brush => Some("sixtyfps::Brush".to_owned()),
Type::LayoutCache => Some("sixtyfps::SharedVector<float>".into()),
_ => None,
}
}
@ -514,9 +516,6 @@ fn handle_repeater(
));
layout_repeater_code.push(format!("self->{}.ensure_updated(self);", repeater_id));
// FIXME: we should probably pass the parent element rect?
layout_repeater_code
.push(format!("self->{}.compute_layout(sixtyfps::Rect{{ }});", repeater_id,));
}
repeated_input_branch.push(format!(
@ -550,7 +549,7 @@ pub fn generate(doc: &Document, diag: &mut BuildDiagnostics) -> Option<impl std:
file.includes.push("<sixtyfps.h>".into());
for ty in doc.root_component.used_structs.borrow().iter() {
if let Type::Struct { fields, name: Some(name), .. } = ty {
if let Type::Struct { fields, name: Some(name), node: Some(_) } = ty {
generate_struct(&mut file, name, fields, diag);
}
}
@ -834,10 +833,11 @@ fn generate_component(
));
component_struct.friends.push(parent_component_id);
let p_y = access_member(&component.root_element, "y", component, "this");
let p_height = access_member(&component.root_element, "height", component, "this");
let p_width = access_member(&component.root_element, "width", component, "this");
if parent_element.borrow().repeated.as_ref().map_or(false, |r| r.is_listview.is_some()) {
let p_y = access_member(&component.root_element, "y", component, "this");
let p_height = access_member(&component.root_element, "height", component, "this");
let p_width = access_member(&component.root_element, "width", component, "this");
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
@ -847,7 +847,7 @@ fn generate_component(
.to_owned(),
statements: Some(vec![
"float vp_w = viewport_width->get();".to_owned(),
format!("apply_layout({{&static_vtable, const_cast<void *>(static_cast<const void *>(this))}}, sixtyfps::Rect{{ 0, *offset_y, vp_w, {h} }});", h = "0/*FIXME: should compute the heigth somehow*/"),
format!("{}.set(*offset_y);", p_y), // FIXME: shouldn't that be handled by apply layout?
format!("*offset_y += {}.get();", p_height),
format!("float w = {}.get();", p_width),
@ -858,22 +858,13 @@ fn generate_component(
}),
));
} else if parent_element.borrow().repeated.is_some() {
let p_x = access_member(&component.root_element, "x", component, "this");
let root_c = &component.layouts.borrow().root_constraints;
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
name: "box_layout_data".into(),
signature: "() const -> sixtyfps::BoxLayoutCellData".to_owned(),
statements: Some(vec![format!(
"return {{ layouting_info({{&static_vtable, const_cast<void *>(static_cast<const void *>(this))}}), &{x}, &{y}, {w}, {h} }};",
x = p_x,
y = p_y,
w = if root_c.fixed_width { "nullptr".into() } else { format!("&{}", p_width) },
h = if root_c.fixed_height { "nullptr".into() } else { format!("&{}", p_height) },
statements: Some(vec!["return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}) };".into()]),
)]),
..Function::default()
}),
));
@ -1194,30 +1185,18 @@ fn generate_component(
}),
));
let (apply_layout, layout_info) = compute_layout(component, &mut repeater_layout_code);
component_struct.members.push((
Access::Public, // FIXME: we call this function from tests
Declaration::Function(Function {
name: "apply_layout".into(),
signature:
"(sixtyfps::private_api::ComponentRef component, [[maybe_unused]] sixtyfps::Rect rect) -> void"
.into(),
is_static: true,
statements: Some(apply_layout),
..Default::default()
}),
));
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "layouting_info".into(),
name: "layout_info".into(),
signature:
"([[maybe_unused]] sixtyfps::private_api::ComponentRef component) -> sixtyfps::LayoutInfo"
.into(),
is_static: true,
statements: Some(layout_info),
statements: Some(vec![
format!("[[maybe_unused]] auto self = reinterpret_cast<const {}*>(component.instance);", component_id),
format!("return {};", get_layout_info(&component.root_element, component, &component.root_constraints.borrow()))
]),
..Default::default()
}),
));
@ -1271,7 +1250,7 @@ fn generate_component(
ty: "const sixtyfps::private_api::ComponentVTable".to_owned(),
name: format!("{}::static_vtable", component_id),
init: Some(format!(
"{{ visit_children, get_item_ref, parent_item, layouting_info, apply_layout, sixtyfps::private_api::drop_in_place<{}>, sixtyfps::private_api::dealloc }}",
"{{ visit_children, get_item_ref, parent_item, layout_info, sixtyfps::private_api::drop_in_place<{}>, sixtyfps::private_api::dealloc }}",
component_id)
),
}));
@ -1578,7 +1557,7 @@ fn compile_expression(
let item = item.upgrade().unwrap();
let item = item.borrow();
let native_item = item.base_type.as_native();
format!("{vt}->layouting_info({{&sixtyfps::private_api::{vt}, const_cast<sixtyfps::{ty}*>(&self->{id})}}, &window)",
format!("{vt}->layouting_info({{{vt}, const_cast<sixtyfps::{ty}*>(&self->{id})}}, &window)",
vt = native_item.cpp_vtable_getter,
ty = native_item.class_name,
id = item.id
@ -1710,6 +1689,98 @@ fn compile_expression(
compile_expression(expr, component)
),
Expression::ReturnStatement(None) => "throw sixtyfps::private_api::ReturnWrapper<void>()".to_owned(),
Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
let cache = access_named_reference(layout_cache_prop, component, "self");
if let Some(ri) = repeater_index {
format!("[&](auto cache) {{ return cache[int(cache[{}]) + {} * 4]; }} ({}.get())", index, compile_expression(&ri, component), cache)
} else {
format!("{}.get()[{}]", cache, index)
}
}
Expression::ComputeLayoutInfo(Layout::GridLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let cells = grid_layout_cell_data(layout, component);
format!("[&] {{ \
const auto padding = {};\
sixtyfps::GridLayoutCellData cells[] = {{ {} }}; \
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}}; \
return sixtyfps::sixtyfps_grid_layout_info(slice, {}, &padding);\
}}()",
padding, cells, spacing
)
}
Expression::ComputeLayoutInfo(Layout::BoxLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let (cells, alignment) = box_layout_data(layout, component, None);
format!("[&] {{ \
const auto padding = {};\
{}\
const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> slice{{ &*std::begin(cells), std::size(cells)}}; \
return sixtyfps::sixtyfps_box_layout_info(slice, {}, &padding, {}, {});\
}}()",
padding, cells, spacing, alignment, layout.is_horizontal
)
}
Expression::ComputeLayoutInfo(Layout::PathLayout(_)) => unimplemented!(),
Expression::SolveLayout(Layout::GridLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let cells = grid_layout_cell_data(layout, component);
let (width, height) = layout_geometry_width_height(&layout.geometry.rect, component);
format!("[&] {{ \
const auto padding = {p};\
sixtyfps::GridLayoutCellData cells[] = {{ {c} }}; \
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}}; \
const sixtyfps::GridLayoutData grid {{ {w}, {h}, 0, 0, {s}, &padding, slice }};
sixtyfps::SharedVector<float> result;
sixtyfps::sixtyfps_solve_grid_layout(&grid, &result);\
return result;
}}()",
p = padding, c = cells, s = spacing, w = width, h = height
)
}
Expression::SolveLayout(Layout::BoxLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let mut repeated_indices = Default::default();
let mut repeated_indices_init = Default::default();
let (cells, alignment) = box_layout_data(layout, component, Some((&mut repeated_indices, &mut repeated_indices_init)));
let (width, height) = layout_geometry_width_height(&layout.geometry.rect, component);
format!("[&] {{ \
{ri_init}\
const auto padding = {p};\
{c}\
const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> slice{{ &*std::begin(cells), std::size(cells)}}; \
sixtyfps::BoxLayoutData box {{ {w}, {h}, 0, 0, {s}, &padding, {a}, slice }};
sixtyfps::SharedVector<float> result;
sixtyfps::sixtyfps_solve_box_layout(&box, {hz}, {ri}, &result);\
return result;
}}()",
ri_init = repeated_indices_init, ri = repeated_indices,
p = padding, c = cells, s = spacing, w = width, h = height, a = alignment,
hz = layout.is_horizontal
)
}
Expression::SolveLayout(Layout::PathLayout(layout)) => {
let (width, height) = layout_geometry_width_height(&layout.rect, component);
let elements = compile_path(&layout.path, component);
let prop = |expr: &Option<NamedReference>| {
if let Some(nr) = expr.as_ref() {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
// FIXME! repeater
format!("[&] {{ \
const auto elements = {e};\
sixtyfps::PathLayoutData path {{ &elements, {c}, 0, 0, {w}, {h}, {o} }};
sixtyfps::SharedVector<float> result;
sixtyfps::sixtyfps_solve_path_layout(&path, {{}}, &result);\
return result;
}}()",
e = elements, c = layout.elements.len(), w = width, h = height, o = prop(&layout.offset_reference)
)
}
Expression::Uncompiled(_) | Expression::TwoWayBinding(..) => panic!(),
Expression::Invalid => "\n#error invalid expression\n".to_string(),
}
@ -1796,215 +1867,144 @@ fn compile_assignment(
}
}
struct CppLanguageLayoutGen;
impl crate::layout::gen::Language for CppLanguageLayoutGen {
type CompiledCode = String;
fn make_grid_layout_cell_data<'a, 'b>(
item: &'a crate::layout::LayoutItem,
col: u16,
row: u16,
colspan: u16,
rowspan: u16,
layout_tree: &'b mut Vec<crate::layout::gen::LayoutTreeItem<'a, Self>>,
component: &Rc<Component>,
) -> Self::CompiledCode {
let layout_info = get_layout_info_ref(item, layout_tree, component);
let lay_rect = 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!(
" {{ {c}, {r}, {cs}, {rs}, {li}, {x}, {y}, {w}, {h} }},",
c = col,
r = row,
cs = colspan,
rs = rowspan,
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)
)
}
fn grid_layout_tree_item<'a, 'b>(
layout_tree: &'b mut Vec<crate::layout::gen::LayoutTreeItem<'a, Self>>,
geometry: &'a LayoutGeometry,
cells: Vec<Self::CompiledCode>,
component: &Rc<Component>,
) -> crate::layout::gen::LayoutTreeItem<'a, Self> {
let cell_ref_variable = format!("cells_{}", layout_tree.len());
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,
geometry,
&layout_tree,
component,
);
LayoutTreeItem::GridLayout {
geometry: &geometry,
spacing,
padding,
var_creation_code: creation_code.join("\n"),
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 is_static_array = box_layout
.elems
.iter()
.all(|i| i.element.as_ref().map_or(true, |x| x.borrow().repeated.is_none()));
let mut make_box_layout_cell_data = |cell: &'a crate::layout::LayoutItem| {
let layout_info = get_layout_info_ref(&cell, layout_tree, component);
let lay_rect = cell.rect();
let get_property_ref = |p: &Option<NamedReference>| match p {
Some(nr) => format!("&{}", access_named_reference(nr, component, "self")),
None => "nullptr".to_owned(),
};
fn grid_layout_cell_data(layout: &crate::layout::GridLayout, component: &Rc<Component>) -> String {
layout
.elems
.iter()
.map(|c| {
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)
"sixtyfps::GridLayoutCellData {{ {col}, {row}, {cs}, {rs}, {cstr} }}",
col = c.col,
row = c.row,
cs = c.colspan,
rs = c.rowspan,
cstr = get_layout_info(&c.item.element, component, &c.item.constraints),
)
};
let mut creation_code = if is_static_array {
let mut creation_code: Vec<_> =
box_layout.elems.iter().map(make_box_layout_cell_data).map(|s| s + ",").collect();
creation_code.insert(0, "sixtyfps::BoxLayoutCellData @_data[] = {".into());
creation_code.push("};".to_owned());
creation_code
} else {
let mut push_code = vec!["std::vector<sixtyfps::BoxLayoutCellData> @_data;".into()];
for item in &box_layout.elems {
match &item.element {
Some(elem) if elem.borrow().repeated.is_some() => {
push_code.push(format!(
"self->repeater_{}.ensure_updated(self);",
elem.borrow().id
));
push_code.push(format!(
"if (self->repeater_{id}.inner) for (auto &&sub_comp : self->repeater_{id}.inner->data)",
id = elem.borrow().id
));
push_code.push(
" @_data.push_back((*sub_comp.ptr)->box_layout_data());".to_owned(),
);
}
_ => {
push_code.push(format!(
"@_data.push_back({});",
make_box_layout_cell_data(item)
));
}
}
}
push_code
};
let cell_ref_variable = format!("cells_{}", layout_tree.len());
creation_code.iter_mut().for_each(|s| *s = s.replace('@', cell_ref_variable.as_ref()));
creation_code.push(format!(
"const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> {cv}{{&*std::begin({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,
);
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,
}
}
})
.join(", ")
}
type LayoutTreeItem<'a> = crate::layout::gen::LayoutTreeItem<'a, CppLanguageLayoutGen>;
impl<'a> LayoutTreeItem<'a> {
fn layout_info(&self) -> String {
match self {
LayoutTreeItem::GridLayout { cell_ref_variable, spacing, padding, .. } => format!(
"sixtyfps::sixtyfps_grid_layout_info(&{}, {}, &{})",
cell_ref_variable, spacing, padding
),
LayoutTreeItem::BoxLayout {
spacing,
cell_ref_variable,
padding,
alignment,
is_horizontal,
..
} => format!(
"sixtyfps::sixtyfps_box_layout_info(&{}, {}, &{}, {}, {})",
cell_ref_variable, spacing, padding, alignment, is_horizontal
),
LayoutTreeItem::PathLayout(_) => "{/*layout_info for path not implemented*/}".into(),
}
}
}
fn get_layout_info_ref<'a, 'b>(
item: &'a crate::layout::LayoutItem,
layout_tree: &'b mut Vec<LayoutTreeItem<'a>>,
/// Returns `(cells, alignment)`.
/// The repeated_indices initialize the repeated_indices (var, init_code)
fn box_layout_data(
layout: &crate::layout::BoxLayout,
component: &Rc<Component>,
) -> String {
let layout_info = item.layout.as_ref().map(|l| {
crate::layout::gen::collect_layouts_recursively(layout_tree, l, component).layout_info()
});
let elem_info = item.element.as_ref().map(|elem| {
mut repeated_indices: Option<(&mut String, &mut String)>,
) -> (String, String) {
let alignment = if let Some(nr) = &layout.geometry.alignment {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"{}".into()
};
let repeater_count =
layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
if repeater_count == 0 {
let mut cells = layout.elems.iter().map(|li| {
format!(
"{vt}->layouting_info({{{vt}, const_cast<sixtyfps::{ty}*>(&self->{id})}}, &self->window)",
vt = elem.borrow().base_type.as_native().cpp_vtable_getter,
ty = elem.borrow().base_type.as_native().class_name,
id = elem.borrow().id,
"sixtyfps::BoxLayoutCellData{{ {} }}",
get_layout_info(&li.element, component, &li.constraints)
)
});
let mut layout_info = match (layout_info, elem_info) {
(None, None) => String::default(),
(None, Some(x)) => x,
(Some(x), None) => x,
(Some(layout_info), Some(elem_info)) => format!("{}.merge({})", layout_info, elem_info),
if let Some((ri, _)) = &mut repeated_indices {
**ri = "{}".into();
}
(format!("sixtyfps::BoxLayoutCellData cells[] = {{ {} }};", cells.join(", ")), alignment)
} else {
let mut push_code = "std::vector<sixtyfps::BoxLayoutCellData> cells;".to_owned();
if let Some((ri, init)) = &mut repeated_indices {
**ri =
"sixtyfps::Slice<unsigned int>{&*std::begin(repeater_indices), std::size(repeater_indices)}"
.to_owned();
**init = format!("std::array<unsigned int, {}> repeater_indices;", repeater_count * 2);
}
let mut repeater_idx = 0usize;
for item in &layout.elems {
if item.element.borrow().repeated.is_some() {
push_code +=
&format!("self->repeater_{}.ensure_updated(self);", item.element.borrow().id);
if repeated_indices.is_some() {
push_code += &format!("repeater_indices[{}] = cells.size();", repeater_idx * 2);
push_code += &format!(
"repeater_indices[{c}] = self->repeater_{id}.inner ? self->repeater_{id}.inner->data.size() : 0;",
c = repeater_idx * 2 + 1,
id = item.element.borrow().id
);
}
repeater_idx += 1;
push_code += &format!(
"if (self->repeater_{id}.inner) \
for (auto &&sub_comp : self->repeater_{id}.inner->data) \
cells.push_back((*sub_comp.ptr)->box_layout_data());",
id = item.element.borrow().id
);
} else {
push_code += &format!(
"cells.push_back({{ {} }});",
get_layout_info(&item.element, component, &item.constraints)
);
}
}
(push_code, alignment)
}
}
fn generate_layout_padding_and_spacing(
layout_geometry: &LayoutGeometry,
component: &Rc<Component>,
) -> (String, String) {
let prop = |expr: &Option<NamedReference>| {
if let Some(nr) = expr.as_ref() {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
if item.constraints.has_explicit_restrictions() {
let spacing = prop(&layout_geometry.spacing);
let padding = format!(
"sixtyfps::Padding {{ {}, {}, {}, {} }};",
prop(&layout_geometry.padding.left),
prop(&layout_geometry.padding.right),
prop(&layout_geometry.padding.top),
prop(&layout_geometry.padding.bottom),
);
(padding, spacing)
}
fn layout_geometry_width_height(rect: &LayoutRect, component: &Rc<Component>) -> (String, String) {
let prop = |expr: &Option<NamedReference>| {
if let Some(nr) = expr.as_ref() {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
(prop(&rect.width_reference), prop(&rect.height_reference))
}
fn get_layout_info(
elem: &ElementRc,
component: &Rc<Component>,
constraints: &crate::layout::LayoutConstraints,
) -> String {
let mut layout_info = if let Some(layout_info_prop) = &elem.borrow().layout_info_prop {
format!("{}.get()", access_named_reference(layout_info_prop, component, "self"))
} else {
format!(
"{vt}->layouting_info({{{vt}, const_cast<sixtyfps::{ty}*>(&self->{id})}}, &self->window)",
vt = elem.borrow().base_type.as_native().cpp_vtable_getter,
ty = elem.borrow().base_type.as_native().class_name,
id = elem.borrow().id,
)
};
if constraints.has_explicit_restrictions() {
layout_info = format!("[&]{{ auto layout_info = {};", layout_info);
for (expr, name) in item.constraints.for_each_restrictions() {
for (expr, name) in constraints.for_each_restrictions() {
layout_info += &format!(
" layout_info.{} = {}.get();",
name,
@ -2016,305 +2016,6 @@ fn get_layout_info_ref<'a, 'b>(
layout_info
}
fn generate_layout_padding_and_spacing<'a, 'b>(
creation_code: &mut Vec<String>,
layout_geometry: &LayoutGeometry,
layout_tree: &'b [LayoutTreeItem<'a>],
component: &Rc<Component>,
) -> (String, String) {
let spacing = if let Some(spacing) = &layout_geometry.spacing {
let variable = format!("spacing_{}", layout_tree.len());
creation_code.push(format!(
"auto {} = {}.get();",
variable,
access_named_reference(spacing, component, "self")
));
variable
} else {
"0.".into()
};
let padding = format!("padding_{}", layout_tree.len());
let padding_prop = |expr| {
if let Some(nr) = expr {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
creation_code.push(format!(
"sixtyfps::Padding {} = {{ {}, {}, {}, {} }};",
padding,
padding_prop(layout_geometry.padding.left.as_ref()),
padding_prop(layout_geometry.padding.right.as_ref()),
padding_prop(layout_geometry.padding.top.as_ref()),
padding_prop(layout_geometry.padding.bottom.as_ref()),
));
(padding, spacing)
}
impl<'a> LayoutTreeItem<'a> {
fn emit_solve_calls(&self, component: &Rc<Component>, code_stream: &mut Vec<String>) {
if let Some(geometry) = self.geometry() {
if geometry.materialized_constraints.has_explicit_restrictions() {
code_stream.push(" {".into());
code_stream.push(format!(" auto layout_info = {};", self.layout_info()));
for (expr, name) in geometry.materialized_constraints.for_each_restrictions() {
code_stream.push(format!(
" {}.set(layout_info.{});",
access_named_reference(expr, component, "self"),
name
));
}
code_stream.push(" }".into());
}
}
let layout_prop = |p: &Option<NamedReference>| match p {
Some(nr) => format!("{}.get()", access_named_reference(nr, component, "self")),
None => "0".into(),
};
match self {
LayoutTreeItem::GridLayout {
geometry, spacing, cell_ref_variable, padding, ..
} => {
code_stream.push(" { ".into());
code_stream.push(" sixtyfps::GridLayoutData grid { ".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::sixtyfps_solve_grid_layout(&grid);".to_owned());
code_stream.push(" } ".into());
}
LayoutTreeItem::BoxLayout {
geometry,
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}, {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());
code_stream.push(format!(
" sixtyfps::sixtyfps_solve_box_layout(&box, {});",
is_horizontal
));
code_stream.push(" } ".into());
}
LayoutTreeItem::PathLayout(path_layout) => {
code_stream.push("{".to_owned());
let path_layout_item_data =
|elem: &ElementRc, elem_cpp: &str, component_cpp: &str| {
let prop_ref = |n: &str| {
if elem.borrow().lookup_property(n).property_type == Type::LogicalLength
{
format!("&{}.{}", elem_cpp, n)
} else {
"nullptr".to_owned()
}
};
let prop_value = |n: &str| {
if elem.borrow().lookup_property(n).property_type == Type::LogicalLength
{
let value_accessor = access_member(
&elem,
n,
&elem.borrow().enclosing_component.upgrade().unwrap(),
component_cpp,
);
format!("{}.get()", value_accessor)
} else {
"0.".into()
}
};
format!(
"{{ {}, {}, {}, {} }}",
prop_ref("x"),
prop_ref("y"),
prop_value("width"),
prop_value("height")
)
};
let path_layout_item_data_for_elem = |elem: &ElementRc| {
path_layout_item_data(elem, &format!("self->{}", elem.borrow().id), "self")
};
let is_static_array =
path_layout.elements.iter().all(|elem| elem.borrow().repeated.is_none());
let slice = if is_static_array {
code_stream.push(" sixtyfps::PathLayoutItemData items[] = {".to_owned());
for elem in &path_layout.elements {
code_stream
.push(format!(" {},", path_layout_item_data_for_elem(elem)));
}
code_stream.push(" };".to_owned());
" {items, std::size(items)},".to_owned()
} else {
code_stream
.push(" std::vector<sixtyfps::PathLayoutItemData> items;".to_owned());
for elem in &path_layout.elements {
if elem.borrow().repeated.is_some() {
let root_element =
elem.borrow().base_type.as_component().root_element.clone();
code_stream.push(format!(
" for (auto &&sub_comp : self->repeater_{}.inner->data)",
elem.borrow().id
));
code_stream.push(format!(
" items.push_back({});",
path_layout_item_data(
&root_element,
&format!("(*sub_comp.ptr)->{}", root_element.borrow().id),
"(*sub_comp.ptr)",
)
));
} else {
code_stream.push(format!(
" items.push_back({});",
path_layout_item_data_for_elem(elem)
));
}
}
" {items.data(), std::size(items)},".to_owned()
};
code_stream.push(format!(
" auto path = {};",
compile_path(&path_layout.path, component)
));
code_stream
.push(format!(" auto x = {};", layout_prop(&path_layout.rect.x_reference)));
code_stream
.push(format!(" auto y = {};", layout_prop(&path_layout.rect.y_reference)));
code_stream.push(format!(
" auto width = {};",
layout_prop(&path_layout.rect.width_reference)
));
code_stream.push(format!(
" auto height = {};",
layout_prop(&path_layout.rect.height_reference)
));
code_stream.push(format!(
" auto offset = {};",
layout_prop(&Some(path_layout.offset_reference.clone()))
));
code_stream.push(" sixtyfps::PathLayoutData pl { ".into());
code_stream.push(" &path,".to_owned());
code_stream.push(slice);
code_stream.push(" x, y, width, height, offset".to_owned());
code_stream.push(" };".to_owned());
code_stream.push(" sixtyfps::sixtyfps_solve_path_layout(&pl);".to_owned());
code_stream.push("}".to_owned());
}
}
}
}
fn compute_layout(
component: &Rc<Component>,
repeater_layout_code: &mut Vec<String>,
) -> (Vec<String>, Vec<String>) {
let intro = format!(
"[[maybe_unused]] auto self = reinterpret_cast<const {ty}*>(component.instance);",
ty = component_id(component)
);
let mut res = vec![intro.clone()];
let mut layout_info = vec![
intro.clone(),
"auto layout_info = self->root_item().vtable->layouting_info(self->root_item(), &self->window);".into(),
];
let component_layouts = component.layouts.borrow();
component_layouts.iter().enumerate().for_each(|(idx, layout)| {
let mut inverse_layout_tree = Vec::new();
res.push(" {".into());
let layout_item = crate::layout::gen::collect_layouts_recursively(
&mut inverse_layout_tree,
layout,
component,
);
if component_layouts.main_layout == Some(idx) {
layout_info = vec![
intro.clone(),
format!("sixtyfps::LayoutInfo layout_info = {};", layout_item.layout_info()),
];
}
let mut creation_code = 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,
})
.collect::<Vec<_>>();
if component_layouts.main_layout == Some(idx) {
layout_info.splice(1..1, creation_code.iter().cloned());
}
res.append(&mut creation_code);
inverse_layout_tree
.iter()
.rev()
.for_each(|layout| layout.emit_solve_calls(component, &mut res));
res.push(" }".into());
});
let root_constraints = &component.layouts.borrow().root_constraints;
if root_constraints.has_explicit_restrictions() {
layout_info.push("auto layout_info_other = layout_info;".into());
for (expr, name) in root_constraints.for_each_restrictions() {
layout_info.push(format!(
"layout_info_other.{} = {}.get();",
name,
access_named_reference(expr, component, "self")
));
}
layout_info.push("return layout_info.merge(layout_info_other);".into());
} else {
layout_info.push("return layout_info;".into());
}
res.append(repeater_layout_code);
(res, layout_info)
}
fn compile_path(path: &crate::expression_tree::Path, component: &Rc<Component>) -> String {
match path {
crate::expression_tree::Path::Elements(elements) => {
@ -2474,4 +2175,4 @@ fn return_compile_expression(
format!("return {}", e)
}
}
}
}

View file

@ -1431,13 +1431,13 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
Expression::ComputeLayoutInfo(Layout::GridLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let cells = grid_layout_cell_data(layout, component);
quote!(grid_layout_info(&Slice::from_slice(&#cells), #spacing, #padding))
quote!(grid_layout_info(Slice::from_slice(&#cells), #spacing, #padding))
}
Expression::ComputeLayoutInfo(Layout::BoxLayout(layout)) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, component);
let (cells, alignment) = box_layout_data(layout, component, None);
let is_horizontal = layout.is_horizontal;
quote!(box_layout_info(&Slice::from_slice(&#cells), #spacing, #padding, #alignment, #is_horizontal))
quote!(box_layout_info(Slice::from_slice(&#cells), #spacing, #padding, #alignment, #is_horizontal))
}
Expression::ComputeLayoutInfo(Layout::PathLayout(_)) => unimplemented!(),
Expression::SolveLayout(Layout::GridLayout(layout)) => {
@ -1618,28 +1618,6 @@ fn compile_assignment(
}
}
fn apply_layout_constraint(
layout_info: TokenStream,
constraints: &crate::layout::LayoutConstraints,
component: &Rc<Component>,
) -> TokenStream {
if constraints.has_explicit_restrictions() {
let (name, expr): (Vec<_>, Vec<_>) = constraints
.for_each_restrictions()
.map(|(e, s)| {
(format_ident!("{}", s), access_named_reference(e, component, quote!(_self)))
})
.unzip();
quote!({
let mut layout_info = #layout_info;
#(layout_info.#name = #expr.get();)*
layout_info
})
} else {
layout_info
}
}
fn grid_layout_cell_data(
layout: &crate::layout::GridLayout,
component: &Rc<Component>,
@ -1649,11 +1627,7 @@ fn grid_layout_cell_data(
let row = c.row;
let colspan = c.colspan;
let rowspan = c.rowspan;
let layout_info = apply_layout_constraint(
get_layout_info(&c.item.element, component),
&c.item.constraints,
component,
);
let layout_info = get_layout_info(&c.item.element, component, &c.item.constraints);
quote!(GridLayoutCellData {
col: #col,
row: #row,
@ -1684,11 +1658,7 @@ fn box_layout_data(
if repeater_count == 0 {
let cells = layout.elems.iter().map(|li| {
let layout_info = apply_layout_constraint(
get_layout_info(&li.element, component),
&li.constraints,
component,
);
let layout_info = get_layout_info(&li.element, component, &li.constraints);
quote!(BoxLayoutCellData { constraint: #layout_info })
});
if let Some((ri, _)) = &mut repeated_indices {
@ -1730,11 +1700,7 @@ fn box_layout_data(
}
}
} else {
let layout_info = apply_layout_constraint(
get_layout_info(&item.element, component),
&item.constraints,
component,
);
let layout_info = get_layout_info(&item.element, component, &item.constraints);
fixed_count += 1;
push_code = quote! {
#push_code
@ -1804,11 +1770,7 @@ fn layout_geometry_width_height(
fn compute_layout(component: &Rc<Component>) -> TokenStream {
let elem = &component.root_element;
let mut layout_info = get_layout_info(elem, component);
layout_info =
apply_layout_constraint(layout_info, &component.root_constraints.borrow(), component);
let layout_info = get_layout_info(elem, component, &component.root_constraints.borrow());
quote! {
fn layout_info(self: ::core::pin::Pin<&Self>) -> sixtyfps::re_exports::LayoutInfo {
#![allow(unused)]
@ -1819,14 +1781,34 @@ fn compute_layout(component: &Rc<Component>) -> TokenStream {
}
}
fn get_layout_info(elem: &ElementRc, component: &Rc<Component>) -> TokenStream {
if let Some(layout_info_prop) = &elem.borrow().layout_info_prop {
fn get_layout_info(
elem: &ElementRc,
component: &Rc<Component>,
constraints: &crate::layout::LayoutConstraints,
) -> TokenStream {
let layout_info = if let Some(layout_info_prop) = &elem.borrow().layout_info_prop {
let li = access_named_reference(layout_info_prop, component, quote!(_self));
quote! {#li.get()}
} else {
let elem_id = format_ident!("{}", elem.borrow().id);
let inner_component_id = inner_component_id(component);
quote!(#inner_component_id::FIELD_OFFSETS.#elem_id.apply_pin(_self).layouting_info(&_self.window))
};
if constraints.has_explicit_restrictions() {
let (name, expr): (Vec<_>, Vec<_>) = constraints
.for_each_restrictions()
.map(|(e, s)| {
(format_ident!("{}", s), access_named_reference(e, component, quote!(_self)))
})
.unzip();
quote!({
let mut layout_info = #layout_info;
#(layout_info.#name = #expr.get();)*
layout_info
})
} else {
layout_info
}
}

View file

@ -103,7 +103,7 @@ fn lower_grid_layout(
};
let layout_cache_prop = create_new_prop(grid_layout_element, "layout_cache", Type::LayoutCache);
let layout_info_prop = create_new_prop(grid_layout_element, "layout_info", layout_info_type());
let layout_info_prop = create_new_prop(grid_layout_element, "layoutinfo", layout_info_type());
let mut row = 0;
let mut col = 0;
@ -203,7 +203,7 @@ fn lower_box_layout(
};
let layout_cache_prop = create_new_prop(layout_element, "layout_cache", Type::LayoutCache);
let layout_info_prop = create_new_prop(layout_element, "layout_info", layout_info_type());
let layout_info_prop = create_new_prop(layout_element, "layoutinfo", layout_info_type());
let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
for layout_child in &layout_children {

View file

@ -16,6 +16,7 @@ use crate::{slice::Slice, SharedVector};
type Coord = f32;
/// The constraint that applies to an item
// NOTE: when adding new fields, the C++ operator== also need updates
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutInfo {
@ -371,7 +372,7 @@ pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector<Coord> {
}
pub fn grid_layout_info<'a>(
cells: &Slice<'a, GridLayoutCellData>,
cells: Slice<'a, GridLayoutCellData>,
spacing: Coord,
padding: &Padding,
) -> LayoutInfo {
@ -707,7 +708,7 @@ pub fn solve_box_layout(
/// Return the LayoutInfo for a BoxLayout with the given cells.
pub fn box_layout_info<'a>(
cells: &Slice<'a, BoxLayoutCellData>,
cells: Slice<'a, BoxLayoutCellData>,
spacing: Coord,
padding: &Padding,
alignment: LayoutAlignment,
@ -914,13 +915,16 @@ pub(crate) mod ffi {
use super::*;
#[no_mangle]
pub extern "C" fn sixtyfps_solve_grid_layout(data: &GridLayoutData) {
super::solve_grid_layout(data)
pub extern "C" fn sixtyfps_solve_grid_layout(
data: &GridLayoutData,
result: &mut SharedVector<Coord>,
) {
*result = super::solve_grid_layout(data)
}
#[no_mangle]
pub extern "C" fn sixtyfps_grid_layout_info<'a>(
cells: &Slice<'a, GridLayoutCellData<'a>>,
cells: Slice<'a, GridLayoutCellData>,
spacing: Coord,
padding: &Padding,
) -> LayoutInfo {
@ -928,14 +932,19 @@ pub(crate) mod ffi {
}
#[no_mangle]
pub extern "C" fn sixtyfps_solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
super::solve_box_layout(data, is_horizontal)
pub extern "C" fn sixtyfps_solve_box_layout(
data: &BoxLayoutData,
is_horizontal: bool,
repeater_indexes: Slice<u32>,
result: &mut SharedVector<Coord>,
) {
*result = super::solve_box_layout(data, is_horizontal, repeater_indexes)
}
#[no_mangle]
/// Return the LayoutInfo for a BoxLayout with the given cells.
pub extern "C" fn sixtyfps_box_layout_info<'a>(
cells: &Slice<'a, BoxLayoutCellData<'a>>,
cells: Slice<'a, BoxLayoutCellData>,
spacing: Coord,
padding: &Padding,
alignment: LayoutAlignment,
@ -945,7 +954,11 @@ pub(crate) mod ffi {
}
#[no_mangle]
pub extern "C" fn sixtyfps_solve_path_layout(data: &PathLayoutData) {
super::solve_path_layout(data)
pub extern "C" fn sixtyfps_solve_path_layout(
data: &PathLayoutData,
repeater_indexes: Slice<u32>,
result: &mut SharedVector<Coord>,
) {
*result = super::solve_path_layout(data, repeater_indexes)
}
}

View file

@ -34,13 +34,13 @@ pub(crate) fn compute_layout_info(lay: &Layout, local_context: &mut EvalLocalCon
Layout::GridLayout(grid_layout) => {
let cells = grid_layout_data(grid_layout, component, &expr_eval);
let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, &expr_eval);
core_layout::grid_layout_info(&Slice::from(cells.as_slice()), spacing, &padding).into()
core_layout::grid_layout_info(Slice::from(cells.as_slice()), spacing, &padding).into()
}
Layout::BoxLayout(box_layout) => {
let (cells, alignment) = box_layout_data(box_layout, component, &expr_eval, None);
let (padding, spacing) = padding_and_spacing(&box_layout.geometry, &expr_eval);
core_layout::box_layout_info(
&Slice::from(cells.as_slice()),
Slice::from(cells.as_slice()),
spacing,
&padding,
alignment,

View file

@ -274,7 +274,8 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
);
config.export.body.insert(
"LayoutInfo".to_owned(),
" inline LayoutInfo merge(const LayoutInfo &other) const;".into(),
" inline LayoutInfo merge(const LayoutInfo &other) const;
friend inline LayoutInfo operator+(const LayoutInfo &a, const LayoutInfo &b) { return a.merge(b); }".into(),
);
config
.export