mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Make it possible to use a string binding for Path commands
This was requested a few days ago in the chat and might also present an alternative for #754
This commit is contained in:
parent
ac031a45f8
commit
36e07b8801
11 changed files with 139 additions and 111 deletions
|
@ -37,6 +37,11 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathData(const SharedString &commands)
|
||||||
|
: data(cbindgen_private::types::PathData::Commands(commands))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
friend bool operator==(const PathData &a, const PathData &b)
|
friend bool operator==(const PathData &a, const PathData &b)
|
||||||
{
|
{
|
||||||
if (a.data.tag != b.data.tag)
|
if (a.data.tag != b.data.tag)
|
||||||
|
@ -46,6 +51,8 @@ public:
|
||||||
return a.data.elements._0 == b.data.elements._0;
|
return a.data.elements._0 == b.data.elements._0;
|
||||||
case cbindgen_private::types::PathData::Tag::Events:
|
case cbindgen_private::types::PathData::Tag::Events:
|
||||||
return a.data.events._0 == b.data.events._0 && b.data.events._0 == b.data.events._0;
|
return a.data.events._0 == b.data.events._0 && b.data.events._0 == b.data.events._0;
|
||||||
|
case cbindgen_private::types::PathData::Tag::Commands:
|
||||||
|
return a.data.commands._0 == b.data.commands._0;
|
||||||
case cbindgen_private::types::PathData::Tag::None:
|
case cbindgen_private::types::PathData::Tag::None:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ accordingly.
|
||||||
|
|
||||||
SVG is a popular file format for defining scalable graphics, which are often composed of paths. In SVG
|
SVG is a popular file format for defining scalable graphics, which are often composed of paths. In SVG
|
||||||
paths are composed using [commands](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands),
|
paths are composed using [commands](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands),
|
||||||
which in turn are written in a string literal. In `.60` the path commands are provided to the `commands`
|
which in turn are written in a string. In `.60` the path commands are provided to the `commands`
|
||||||
property. The following example renders a shape consists of an arc and a rectangle, composed of `line-to`,
|
property. The following example renders a shape consists of an arc and a rectangle, composed of `line-to`,
|
||||||
`move-to` and `arc` commands:
|
`move-to` and `arc` commands:
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ Example := Path {
|
||||||
|
|
||||||
The commands are provided in a property:
|
The commands are provided in a property:
|
||||||
|
|
||||||
* **`commands`** (*string): A string literal providing the commands according to the SVG path specification.
|
* **`commands`** (*string): A string providing the commands according to the SVG path specification.
|
||||||
|
|
||||||
#### Path Using SVG Path Elements
|
#### Path Using SVG Path Elements
|
||||||
|
|
||||||
|
|
|
@ -675,6 +675,7 @@ impl Expression {
|
||||||
Path::Events(events, coordinates) => {
|
Path::Events(events, coordinates) => {
|
||||||
events.iter().chain(coordinates.iter()).for_each(visitor);
|
events.iter().chain(coordinates.iter()).for_each(visitor);
|
||||||
}
|
}
|
||||||
|
Path::Commands(commands) => visitor(commands),
|
||||||
},
|
},
|
||||||
Expression::StoreLocalVariable { value, .. } => visitor(&**value),
|
Expression::StoreLocalVariable { value, .. } => visitor(&**value),
|
||||||
Expression::ReadLocalVariable { .. } => {}
|
Expression::ReadLocalVariable { .. } => {}
|
||||||
|
@ -767,6 +768,7 @@ impl Expression {
|
||||||
Path::Events(events, coordinates) => {
|
Path::Events(events, coordinates) => {
|
||||||
events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
|
events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
|
||||||
}
|
}
|
||||||
|
Path::Commands(commands) => visitor(commands),
|
||||||
},
|
},
|
||||||
Expression::StoreLocalVariable { value, .. } => visitor(&mut **value),
|
Expression::StoreLocalVariable { value, .. } => visitor(&mut **value),
|
||||||
Expression::ReadLocalVariable { .. } => {}
|
Expression::ReadLocalVariable { .. } => {}
|
||||||
|
@ -835,15 +837,13 @@ impl Expression {
|
||||||
Expression::UnaryOp { sub, .. } => sub.is_constant(),
|
Expression::UnaryOp { sub, .. } => sub.is_constant(),
|
||||||
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
||||||
Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
|
Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
|
||||||
Expression::PathData(data) => {
|
Expression::PathData(data) => match data {
|
||||||
if let Path::Elements(elements) = data {
|
Path::Elements(elements) => elements
|
||||||
elements
|
.iter()
|
||||||
.iter()
|
.all(|element| element.bindings.values().all(|v| v.borrow().is_constant())),
|
||||||
.all(|element| element.bindings.values().all(|v| v.borrow().is_constant()))
|
Path::Events(_, _) => true,
|
||||||
} else {
|
Path::Commands(_) => false,
|
||||||
true
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
Expression::StoreLocalVariable { .. } => false,
|
Expression::StoreLocalVariable { .. } => false,
|
||||||
// we should somehow find out if this is constant or not
|
// we should somehow find out if this is constant or not
|
||||||
Expression::ReadLocalVariable { .. } => false,
|
Expression::ReadLocalVariable { .. } => false,
|
||||||
|
@ -1210,6 +1210,7 @@ pub struct BindingAnalysis {
|
||||||
pub enum Path {
|
pub enum Path {
|
||||||
Elements(Vec<PathElement>),
|
Elements(Vec<PathElement>),
|
||||||
Events(Vec<Expression>, Vec<Expression>),
|
Events(Vec<Expression>, Vec<Expression>),
|
||||||
|
Commands(Box<Expression>), // expr must evaluate to string
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -1340,6 +1340,9 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
|
||||||
};
|
};
|
||||||
quote!(sixtyfps::re_exports::PathData::Events(sixtyfps::re_exports::SharedVector::<_>::from_slice(&#events), sixtyfps::re_exports::SharedVector::<_>::from_slice(&#points)))
|
quote!(sixtyfps::re_exports::PathData::Events(sixtyfps::re_exports::SharedVector::<_>::from_slice(&#events), sixtyfps::re_exports::SharedVector::<_>::from_slice(&#points)))
|
||||||
}
|
}
|
||||||
|
(Type::String, Type::PathData) => {
|
||||||
|
quote!(sixtyfps::re_exports::PathData::Commands(#f))
|
||||||
|
}
|
||||||
_ => f,
|
_ => f,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -962,6 +962,10 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
|
||||||
to: Type::PathData,
|
to: Type::PathData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
crate::expression_tree::Path::Commands(commands) => llr_Expression::Cast {
|
||||||
|
from: lower_expression(&commands, &ctx).into(),
|
||||||
|
to: Type::PathData,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub fn compile_paths(
|
||||||
|
|
||||||
let mut elem = elem_.borrow_mut();
|
let mut elem = elem_.borrow_mut();
|
||||||
|
|
||||||
let path_data = if let Some(commands_expr) =
|
let path_data_binding = if let Some(commands_expr) =
|
||||||
elem.bindings.remove("commands").map(RefCell::into_inner)
|
elem.bindings.remove("commands").map(RefCell::into_inner)
|
||||||
{
|
{
|
||||||
if let Some(path_child) = elem.children.iter().find(|child| {
|
if let Some(path_child) = elem.children.iter().find(|child| {
|
||||||
|
@ -60,100 +60,21 @@ pub fn compile_paths(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let commands = match &commands_expr.expression {
|
match &commands_expr.expression {
|
||||||
Expression::StringLiteral(commands) => commands,
|
Expression::StringLiteral(commands) => {
|
||||||
|
if let Some(binding) = compile_path_from_string_literal(commands) {
|
||||||
|
binding
|
||||||
|
} else {
|
||||||
|
diag.push_error("Error parsing SVG commands".into(), &commands_expr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expr @ _ if expr.ty() == Type::String => Expression::PathData(
|
||||||
|
crate::expression_tree::Path::Commands(Box::new(commands_expr.expression)),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
_ => {
|
_ => {
|
||||||
diag.push_error(
|
diag.push_error("The commands property only accepts strings".into(), &*elem);
|
||||||
"The commands property only accepts string literals".into(),
|
|
||||||
&*elem,
|
|
||||||
);
|
|
||||||
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) => {
|
|
||||||
let event_enum = crate::typeregister::PATH_EVENT_ENUM.with(|e| e.clone());
|
|
||||||
let point_type = Type::Struct {
|
|
||||||
fields: IntoIterator::into_iter([
|
|
||||||
("x".to_owned(), Type::Float32),
|
|
||||||
("y".to_owned(), Type::Float32),
|
|
||||||
])
|
|
||||||
.collect(),
|
|
||||||
name: Some("Point".into()),
|
|
||||||
node: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut points = Vec::new();
|
|
||||||
let events = path
|
|
||||||
.into_iter()
|
|
||||||
.map(|event| {
|
|
||||||
Expression::EnumerationValue(match event {
|
|
||||||
lyon_path::Event::Begin { at } => {
|
|
||||||
points.push(at);
|
|
||||||
event_enum.clone().try_value_from_string("begin").unwrap()
|
|
||||||
}
|
|
||||||
lyon_path::Event::Line { from, to } => {
|
|
||||||
points.push(from);
|
|
||||||
points.push(to);
|
|
||||||
|
|
||||||
event_enum.clone().try_value_from_string("line").unwrap()
|
|
||||||
}
|
|
||||||
lyon_path::Event::Quadratic { from, ctrl, to } => {
|
|
||||||
points.push(from);
|
|
||||||
points.push(ctrl);
|
|
||||||
points.push(to);
|
|
||||||
|
|
||||||
event_enum.clone().try_value_from_string("quadratic").unwrap()
|
|
||||||
}
|
|
||||||
lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => {
|
|
||||||
points.push(from);
|
|
||||||
points.push(ctrl1);
|
|
||||||
points.push(ctrl2);
|
|
||||||
points.push(to);
|
|
||||||
event_enum.clone().try_value_from_string("cubic").unwrap()
|
|
||||||
}
|
|
||||||
lyon_path::Event::End { first: _, last: _, close } => {
|
|
||||||
if close {
|
|
||||||
event_enum
|
|
||||||
.clone()
|
|
||||||
.try_value_from_string("end_closed")
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
event_enum
|
|
||||||
.clone()
|
|
||||||
.try_value_from_string("end_open")
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let points = points
|
|
||||||
.into_iter()
|
|
||||||
.map(|point| Expression::Struct {
|
|
||||||
ty: point_type.clone(),
|
|
||||||
values: IntoIterator::into_iter([
|
|
||||||
(
|
|
||||||
"x".to_owned(),
|
|
||||||
Expression::NumberLiteral(point.x as _, Unit::None),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"y".to_owned(),
|
|
||||||
Expression::NumberLiteral(point.y as _, Unit::None),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Path::Events(events, points)
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
diag.push_error("Error parsing SVG commands".into(), &commands_expr);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,10 +110,79 @@ pub fn compile_paths(
|
||||||
elem.children.push(child);
|
elem.children.push(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::expression_tree::Path::Elements(path_data)
|
Expression::PathData(crate::expression_tree::Path::Elements(path_data)).into()
|
||||||
};
|
};
|
||||||
|
|
||||||
elem.bindings
|
elem.bindings.insert("elements".into(), RefCell::new(path_data_binding));
|
||||||
.insert("elements".into(), RefCell::new(Expression::PathData(path_data).into()));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_path_from_string_literal(commands: &str) -> Option<BindingExpression> {
|
||||||
|
let path_builder = lyon_path::Path::builder().with_svg();
|
||||||
|
lyon_svg::path_utils::build_path(path_builder, commands).ok().map(|path| {
|
||||||
|
let event_enum = crate::typeregister::PATH_EVENT_ENUM.with(|e| e.clone());
|
||||||
|
let point_type = Type::Struct {
|
||||||
|
fields: IntoIterator::into_iter([
|
||||||
|
("x".to_owned(), Type::Float32),
|
||||||
|
("y".to_owned(), Type::Float32),
|
||||||
|
])
|
||||||
|
.collect(),
|
||||||
|
name: Some("Point".into()),
|
||||||
|
node: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut points = Vec::new();
|
||||||
|
let events = path
|
||||||
|
.into_iter()
|
||||||
|
.map(|event| {
|
||||||
|
Expression::EnumerationValue(match event {
|
||||||
|
lyon_path::Event::Begin { at } => {
|
||||||
|
points.push(at);
|
||||||
|
event_enum.clone().try_value_from_string("begin").unwrap()
|
||||||
|
}
|
||||||
|
lyon_path::Event::Line { from, to } => {
|
||||||
|
points.push(from);
|
||||||
|
points.push(to);
|
||||||
|
|
||||||
|
event_enum.clone().try_value_from_string("line").unwrap()
|
||||||
|
}
|
||||||
|
lyon_path::Event::Quadratic { from, ctrl, to } => {
|
||||||
|
points.push(from);
|
||||||
|
points.push(ctrl);
|
||||||
|
points.push(to);
|
||||||
|
|
||||||
|
event_enum.clone().try_value_from_string("quadratic").unwrap()
|
||||||
|
}
|
||||||
|
lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => {
|
||||||
|
points.push(from);
|
||||||
|
points.push(ctrl1);
|
||||||
|
points.push(ctrl2);
|
||||||
|
points.push(to);
|
||||||
|
event_enum.clone().try_value_from_string("cubic").unwrap()
|
||||||
|
}
|
||||||
|
lyon_path::Event::End { first: _, last: _, close } => {
|
||||||
|
if close {
|
||||||
|
event_enum.clone().try_value_from_string("end_closed").unwrap()
|
||||||
|
} else {
|
||||||
|
event_enum.clone().try_value_from_string("end_open").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let points = points
|
||||||
|
.into_iter()
|
||||||
|
.map(|point| Expression::Struct {
|
||||||
|
ty: point_type.clone(),
|
||||||
|
values: IntoIterator::into_iter([
|
||||||
|
("x".to_owned(), Expression::NumberLiteral(point.x as _, Unit::None)),
|
||||||
|
("y".to_owned(), Expression::NumberLiteral(point.y as _, Unit::None)),
|
||||||
|
])
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Expression::PathData(Path::Events(events, points)).into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ TestCase := Rectangle {
|
||||||
property<string> cmds: "M 350 300 L 550 300 ";
|
property<string> cmds: "M 350 300 L 550 300 ";
|
||||||
|
|
||||||
Path {
|
Path {
|
||||||
// ^error{The commands property only accepts string literals}
|
|
||||||
commands: cmds;
|
commands: cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ rtti = []
|
||||||
# Expose C ABI
|
# Expose C ABI
|
||||||
ffi = []
|
ffi = []
|
||||||
# Use the standard library
|
# Use the standard library
|
||||||
std = ["euclid/std", "once_cell/std", "scoped-tls-hkt", "lyon_path", "lyon_algorithms", "lyon_geom", "instant"]
|
std = ["euclid/std", "once_cell/std", "scoped-tls-hkt", "lyon_path", "lyon_algorithms", "lyon_geom", "lyon_svg", "instant"]
|
||||||
|
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ sixtyfps-corelib-macros = { version = "=0.2.0", path = "../corelib_macros" }
|
||||||
lyon_path = { version = "0.17.3" , optional = true }
|
lyon_path = { version = "0.17.3" , optional = true }
|
||||||
lyon_algorithms = { version = "0.17.1" , optional = true }
|
lyon_algorithms = { version = "0.17.1" , optional = true }
|
||||||
lyon_geom = { version = "0.17.0", optional = true }
|
lyon_geom = { version = "0.17.0", optional = true }
|
||||||
|
lyon_svg = { version = "0.17.2", optional = true }
|
||||||
euclid = { version = "0.22.1", default-features = false }
|
euclid = { version = "0.22.1", default-features = false }
|
||||||
once_cell = { version = "1.5", default-features = false }
|
once_cell = { version = "1.5", default-features = false }
|
||||||
instant = { version = "0.1", features = [ "now" ], optional = true }
|
instant = { version = "0.1", features = [ "now" ], optional = true }
|
||||||
|
|
|
@ -292,6 +292,8 @@ pub enum PathData {
|
||||||
/// The Events variant describes the path as a series of low-level events and
|
/// The Events variant describes the path as a series of low-level events and
|
||||||
/// associated coordinates.
|
/// associated coordinates.
|
||||||
Events(crate::SharedVector<PathEvent>, crate::SharedVector<Point>),
|
Events(crate::SharedVector<PathEvent>, crate::SharedVector<Point>),
|
||||||
|
/// The Commands variant describes the path as a series of SVG encoded path commands.
|
||||||
|
Commands(crate::SharedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PathData {
|
impl Default for PathData {
|
||||||
|
@ -312,6 +314,13 @@ impl PathData {
|
||||||
PathData::Events(events, coordinates) => {
|
PathData::Events(events, coordinates) => {
|
||||||
LyonPathIteratorVariant::FromEvents(events, coordinates)
|
LyonPathIteratorVariant::FromEvents(events, coordinates)
|
||||||
}
|
}
|
||||||
|
PathData::Commands(commands) => {
|
||||||
|
let path_builder = lyon_path::Path::builder().with_svg();
|
||||||
|
LyonPathIteratorVariant::FromPath(
|
||||||
|
lyon_svg::path_utils::build_path(path_builder, &commands)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
transform: Default::default(),
|
transform: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1101,6 +1101,13 @@ pub fn convert_path(path: &ExprPath, local_context: &mut EvalLocalContext) -> Pa
|
||||||
ExprPath::Events(events, points) => {
|
ExprPath::Events(events, points) => {
|
||||||
convert_from_lyon_path(events.iter(), points.iter(), local_context)
|
convert_from_lyon_path(events.iter(), points.iter(), local_context)
|
||||||
}
|
}
|
||||||
|
ExprPath::Commands(commands) => {
|
||||||
|
if let Value::String(commands) = eval_expression(commands, local_context) {
|
||||||
|
PathData::Commands(commands)
|
||||||
|
} else {
|
||||||
|
panic!("binding to path commands does not evalutate to string");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,10 +156,17 @@ Hello := Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
Path {
|
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";
|
commands: ta.pressed ? root.arrow_down_commands : root.funky-shape-commands;
|
||||||
x: 100phx;
|
x: 100phx;
|
||||||
y: 500phx;
|
y: 500phx;
|
||||||
stroke: black;
|
stroke: black;
|
||||||
stroke_width: 2px;
|
stroke_width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ta := TouchArea {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
property <string> arrow_down_commands: "M21.8,311.1l84.2-82.1c15.7-15.2,41-15.2,56.7,0l341.1,304.1l333.7-297.5c15.5-15.2,41-15.2,56.6,0l84.3,82.1c15.6,15.2,15.6,40,0,55.2L531.7,771c-15.7,15.3-41,15.3-56.7,0l-6.9-6.7L21.8,366.3C6.1,351,6.1,326.3,21.8,311.1z";
|
||||||
|
property <string> funky_shape_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";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue