mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-27 20:42:25 +00:00
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:
parent
8edb9bd7f8
commit
4b75ec0ee8
10 changed files with 309 additions and 139 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -71,31 +71,29 @@ 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();
|
||||
|
||||
if let Some(path_element) = element_types.get(element_name) {
|
||||
let element_type = match path_element {
|
||||
Type::Builtin(b) => b.clone(),
|
||||
_ => panic!(
|
||||
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 {
|
||||
Type::Builtin(b) => b.clone(),
|
||||
_ => panic!(
|
||||
"Incorrect type registry -- expected built-in type for path element {}",
|
||||
element_name
|
||||
),
|
||||
};
|
||||
let bindings = std::mem::take(&mut child.bindings);
|
||||
Some(PathElement { element_type, bindings })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
let bindings = std::mem::take(&mut child.borrow_mut().bindings);
|
||||
path_data.push(PathElement { element_type, bindings });
|
||||
} else {
|
||||
elem.children.push(child);
|
||||
}
|
||||
}
|
||||
crate::expression_tree::Path::Elements(path_data)
|
||||
};
|
||||
|
||||
elem.bindings.insert("elements".into(), Expression::PathElements { elements: path_data });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
28
tests/cases/path_layout.60
Normal file
28
tests/cases/path_layout.60
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue