mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 22:01:13 +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)
|
||||
{
|
||||
if (a.data.tag != b.data.tag)
|
||||
|
@ -46,6 +51,8 @@ public:
|
|||
return a.data.elements._0 == b.data.elements._0;
|
||||
case cbindgen_private::types::PathData::Tag::Events:
|
||||
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:
|
||||
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
|
||||
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`,
|
||||
`move-to` and `arc` commands:
|
||||
|
||||
|
@ -293,7 +293,7 @@ Example := Path {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -675,6 +675,7 @@ impl Expression {
|
|||
Path::Events(events, coordinates) => {
|
||||
events.iter().chain(coordinates.iter()).for_each(visitor);
|
||||
}
|
||||
Path::Commands(commands) => visitor(commands),
|
||||
},
|
||||
Expression::StoreLocalVariable { value, .. } => visitor(&**value),
|
||||
Expression::ReadLocalVariable { .. } => {}
|
||||
|
@ -767,6 +768,7 @@ impl Expression {
|
|||
Path::Events(events, coordinates) => {
|
||||
events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
|
||||
}
|
||||
Path::Commands(commands) => visitor(commands),
|
||||
},
|
||||
Expression::StoreLocalVariable { value, .. } => visitor(&mut **value),
|
||||
Expression::ReadLocalVariable { .. } => {}
|
||||
|
@ -835,15 +837,13 @@ impl Expression {
|
|||
Expression::UnaryOp { sub, .. } => sub.is_constant(),
|
||||
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
||||
Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
|
||||
Expression::PathData(data) => {
|
||||
if let Path::Elements(elements) = data {
|
||||
elements
|
||||
Expression::PathData(data) => match data {
|
||||
Path::Elements(elements) => elements
|
||||
.iter()
|
||||
.all(|element| element.bindings.values().all(|v| v.borrow().is_constant()))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.all(|element| element.bindings.values().all(|v| v.borrow().is_constant())),
|
||||
Path::Events(_, _) => true,
|
||||
Path::Commands(_) => false,
|
||||
},
|
||||
Expression::StoreLocalVariable { .. } => false,
|
||||
// we should somehow find out if this is constant or not
|
||||
Expression::ReadLocalVariable { .. } => false,
|
||||
|
@ -1210,6 +1210,7 @@ pub struct BindingAnalysis {
|
|||
pub enum Path {
|
||||
Elements(Vec<PathElement>),
|
||||
Events(Vec<Expression>, Vec<Expression>),
|
||||
Commands(Box<Expression>), // expr must evaluate to string
|
||||
}
|
||||
|
||||
#[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)))
|
||||
}
|
||||
(Type::String, Type::PathData) => {
|
||||
quote!(sixtyfps::re_exports::PathData::Commands(#f))
|
||||
}
|
||||
_ => f,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -962,6 +962,10 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
|
|||
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 path_data = if let Some(commands_expr) =
|
||||
let path_data_binding = if let Some(commands_expr) =
|
||||
elem.bindings.remove("commands").map(RefCell::into_inner)
|
||||
{
|
||||
if let Some(path_child) = elem.children.iter().find(|child| {
|
||||
|
@ -60,21 +60,66 @@ pub fn compile_paths(
|
|||
return;
|
||||
}
|
||||
|
||||
let commands = match &commands_expr.expression {
|
||||
Expression::StringLiteral(commands) => commands,
|
||||
_ => {
|
||||
diag.push_error(
|
||||
"The commands property only accepts string literals".into(),
|
||||
&*elem,
|
||||
);
|
||||
match &commands_expr.expression {
|
||||
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("The commands property only accepts strings".into(), &*elem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let new_children = Vec::with_capacity(elem.children.len());
|
||||
let old_children = std::mem::replace(&mut elem.children, new_children);
|
||||
|
||||
let mut path_data = Vec::new();
|
||||
|
||||
for child in old_children {
|
||||
let element_name =
|
||||
&child.borrow().base_type.as_builtin().native_class.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
|
||||
),
|
||||
};
|
||||
|
||||
if child.borrow().repeated.is_some() {
|
||||
diag.push_error(
|
||||
"Path elements are not supported with `for`-`in` syntax, yet (https://github.com/sixtyfpsui/sixtyfps/issues/754)".into(),
|
||||
&*child.borrow(),
|
||||
);
|
||||
} else {
|
||||
let bindings = std::mem::take(&mut child.borrow_mut().bindings);
|
||||
path_data.push(PathElement { element_type, bindings });
|
||||
}
|
||||
} else {
|
||||
elem.children.push(child);
|
||||
}
|
||||
}
|
||||
Expression::PathData(crate::expression_tree::Path::Elements(path_data)).into()
|
||||
};
|
||||
|
||||
elem.bindings.insert("elements".into(), RefCell::new(path_data_binding));
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_path_from_string_literal(commands: &str) -> Option<BindingExpression> {
|
||||
let path_builder = lyon_path::Path::builder().with_svg();
|
||||
let path = lyon_svg::path_utils::build_path(path_builder, commands);
|
||||
match path {
|
||||
Ok(path) => {
|
||||
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([
|
||||
|
@ -117,15 +162,9 @@ pub fn compile_paths(
|
|||
}
|
||||
lyon_path::Event::End { first: _, last: _, close } => {
|
||||
if close {
|
||||
event_enum
|
||||
.clone()
|
||||
.try_value_from_string("end_closed")
|
||||
.unwrap()
|
||||
event_enum.clone().try_value_from_string("end_closed").unwrap()
|
||||
} else {
|
||||
event_enum
|
||||
.clone()
|
||||
.try_value_from_string("end_open")
|
||||
.unwrap()
|
||||
event_enum.clone().try_value_from_string("end_open").unwrap()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -137,62 +176,13 @@ pub fn compile_paths(
|
|||
.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),
|
||||
),
|
||||
("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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let new_children = Vec::with_capacity(elem.children.len());
|
||||
let old_children = std::mem::replace(&mut elem.children, new_children);
|
||||
|
||||
let mut path_data = Vec::new();
|
||||
|
||||
for child in old_children {
|
||||
let element_name =
|
||||
&child.borrow().base_type.as_builtin().native_class.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
|
||||
),
|
||||
};
|
||||
|
||||
if child.borrow().repeated.is_some() {
|
||||
diag.push_error(
|
||||
"Path elements are not supported with `for`-`in` syntax, yet (https://github.com/sixtyfpsui/sixtyfps/issues/754)".into(),
|
||||
&*child.borrow(),
|
||||
);
|
||||
} else {
|
||||
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(), RefCell::new(Expression::PathData(path_data).into()));
|
||||
});
|
||||
Expression::PathData(Path::Events(events, points)).into()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ TestCase := Rectangle {
|
|||
property<string> cmds: "M 350 300 L 550 300 ";
|
||||
|
||||
Path {
|
||||
// ^error{The commands property only accepts string literals}
|
||||
commands: cmds;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ rtti = []
|
|||
# Expose C ABI
|
||||
ffi = []
|
||||
# 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"]
|
||||
|
||||
|
@ -40,6 +40,7 @@ sixtyfps-corelib-macros = { version = "=0.2.0", path = "../corelib_macros" }
|
|||
lyon_path = { version = "0.17.3" , optional = true }
|
||||
lyon_algorithms = { version = "0.17.1" , 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 }
|
||||
once_cell = { version = "1.5", default-features = false }
|
||||
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
|
||||
/// associated coordinates.
|
||||
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 {
|
||||
|
@ -312,6 +314,13 @@ impl PathData {
|
|||
PathData::Events(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(),
|
||||
}
|
||||
|
|
|
@ -1101,6 +1101,13 @@ pub fn convert_path(path: &ExprPath, local_context: &mut EvalLocalContext) -> Pa
|
|||
ExprPath::Events(events, points) => {
|
||||
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 {
|
||||
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;
|
||||
y: 500phx;
|
||||
stroke: black;
|
||||
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