Lay out items on a path

This works, but it's still missing support for positioning of the layout
itself.
This commit is contained in:
Simon Hausmann 2020-07-08 19:45:34 +02:00
parent 8edb9bd7f8
commit 4b75ec0ee8
10 changed files with 309 additions and 139 deletions

View file

@ -78,11 +78,14 @@ EvaluationContext evaluation_context_for_root_component(const Component *compone
}
// layouts:
using internal::Slice;
using internal::solve_grid_layout;
using internal::Constraint;
using internal::GridLayoutCellData;
using internal::GridLayoutData;
using internal::Constraint;
using internal::PathLayoutData;
using internal::PathLayoutItemData;
using internal::Slice;
using internal::solve_grid_layout;
using internal::solve_path_layout;
// models

View file

@ -81,7 +81,8 @@ pub mod re_exports {
pub use sixtyfps_corelib::abi::slice::Slice;
pub use sixtyfps_corelib::item_tree::visit_item_tree;
pub use sixtyfps_corelib::layout::{
solve_grid_layout, Constraint, GridLayoutCellData, GridLayoutData,
solve_grid_layout, solve_path_layout, Constraint, GridLayoutCellData, GridLayoutData,
PathLayoutData, PathLayoutItemData,
};
pub use sixtyfps_corelib::Color;
pub use sixtyfps_corelib::ComponentVTable_static;

View file

@ -833,50 +833,7 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
panic!("Expression::Object is not a Type::Object")
}
}
PathElements { elements } => match elements {
crate::expression_tree::Path::Elements(elements) => {
let converted_elements: Vec<String> = elements
.iter()
.map(|element| {
let element_initializer = element
.element_type
.cpp_type
.as_ref()
.map(|cpp_type| {
new_struct_with_bindings(&cpp_type, &element.bindings, component)
})
.unwrap_or_default();
format!(
"sixtyfps::PathElement::{}({})",
element.element_type.class_name, element_initializer
)
})
.collect();
format!(
r#"[&](){{
sixtyfps::PathElement elements[{}] = {{
{}
}};
return sixtyfps::PathElements(&elements[0], sizeof(elements) / sizeof(elements[0]));
}}()"#,
converted_elements.len(),
converted_elements.join(",")
)
}
crate::expression_tree::Path::Events(events) => {
let converted_elements: Vec<String> = compile_path_events(events);
format!(
r#"[&](){{
sixtyfps::PathEvent events[{}] = {{
{}
}};
return sixtyfps::PathElements(&events[0], sizeof(events) / sizeof(events[0]));
}}()"#,
converted_elements.len(),
converted_elements.join(",")
)
}
},
PathElements { elements } => compile_path(elements, component),
Uncompiled(_) => panic!(),
Invalid => format!("\n#error invalid expression\n"),
}
@ -889,7 +846,7 @@ fn compute_layout(component: &Rc<Component>) -> Vec<String> {
"[[maybe_unused]] auto self = reinterpret_cast<const {ty}*>(component.instance);",
ty = component_id(component)
));
for grid in component.layout_constraints.borrow().0.iter() {
for grid in component.layout_constraints.borrow().grids.iter() {
res.push("{".to_owned());
res.push(format!(" std::array<sixtyfps::Constraint, {}> row_constr;", grid.row_count()));
res.push(format!(" std::array<sixtyfps::Constraint, {}> col_constr;", grid.col_count()));
@ -943,6 +900,33 @@ fn compute_layout(component: &Rc<Component>) -> Vec<String> {
res.push(" sixtyfps::solve_grid_layout(&grid);".to_owned());
res.push("}".to_owned());
}
for path_layout in component.layout_constraints.borrow().paths.iter() {
res.push("{".to_owned());
res.push(" sixtyfps::PathLayoutItemData items[] = {".to_owned());
for item in &path_layout.elements {
let p = |n: &str| {
if item.borrow().lookup_property(n) == Type::Float32 {
format!("&self->{}.{}", item.borrow().id, n)
} else {
"nullptr".to_owned()
}
};
res.push(format!(" {{ {}, {} }},", p("x"), p("y")));
}
res.push(" };".to_owned());
res.push(format!(" auto path = {};", compile_path(&path_layout.path, component)));
res.push(" sixtyfps::PathLayoutData pl { ".into());
res.push(" &path,".to_owned());
res.push(" {items, std::size(items)}".to_owned());
res.push(" };".to_owned());
res.push(" sixtyfps::solve_path_layout(&pl);".to_owned());
res.push("}".to_owned());
}
res
}
@ -963,6 +947,53 @@ fn new_path_event(event_name: &str, fields: &[(&str, String)]) -> String {
)
}
fn compile_path(path: &crate::expression_tree::Path, component: &Rc<Component>) -> String {
match path {
crate::expression_tree::Path::Elements(elements) => {
let converted_elements: Vec<String> = elements
.iter()
.map(|element| {
let element_initializer = element
.element_type
.cpp_type
.as_ref()
.map(|cpp_type| {
new_struct_with_bindings(&cpp_type, &element.bindings, component)
})
.unwrap_or_default();
format!(
"sixtyfps::PathElement::{}({})",
element.element_type.class_name, element_initializer
)
})
.collect();
format!(
r#"[&](){{
sixtyfps::PathElement elements[{}] = {{
{}
}};
return sixtyfps::PathElements(&elements[0], sizeof(elements) / sizeof(elements[0]));
}}()"#,
converted_elements.len(),
converted_elements.join(",")
)
}
crate::expression_tree::Path::Events(events) => {
let converted_elements: Vec<String> = compile_path_events(events);
format!(
r#"[&](){{
sixtyfps::PathEvent events[{}] = {{
{}
}};
return sixtyfps::PathElements(&events[0], sizeof(events) / sizeof(events[0]));
}}()"#,
converted_elements.len(),
converted_elements.join(",")
)
}
}
}
fn compile_path_events(events: &crate::expression_tree::PathEvents) -> Vec<String> {
use lyon::path::Event;

View file

@ -2,7 +2,7 @@
*/
use crate::diagnostics::{CompilerDiagnostic, Diagnostics};
use crate::expression_tree::{Expression, NamedReference, OperatorClass};
use crate::expression_tree::{Expression, NamedReference, OperatorClass, Path};
use crate::object_tree::{Component, ElementRc};
use crate::parser::Spanned;
use crate::typeregister::Type;
@ -678,58 +678,13 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
panic!("Expression::Object is not a Type::Object")
}
}
Expression::PathElements { elements } => match elements {
crate::expression_tree::Path::Elements(elements) => {
let converted_elements: Vec<TokenStream> = elements
.iter()
.map(|element| {
let mut bindings = element
.bindings
.iter()
.map(|(property, expr)| {
let prop_ident = quote::format_ident!("{}", property);
let binding_expr = compile_expression(expr, component);
quote!(#prop_ident: #binding_expr as _).to_string()
})
.collect::<Vec<String>>();
if bindings.len() < element.element_type.properties.len() {
bindings.push("..Default::default()".into())
}
let bindings = bindings.join(",");
let ctor_format_string = element
.element_type
.rust_type_constructor
.as_ref()
.expect(
"Unexpected error in type registry: path element is lacking rust type name",
);
ctor_format_string
.replace("{}", &bindings)
.parse()
.expect("Error parsing rust path element constructor")
})
.collect();
quote!(sixtyfps::re_exports::PathElements::SharedElements(
sixtyfps::re_exports::SharedArray::<sixtyfps::re_exports::PathElement>::from(&[#(#converted_elements),*])
))
}
crate::expression_tree::Path::Events(events) => {
let events = compile_path_events(events);
quote!(sixtyfps::re_exports::PathElements::PathEvents(#events))
}
},
Expression::PathElements { elements } => compile_path(elements, component),
}
}
fn compute_layout(component: &Component) -> TokenStream {
fn compute_layout(component: &Rc<Component>) -> TokenStream {
let mut layouts = vec![];
for x in component.layout_constraints.borrow().0.iter() {
for x in component.layout_constraints.borrow().grids.iter() {
let within = quote::format_ident!("{}", x.within.borrow().id);
let within_ty =
quote::format_ident!("{}", x.within.borrow().base_type.as_builtin().class_name);
@ -784,6 +739,39 @@ fn compute_layout(component: &Component) -> TokenStream {
});
}
for path_layout in component.layout_constraints.borrow().paths.iter() {
let items = path_layout
.elements
.iter()
.map(|elem| {
let e = quote::format_ident!("{}", elem.borrow().id);
let p = |n: &str| {
if elem.borrow().lookup_property(n) == Type::Float32 {
let n = quote::format_ident!("{}", n);
quote! {Some(&self.#e.#n)}
} else {
quote! {None}
}
};
let x = p("x");
let y = p("y");
quote!(PathLayoutItemData {
x: #x,
y: #y,
})
})
.collect::<Vec<_>>();
let path = compile_path(&path_layout.path, &component);
layouts.push(quote! {
solve_path_layout(&PathLayoutData {
items: Slice::from_slice(&[#( #items ),*]),
elements: &#path,
});
});
}
quote! {
fn layout_info(self: ::core::pin::Pin<&Self>) -> sixtyfps::re_exports::LayoutInfo {
todo!("Implement in rust.rs")
@ -848,3 +836,52 @@ fn compile_path_events(events: &crate::expression_tree::PathEvents) -> TokenStre
quote!(sixtyfps::re_exports::SharedArray::<sixtyfps::re_exports::PathEvent>::from(&[#(#converted_events),*]))
}
fn compile_path(path: &Path, component: &Rc<Component>) -> TokenStream {
match path {
Path::Elements(elements) => {
let converted_elements: Vec<TokenStream> = elements
.iter()
.map(|element| {
let mut bindings = element
.bindings
.iter()
.map(|(property, expr)| {
let prop_ident = quote::format_ident!("{}", property);
let binding_expr = compile_expression(expr, component);
quote!(#prop_ident: #binding_expr as _).to_string()
})
.collect::<Vec<String>>();
if bindings.len() < element.element_type.properties.len() {
bindings.push("..Default::default()".into())
}
let bindings = bindings.join(",");
let ctor_format_string = element
.element_type
.rust_type_constructor
.as_ref()
.expect(
"Unexpected error in type registry: path element is lacking rust type name",
);
ctor_format_string
.replace("{}", &bindings)
.parse()
.expect("Error parsing rust path element constructor")
})
.collect();
quote!(sixtyfps::re_exports::PathElements::SharedElements(
sixtyfps::re_exports::SharedArray::<sixtyfps::re_exports::PathElement>::from(&[#(#converted_elements),*])
))
}
Path::Events(events) => {
let events = compile_path_events(events);
quote!(sixtyfps::re_exports::PathElements::PathEvents(#events))
}
}
}

View file

@ -1,9 +1,13 @@
//! Datastructures used to represent layouts in the compiler
use crate::expression_tree::Path;
use crate::object_tree::ElementRc;
#[derive(Default, Debug)]
pub struct LayoutConstraints(pub Vec<GridLayout>);
pub struct LayoutConstraints {
pub grids: Vec<GridLayout>,
pub paths: Vec<PathLayout>,
}
/// Internal representation of a grid layout
#[derive(Debug)]
@ -24,3 +28,10 @@ impl GridLayout {
self.elems.len()
}
}
/// Internal representation of a path layout
#[derive(Debug)]
pub struct PathLayout {
pub path: Path,
pub elements: Vec<ElementRc>,
}

View file

@ -71,14 +71,13 @@ pub fn compile_paths(
}
}
} else {
let mut children = std::mem::take(&mut elem.children);
let new_children = Vec::with_capacity(elem.children.len());
let old_children = std::mem::replace(&mut elem.children, new_children);
crate::expression_tree::Path::Elements(
children
.iter_mut()
.filter_map(|child| {
let mut child = child.borrow_mut();
let element_name = &child.base_type.as_builtin().class_name;
let mut path_data = Vec::new();
for child in old_children {
let element_name = &child.borrow().base_type.as_builtin().class_name.clone();
if let Some(path_element) = element_types.get(element_name) {
let element_type = match path_element {
@ -88,14 +87,13 @@ pub fn compile_paths(
element_name
),
};
let bindings = std::mem::take(&mut child.bindings);
Some(PathElement { element_type, bindings })
let bindings = std::mem::take(&mut child.borrow_mut().bindings);
path_data.push(PathElement { element_type, bindings });
} else {
None
elem.children.push(child);
}
})
.collect(),
)
}
crate::expression_tree::Path::Elements(path_data)
};
elem.bindings.insert("elements".into(), Expression::PathElements { elements: path_data });

View file

@ -2,6 +2,7 @@
use crate::diagnostics::Diagnostics;
use crate::expression_tree::*;
use crate::layout::*;
use crate::object_tree::*;
use std::rc::Rc;
@ -14,7 +15,7 @@ pub fn lower_layouts(component: &Rc<Component>, diag: &mut Diagnostics) {
let old_children = std::mem::replace(&mut elem.children, new_children);
for child in old_children {
let is_layout =
let is_grid_layout =
if let crate::typeregister::Type::Builtin(be) = &child.borrow().base_type {
assert!(be.class_name != "Row"); // Caught at element lookup time
be.class_name == "GridLayout"
@ -22,7 +23,14 @@ pub fn lower_layouts(component: &Rc<Component>, diag: &mut Diagnostics) {
false
};
if is_layout {
let is_path_layout =
if let crate::typeregister::Type::Builtin(be) = &child.borrow().base_type {
be.class_name == "PathLayout"
} else {
false
};
if is_grid_layout {
let mut grid = GridLayout { within: elem_.clone(), elems: Default::default() };
let mut row = 0;
let mut col = 0;
@ -53,7 +61,26 @@ pub fn lower_layouts(component: &Rc<Component>, diag: &mut Diagnostics) {
}
}
component.optimized_elements.borrow_mut().push(child.clone());
component.layout_constraints.borrow_mut().0.push(grid);
component.layout_constraints.borrow_mut().grids.push(grid);
continue;
} else if is_path_layout {
let layout_elem = child;
let layout_children = std::mem::take(&mut layout_elem.borrow_mut().children);
elem.children.extend(layout_children.iter().cloned());
component.optimized_elements.borrow_mut().push(layout_elem.clone());
let path_elements_expr = match layout_elem.borrow_mut().bindings.remove("elements")
{
Some(Expression::PathElements { elements }) => elements,
_ => {
diag.push_error("Internal error: elements binding in PathLayout does not contain path elements expression".into(), layout_elem.borrow().span());
return;
}
};
component
.layout_constraints
.borrow_mut()
.paths
.push(PathLayout { elements: layout_children, path: path_elements_expr });
continue;
} else {
elem.children.push(child);

View file

@ -500,7 +500,7 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E
let component_type =
&*(component.get_vtable() as *const ComponentVTable as *const ComponentDescription);
for it in &component_type.original.layout_constraints.borrow().0 {
for it in &component_type.original.layout_constraints.borrow().grids {
use sixtyfps_corelib::layout::*;
let mut row_constraint = vec![];
@ -557,6 +557,32 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E
cells: Slice::from(cells.as_slice()),
});
}
for it in &component_type.original.layout_constraints.borrow().paths {
use sixtyfps_corelib::layout::*;
let items = it
.elements
.iter()
.map(|elem| {
let info = &component_type.items[elem.borrow().id.as_str()];
let get_prop = |name| {
info.rtti.properties.get(name).map(|p| {
&*(component.as_ptr().add(info.offset).add(p.offset())
as *const Property<f32>)
})
};
PathLayoutItemData { x: get_prop("x"), y: get_prop("y") }
})
.collect::<Vec<_>>();
let path_elements = eval::convert_path(&it.path, component_type, eval_context);
solve_path_layout(&PathLayoutData {
items: Slice::from(items.as_slice()),
elements: &path_elements,
});
}
}
/// Get the component description from a ComponentRef

View file

@ -323,22 +323,7 @@ pub fn eval_expression(
.collect(),
),
Expression::PathElements { elements } => {
match elements {
ExprPath::Elements(elements) => Value::PathElements(PathElements::SharedElements(
sixtyfps_corelib::SharedArray::<
sixtyfps_corelib::abi::datastructures::PathElement,
>::from_iter(elements.iter().map(|element| {
convert_path_element(element, component_type, eval_context)
})),
)),
ExprPath::Events(events) => {
Value::PathElements(PathElements::PathEvents(sixtyfps_corelib::SharedArray::<
sixtyfps_corelib::abi::datastructures::PathEvent,
>::from_iter(
events.iter().map(|event| event.into()),
)))
}
}
Value::PathElements(convert_path(elements, component_type, eval_context))
}
}
}
@ -378,6 +363,29 @@ pub fn new_struct_with_bindings<
element
}
pub fn convert_path(
path: &ExprPath,
component_type: &crate::ComponentDescription,
eval_context: &corelib::EvaluationContext,
) -> sixtyfps_corelib::abi::datastructures::PathElements {
match path {
ExprPath::Elements(elements) => {
PathElements::SharedElements(sixtyfps_corelib::SharedArray::<
sixtyfps_corelib::abi::datastructures::PathElement,
>::from_iter(
elements
.iter()
.map(|element| convert_path_element(element, component_type, eval_context)),
))
}
ExprPath::Events(events) => PathElements::PathEvents(sixtyfps_corelib::SharedArray::<
sixtyfps_corelib::abi::datastructures::PathEvent,
>::from_iter(
events.iter().map(|event| event.into()),
)),
}
}
fn convert_path_element(
expr_element: &ExprPathElement,
component_type: &crate::ComponentDescription,

View file

@ -0,0 +1,28 @@
TestCase := Rectangle {
Path {
commands: "M 100 300 Q 150 50 250 150 C 250 300 300 300 300 450 A 50 50 0 1 0 450 450 L 550 300";
stroke_color: black;
stroke_width: 2;
}
PathLayout {
commands: "M 100 300 Q 150 50 250 150 C 250 300 300 300 300 450 A 50 50 0 1 0 450 450 L 550 300";
Text {
text: "First item on path";
color: black;
}
Text {
text: "Second item on path";
color: black;
}
Text {
text: "Third item on path";
color: black;
}
}
}