mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 22:54:36 +00:00
Add support for SVG commands in Path elements
Using the commands property we can just paste SVG paths. This makes it much easier to write examples/demos. A good online path designer is for example https://codepen.io/anthonydugois/pen/mewdyZ
This commit is contained in:
parent
be60079f21
commit
251ef7fc97
14 changed files with 433 additions and 89 deletions
|
@ -7,6 +7,12 @@ namespace sixtyfps {
|
||||||
|
|
||||||
using internal::types::PathArcTo;
|
using internal::types::PathArcTo;
|
||||||
using internal::types::PathElement;
|
using internal::types::PathElement;
|
||||||
|
using internal::types::PathEvent;
|
||||||
|
using internal::types::PathEventBegin;
|
||||||
|
using internal::types::PathEventCubic;
|
||||||
|
using internal::types::PathEventEnd;
|
||||||
|
using internal::types::PathEventLine;
|
||||||
|
using internal::types::PathEventQuadratic;
|
||||||
using internal::types::PathLineTo;
|
using internal::types::PathLineTo;
|
||||||
|
|
||||||
struct PathElements
|
struct PathElements
|
||||||
|
@ -20,6 +26,11 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathElements(const PathEvent *firstEvent, size_t count)
|
||||||
|
: data(Data::PathEvents(events_from_array(firstEvent, count)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static SharedArray<PathElement> elements_from_array(const PathElement *firstElement,
|
static SharedArray<PathElement> elements_from_array(const PathElement *firstElement,
|
||||||
size_t count)
|
size_t count)
|
||||||
|
@ -29,6 +40,13 @@ private:
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SharedArray<PathEvent> events_from_array(const PathEvent *firstEvent, size_t count)
|
||||||
|
{
|
||||||
|
SharedArray<PathEvent> tmp;
|
||||||
|
sixtyfps_new_path_events(&tmp, firstEvent, count);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
using Data = internal::types::PathElements;
|
using Data = internal::types::PathElements;
|
||||||
Data data;
|
Data data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -124,5 +124,13 @@ Hello := 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";
|
||||||
|
x: 100;
|
||||||
|
y: 500;
|
||||||
|
stroke_color: black;
|
||||||
|
stroke_width: 2.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ codemap-diagnostic = { version = "0.1.1", optional = true }
|
||||||
codemap = { version = "0.1", optional = true }
|
codemap = { version = "0.1", optional = true }
|
||||||
quote = { version = "1.0", optional = true }
|
quote = { version = "1.0", optional = true }
|
||||||
proc-macro2 = { version = "1.0.17", optional = true }
|
proc-macro2 = { version = "1.0.17", optional = true }
|
||||||
|
lyon = { version = "0.15.8", features = ["svg"] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -131,7 +131,7 @@ pub enum Expression {
|
||||||
},
|
},
|
||||||
|
|
||||||
PathElements {
|
PathElements {
|
||||||
elements: Vec<PathElement>,
|
elements: Path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,8 +248,10 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::PathElements { elements } => {
|
Expression::PathElements { elements } => {
|
||||||
for element in elements {
|
if let Path::Elements(elements) = elements {
|
||||||
element.bindings.values().for_each(|binding| visitor(binding))
|
for element in elements {
|
||||||
|
element.bindings.values().for_each(|binding| visitor(binding))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,8 +302,10 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::PathElements { elements } => {
|
Expression::PathElements { elements } => {
|
||||||
for element in elements {
|
if let Path::Elements(elements) = elements {
|
||||||
element.bindings.values_mut().for_each(|binding| visitor(binding))
|
for element in elements {
|
||||||
|
element.bindings.values_mut().for_each(|binding| visitor(binding))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,7 +334,13 @@ impl Expression {
|
||||||
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
||||||
Expression::Object { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
|
Expression::Object { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
|
||||||
Expression::PathElements { elements } => {
|
Expression::PathElements { elements } => {
|
||||||
elements.iter().all(|element| element.bindings.values().all(|v| v.is_constant()))
|
if let Path::Elements(elements) = elements {
|
||||||
|
elements
|
||||||
|
.iter()
|
||||||
|
.all(|element| element.bindings.values().all(|v| v.is_constant()))
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,6 +366,14 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type PathEvents = Vec<lyon::path::Event<lyon::math::Point, lyon::math::Point>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Path {
|
||||||
|
Elements(Vec<PathElement>),
|
||||||
|
Events(PathEvents),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PathElement {
|
pub struct PathElement {
|
||||||
pub element_type: Rc<BuiltinElement>,
|
pub element_type: Rc<BuiltinElement>,
|
||||||
|
|
|
@ -833,35 +833,50 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
|
||||||
panic!("Expression::Object is not a Type::Object")
|
panic!("Expression::Object is not a Type::Object")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PathElements { elements } => {
|
PathElements { elements } => match elements {
|
||||||
let converted_elements: Vec<String> = elements
|
crate::expression_tree::Path::Elements(elements) => {
|
||||||
.iter()
|
let converted_elements: Vec<String> = elements
|
||||||
.map(|element| {
|
.iter()
|
||||||
let element_initializer = element
|
.map(|element| {
|
||||||
.element_type
|
let element_initializer = element
|
||||||
.cpp_type
|
.element_type
|
||||||
.as_ref()
|
.cpp_type
|
||||||
.map(|cpp_type| {
|
.as_ref()
|
||||||
new_struct_with_bindings(&cpp_type, &element.bindings, component)
|
.map(|cpp_type| {
|
||||||
})
|
new_struct_with_bindings(&cpp_type, &element.bindings, component)
|
||||||
.unwrap_or_default();
|
})
|
||||||
format!(
|
.unwrap_or_default();
|
||||||
"sixtyfps::PathElement::{}({})",
|
format!(
|
||||||
element.element_type.class_name, element_initializer
|
"sixtyfps::PathElement::{}({})",
|
||||||
)
|
element.element_type.class_name, element_initializer
|
||||||
})
|
)
|
||||||
.collect();
|
})
|
||||||
format!(
|
.collect();
|
||||||
r#"[&](){{
|
format!(
|
||||||
|
r#"[&](){{
|
||||||
sixtyfps::PathElement elements[{}] = {{
|
sixtyfps::PathElement elements[{}] = {{
|
||||||
{}
|
{}
|
||||||
}};
|
}};
|
||||||
return sixtyfps::PathElements(&elements[0], sizeof(elements) / sizeof(elements[0]));
|
return sixtyfps::PathElements(&elements[0], sizeof(elements) / sizeof(elements[0]));
|
||||||
}}()"#,
|
}}()"#,
|
||||||
converted_elements.len(),
|
converted_elements.len(),
|
||||||
converted_elements.join(",")
|
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(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
Uncompiled(_) => panic!(),
|
Uncompiled(_) => panic!(),
|
||||||
Invalid => format!("\n#error invalid expression\n"),
|
Invalid => format!("\n#error invalid expression\n"),
|
||||||
}
|
}
|
||||||
|
@ -930,3 +945,92 @@ fn compute_layout(component: &Rc<Component>) -> Vec<String> {
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_path_event(event_name: &str, fields: &[(&str, String)]) -> String {
|
||||||
|
let fields_initialization: Vec<String> = fields
|
||||||
|
.iter()
|
||||||
|
.map(|(prop, initializer)| format!("var.{} = {};", prop, initializer))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"[&](){{
|
||||||
|
{} var{{}};
|
||||||
|
{}
|
||||||
|
return var;
|
||||||
|
}}()"#,
|
||||||
|
event_name,
|
||||||
|
fields_initialization.join("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_path_events(events: &crate::expression_tree::PathEvents) -> Vec<String> {
|
||||||
|
use lyon::path::Event;
|
||||||
|
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.map(|event| match event {
|
||||||
|
Event::Begin { at } => format!(
|
||||||
|
"sixtyfps::PathEvent::Begin({})",
|
||||||
|
new_path_event(
|
||||||
|
"sixtyfps::PathEventBegin",
|
||||||
|
&[("x", at.x.to_string()), ("y", at.y.to_string())]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Event::Line { from, to } => format!(
|
||||||
|
"sixtyfps::PathEvent::Line({})",
|
||||||
|
new_path_event(
|
||||||
|
"sixtyfps::PathEventLine",
|
||||||
|
&[
|
||||||
|
("from_x", from.x.to_string()),
|
||||||
|
("from_y", from.y.to_string()),
|
||||||
|
("to_x", to.x.to_string()),
|
||||||
|
("to_y", to.y.to_string())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Event::Quadratic { from, ctrl, to } => format!(
|
||||||
|
"sixtyfps::PathEvent::Quadratic({})",
|
||||||
|
new_path_event(
|
||||||
|
"sixtyfps::PathEventQuadratic",
|
||||||
|
&[
|
||||||
|
("from_x", from.x.to_string()),
|
||||||
|
("from_y", from.y.to_string()),
|
||||||
|
("control_x", ctrl.x.to_string()),
|
||||||
|
("control_y", ctrl.y.to_string()),
|
||||||
|
("to_x", to.x.to_string()),
|
||||||
|
("to_y", to.y.to_string())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Event::Cubic { from, ctrl1, ctrl2, to } => format!(
|
||||||
|
"sixtyfps::PathEvent::Cubic({})",
|
||||||
|
new_path_event(
|
||||||
|
"sixtyfps::PathEventCubic",
|
||||||
|
&[
|
||||||
|
("from_x", from.x.to_string()),
|
||||||
|
("from_y", from.y.to_string()),
|
||||||
|
("control1_x", ctrl1.x.to_string()),
|
||||||
|
("control1_y", ctrl1.y.to_string()),
|
||||||
|
("control2_x", ctrl2.x.to_string()),
|
||||||
|
("control2_y", ctrl2.y.to_string()),
|
||||||
|
("to_x", to.x.to_string()),
|
||||||
|
("to_y", to.y.to_string())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Event::End { last, first, close } => format!(
|
||||||
|
"sixtyfps::PathEvent::End({})",
|
||||||
|
new_path_event(
|
||||||
|
"sixtyfps::PathEventEnd",
|
||||||
|
&[
|
||||||
|
("first_x", first.x.to_string()),
|
||||||
|
("first_y", first.y.to_string()),
|
||||||
|
("last_x", last.x.to_string()),
|
||||||
|
("last_y", last.y.to_string()),
|
||||||
|
("close", close.to_string())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
|
@ -678,28 +678,29 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
|
||||||
panic!("Expression::Object is not a Type::Object")
|
panic!("Expression::Object is not a Type::Object")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::PathElements { elements } => {
|
Expression::PathElements { elements } => match elements {
|
||||||
let converted_elements: Vec<TokenStream> = elements
|
crate::expression_tree::Path::Elements(elements) => {
|
||||||
.iter()
|
let converted_elements: Vec<TokenStream> = elements
|
||||||
.map(|element| {
|
.iter()
|
||||||
let mut bindings = element
|
.map(|element| {
|
||||||
.bindings
|
let mut bindings = element
|
||||||
.iter()
|
.bindings
|
||||||
.map(|(property, expr)| {
|
.iter()
|
||||||
let prop_ident = quote::format_ident!("{}", property);
|
.map(|(property, expr)| {
|
||||||
let binding_expr = compile_expression(expr, component);
|
let prop_ident = quote::format_ident!("{}", property);
|
||||||
|
let binding_expr = compile_expression(expr, component);
|
||||||
|
|
||||||
quote!(#prop_ident: #binding_expr as _).to_string()
|
quote!(#prop_ident: #binding_expr as _).to_string()
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
if bindings.len() < element.element_type.properties.len() {
|
if bindings.len() < element.element_type.properties.len() {
|
||||||
bindings.push("..Default::default()".into())
|
bindings.push("..Default::default()".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
let bindings = bindings.join(",");
|
let bindings = bindings.join(",");
|
||||||
|
|
||||||
let ctor_format_string = element
|
let ctor_format_string = element
|
||||||
.element_type
|
.element_type
|
||||||
.rust_type_constructor
|
.rust_type_constructor
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -707,17 +708,22 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
|
||||||
"Unexpected error in type registry: path element is lacking rust type name",
|
"Unexpected error in type registry: path element is lacking rust type name",
|
||||||
);
|
);
|
||||||
|
|
||||||
ctor_format_string
|
ctor_format_string
|
||||||
.replace("{}", &bindings)
|
.replace("{}", &bindings)
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Error parsing rust path element constructor")
|
.expect("Error parsing rust path element constructor")
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
quote!(sixtyfps::re_exports::PathElements::SharedElements(
|
quote!(sixtyfps::re_exports::PathElements::SharedElements(
|
||||||
sixtyfps::re_exports::SharedArray::<sixtyfps::re_exports::PathElement>::from(&[#(#converted_elements),*])
|
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))
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,3 +797,54 @@ fn compute_layout(component: &Component) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_path_events(events: &crate::expression_tree::PathEvents) -> TokenStream {
|
||||||
|
use lyon::path::Event;
|
||||||
|
|
||||||
|
let converted_events: Vec<proc_macro2::TokenStream> = events
|
||||||
|
.iter()
|
||||||
|
.map(|event| match event {
|
||||||
|
Event::Begin { at } => {
|
||||||
|
let x = at.x;
|
||||||
|
let y = at.y;
|
||||||
|
quote!(sixtyfps::re_exports::PathEvent::Begin(sixtyfps::re_exports::PathEventBegin{x: #x, y: #y}))
|
||||||
|
}
|
||||||
|
Event::Line { from, to } => {
|
||||||
|
let from_x = from.x;
|
||||||
|
let from_y = from.y;
|
||||||
|
let to_x = to.x;
|
||||||
|
let to_y = to.y;
|
||||||
|
quote!(sixtyfps::re_exports::PathEvent::Line(sixtyfps::re_exports::PathEventLine{from_x: #from_x, from_y: #from_y, to_x: #to_x, to_y: #to_y}))
|
||||||
|
}
|
||||||
|
Event::Quadratic { from, ctrl, to } => {
|
||||||
|
let from_x = from.x;
|
||||||
|
let from_y = from.y;
|
||||||
|
let control_x = ctrl.x;
|
||||||
|
let control_y = ctrl.y;
|
||||||
|
let to_x = to.x;
|
||||||
|
let to_y = to.y;
|
||||||
|
quote!(sixtyfps::re_exports::PathEvent::Quadratic(sixtyfps::re_exports::PathEventQuadratic{from_x: #from_x, from_y: #from_y, control_x: #control_x, control_y: #control_y, to_x: #to_x, to_y: #to_y}))
|
||||||
|
}
|
||||||
|
Event::Cubic { from, ctrl1, ctrl2, to } => {
|
||||||
|
let from_x = from.x;
|
||||||
|
let from_y = from.y;
|
||||||
|
let control1_x = ctrl1.x;
|
||||||
|
let control1_y = ctrl1.y;
|
||||||
|
let control2_x = ctrl2.x;
|
||||||
|
let control2_y = ctrl2.y;
|
||||||
|
let to_x = to.x;
|
||||||
|
let to_y = to.y;
|
||||||
|
quote!(sixtyfps::re_exports::PathEvent::Cubic(sixtyfps::re_exports::PathEventCubic{from_x: #from_x, from_y: #from_y, control1_x: #control1_x, control1_y: #control1_y, control2_x: #control2_x, control2_y: #control2_y, to_x: #to_x, to_y: #to_y}))
|
||||||
|
}
|
||||||
|
Event::End { last, first, close } => {
|
||||||
|
let first_x = first.x;
|
||||||
|
let first_y = first.y;
|
||||||
|
let last_x = last.x;
|
||||||
|
let last_y = last.y;
|
||||||
|
quote!(sixtyfps::re_exports::PathEvent::End(sixtyfps::re_exports::PathEventEnd{first_x: #first_x, first_y: #first_y, last_x: #last_x, last_y: #last_y, close: #close}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
quote!(sixtyfps::re_exports::SharedArray::<sixtyfps::re_exports::PathEvent>::from(&[#(#converted_events),*]))
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub fn run_passes(
|
||||||
passes::resolving::resolve_expressions(doc, diag, tr);
|
passes::resolving::resolve_expressions(doc, diag, tr);
|
||||||
passes::inlining::inline(doc);
|
passes::inlining::inline(doc);
|
||||||
passes::lower_layout::lower_layouts(&doc.root_component, diag);
|
passes::lower_layout::lower_layouts(&doc.root_component, diag);
|
||||||
passes::compile_paths::compile_paths(&doc.root_component, tr);
|
passes::compile_paths::compile_paths(&doc.root_component, tr, diag);
|
||||||
passes::unique_id::assign_unique_id(&doc.root_component);
|
passes::unique_id::assign_unique_id(&doc.root_component);
|
||||||
if compiler_config.embed_resources {
|
if compiler_config.embed_resources {
|
||||||
passes::collect_resources::collect_resources(&doc.root_component);
|
passes::collect_resources::collect_resources(&doc.root_component);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::diagnostics::Diagnostics;
|
||||||
/// This pass converts the verbose markup used for paths, such as
|
/// This pass converts the verbose markup used for paths, such as
|
||||||
/// Path {
|
/// Path {
|
||||||
/// LineTo { ... } ArcTo { ... }
|
/// LineTo { ... } ArcTo { ... }
|
||||||
|
@ -10,7 +11,11 @@ use crate::object_tree::*;
|
||||||
use crate::typeregister::Type;
|
use crate::typeregister::Type;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn compile_paths(component: &Rc<Component>, tr: &mut crate::typeregister::TypeRegister) {
|
pub fn compile_paths(
|
||||||
|
component: &Rc<Component>,
|
||||||
|
tr: &mut crate::typeregister::TypeRegister,
|
||||||
|
diag: &mut Diagnostics,
|
||||||
|
) {
|
||||||
let path_type = tr.lookup("Path");
|
let path_type = tr.lookup("Path");
|
||||||
let path_type = path_type.as_builtin();
|
let path_type = path_type.as_builtin();
|
||||||
|
|
||||||
|
@ -25,31 +30,70 @@ pub fn compile_paths(component: &Rc<Component>, tr: &mut crate::typeregister::Ty
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut elem = elem_.borrow_mut();
|
let mut elem = elem_.borrow_mut();
|
||||||
let mut children = std::mem::take(&mut elem.children);
|
|
||||||
|
|
||||||
let path_data: Vec<_> = children
|
let path_data = if let Some(commands_expr) = elem.bindings.remove("commands") {
|
||||||
.iter_mut()
|
if let Some(path_child) = elem.children.iter().find(|child| {
|
||||||
.filter_map(|child| {
|
path_type
|
||||||
let mut child = child.borrow_mut();
|
.additional_accepted_child_types
|
||||||
let element_name = &child.base_type.as_builtin().class_name;
|
.contains_key(&child.borrow().base_type.as_builtin().class_name)
|
||||||
|
}) {
|
||||||
|
diag.push_error(
|
||||||
|
"Path elements cannot be mixed with the use of the SVG commands property."
|
||||||
|
.into(),
|
||||||
|
path_child.borrow().span(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path_element) =
|
let commands = match commands_expr {
|
||||||
path_type.additional_accepted_child_types.get(element_name)
|
Expression::StringLiteral(commands) => commands,
|
||||||
{
|
_ => {
|
||||||
let element_type = match path_element {
|
diag.push_error(
|
||||||
Type::Builtin(b) => b.clone(),
|
"The commands property only accepts string literals.".into(),
|
||||||
_ => panic!(
|
elem.span(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_builder = lyon::path::Path::builder().with_svg();
|
||||||
|
let path = lyon::svg::path_utils::build_path(path_builder, &commands);
|
||||||
|
match path {
|
||||||
|
Ok(path) => Path::Events(path.into_iter().collect()),
|
||||||
|
Err(err) => {
|
||||||
|
diag.push_error(format!("Error parsing SVG commands: {:?}", err), elem.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut children = std::mem::take(&mut elem.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;
|
||||||
|
|
||||||
|
if let Some(path_element) =
|
||||||
|
path_type.additional_accepted_child_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 {}",
|
"Incorrect type registry -- expected built-in type for path element {}",
|
||||||
element_name
|
element_name
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let bindings = std::mem::take(&mut child.bindings);
|
let bindings = std::mem::take(&mut child.bindings);
|
||||||
Some(PathElement { element_type, bindings })
|
Some(PathElement { element_type, bindings })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
elem.bindings.insert("elements".into(), Expression::PathElements { elements: path_data });
|
elem.bindings.insert("elements".into(), Expression::PathElements { elements: path_data });
|
||||||
});
|
});
|
||||||
|
|
18
sixtyfps_compiler/tests/basic/svg_path.60
Normal file
18
sixtyfps_compiler/tests/basic/svg_path.60
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
|
||||||
|
TestCase := Rectangle {
|
||||||
|
Path {
|
||||||
|
commands: "M 350 300 L 550 300 ";
|
||||||
|
LineTo { x: 10; y: 100; }
|
||||||
|
// ^error{Path elements cannot be mixed with the use of the SVG commands property.}
|
||||||
|
}
|
||||||
|
|
||||||
|
property<string> cmds: "M 350 300 L 550 300 ";
|
||||||
|
|
||||||
|
Path {
|
||||||
|
// ^error{The commands property only accepts string literals.}
|
||||||
|
commands: cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -281,6 +281,7 @@ impl TypeRegister {
|
||||||
path.properties.insert("fill_color".to_owned(), Type::Color);
|
path.properties.insert("fill_color".to_owned(), Type::Color);
|
||||||
path.properties.insert("stroke_color".to_owned(), Type::Color);
|
path.properties.insert("stroke_color".to_owned(), Type::Color);
|
||||||
path.properties.insert("stroke_width".to_owned(), Type::Float32);
|
path.properties.insert("stroke_width".to_owned(), Type::Float32);
|
||||||
|
path.properties.insert("commands".to_owned(), Type::String);
|
||||||
path.disallow_global_types_as_child_elements = true;
|
path.disallow_global_types_as_child_elements = true;
|
||||||
|
|
||||||
let path_elements = {
|
let path_elements = {
|
||||||
|
|
|
@ -467,6 +467,46 @@ impl From<&PathEvent> for lyon::path::Event<lyon::math::Point, lyon::math::Point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&lyon::path::Event<lyon::math::Point, lyon::math::Point>> for PathEvent {
|
||||||
|
fn from(event: &lyon::path::Event<lyon::math::Point, lyon::math::Point>) -> Self {
|
||||||
|
use lyon::path::Event;
|
||||||
|
match event {
|
||||||
|
Event::Begin { at } => PathEvent::Begin(PathEventBegin { x: at.x, y: at.y }),
|
||||||
|
Event::Line { from, to } => PathEvent::Line(PathEventLine {
|
||||||
|
from_x: from.x,
|
||||||
|
from_y: from.y,
|
||||||
|
to_x: to.x,
|
||||||
|
to_y: to.y,
|
||||||
|
}),
|
||||||
|
Event::Quadratic { from, ctrl, to } => PathEvent::Quadratic(PathEventQuadratic {
|
||||||
|
from_x: from.x,
|
||||||
|
from_y: from.y,
|
||||||
|
control_x: ctrl.x,
|
||||||
|
control_y: ctrl.y,
|
||||||
|
to_x: to.x,
|
||||||
|
to_y: to.y,
|
||||||
|
}),
|
||||||
|
Event::Cubic { from, ctrl1, ctrl2, to } => PathEvent::Cubic(PathEventCubic {
|
||||||
|
from_x: from.x,
|
||||||
|
from_y: from.y,
|
||||||
|
control1_x: ctrl1.x,
|
||||||
|
control1_y: ctrl1.y,
|
||||||
|
control2_x: ctrl2.x,
|
||||||
|
control2_y: ctrl2.y,
|
||||||
|
to_x: to.x,
|
||||||
|
to_y: to.y,
|
||||||
|
}),
|
||||||
|
Event::End { last, first, close } => PathEvent::End(PathEventEnd {
|
||||||
|
first_x: first.x,
|
||||||
|
first_y: first.y,
|
||||||
|
last_x: last.x,
|
||||||
|
last_y: last.y,
|
||||||
|
close: *close,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// PathElements holds the elements of a path.
|
/// PathElements holds the elements of a path.
|
||||||
|
@ -572,6 +612,17 @@ pub unsafe extern "C" fn sixtyfps_new_path_elements(
|
||||||
core::ptr::write(out as *mut crate::SharedArray<PathElement>, arr.clone());
|
core::ptr::write(out as *mut crate::SharedArray<PathElement>, arr.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
/// This function is used for the low-level C++ interface to allocate the backing vector for a shared path event array.
|
||||||
|
pub unsafe extern "C" fn sixtyfps_new_path_events(
|
||||||
|
out: *mut c_void,
|
||||||
|
first_event: *const PathEvent,
|
||||||
|
count: usize,
|
||||||
|
) {
|
||||||
|
let arr = crate::SharedArray::from(std::slice::from_raw_parts(first_event, count));
|
||||||
|
core::ptr::write(out as *mut crate::SharedArray<PathEvent>, arr.clone());
|
||||||
|
}
|
||||||
|
|
||||||
/// Each item return a RenderingPrimitive to the backend with information about what to draw.
|
/// Each item return a RenderingPrimitive to the backend with information about what to draw.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
|
@ -27,6 +27,7 @@ fn main() {
|
||||||
"PathElements",
|
"PathElements",
|
||||||
"PathElement",
|
"PathElement",
|
||||||
"sixtyfps_new_path_elements",
|
"sixtyfps_new_path_elements",
|
||||||
|
"sixtyfps_new_path_events",
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
|
@ -85,7 +86,12 @@ fn main() {
|
||||||
(vec!["Resource"], "sixtyfps_resource_internal.h"),
|
(vec!["Resource"], "sixtyfps_resource_internal.h"),
|
||||||
(vec!["Color"], "sixtyfps_color_internal.h"),
|
(vec!["Color"], "sixtyfps_color_internal.h"),
|
||||||
(
|
(
|
||||||
vec!["PathElements", "PathElement", "sixtyfps_new_path_elements"],
|
vec![
|
||||||
|
"PathElements",
|
||||||
|
"PathElement",
|
||||||
|
"sixtyfps_new_path_elements",
|
||||||
|
"sixtyfps_new_path_events",
|
||||||
|
],
|
||||||
"sixtyfps_pathelements_internal.h",
|
"sixtyfps_pathelements_internal.h",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -98,6 +104,7 @@ fn main() {
|
||||||
"sixtyfps_component_window_drop",
|
"sixtyfps_component_window_drop",
|
||||||
"sixtyfps_component_window_run",
|
"sixtyfps_component_window_run",
|
||||||
"sixtyfps_new_path_elements",
|
"sixtyfps_new_path_elements",
|
||||||
|
"sixtyfps_new_path_events",
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|exclusion| rust_types.iter().find(|inclusion| inclusion == exclusion).is_none())
|
.filter(|exclusion| rust_types.iter().find(|inclusion| inclusion == exclusion).is_none())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::convert::{TryFrom, TryInto};
|
use core::convert::{TryFrom, TryInto};
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use sixtyfps_compilerlib::expression_tree::{
|
use sixtyfps_compilerlib::expression_tree::{
|
||||||
Expression, NamedReference, PathElement as ExprPathElement,
|
Expression, NamedReference, Path as ExprPath, PathElement as ExprPathElement,
|
||||||
};
|
};
|
||||||
use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type};
|
use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type};
|
||||||
use sixtyfps_corelib as corelib;
|
use sixtyfps_corelib as corelib;
|
||||||
|
@ -323,13 +323,22 @@ pub fn eval_expression(
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
Expression::PathElements { elements } => {
|
Expression::PathElements { elements } => {
|
||||||
Value::PathElements(PathElements::SharedElements(sixtyfps_corelib::SharedArray::<
|
match elements {
|
||||||
sixtyfps_corelib::abi::datastructures::PathElement,
|
ExprPath::Elements(elements) => Value::PathElements(PathElements::SharedElements(
|
||||||
>::from_iter(
|
sixtyfps_corelib::SharedArray::<
|
||||||
elements
|
sixtyfps_corelib::abi::datastructures::PathElement,
|
||||||
.iter()
|
>::from_iter(elements.iter().map(|element| {
|
||||||
.map(|element| convert_path_element(element, component_type, eval_context)),
|
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()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,5 +127,13 @@ Hello := Rectangle {
|
||||||
|
|
||||||
Close {}
|
Close {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
x: 100;
|
||||||
|
y: 500;
|
||||||
|
stroke_color: black;
|
||||||
|
stroke_width: 2.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue