i_slint_compiler/passes/
compile_paths.rs1use crate::EmbedResourcesKind;
13use crate::diagnostics::BuildDiagnostics;
14use crate::expression_tree::*;
15use crate::langtype::{BuiltinPrivateStruct, Struct, Type};
16use crate::object_tree::*;
17use smol_str::SmolStr;
18use std::cell::RefCell;
19use std::rc::Rc;
20
21pub fn compile_paths(
22 component: &Rc<Component>,
23 tr: &crate::typeregister::TypeRegister,
24 _embed_resources: EmbedResourcesKind,
25 diag: &mut BuildDiagnostics,
26) {
27 let path_type = tr.lookup_element("Path").unwrap();
28 let path_type = path_type.as_builtin();
29
30 recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem_, _| {
31 if elem_.borrow().builtin_type().is_none_or(|bt| bt.name != "Path") {
32 return;
33 }
34
35 let element_types = &path_type.additional_accepted_child_types;
36
37 let commands_binding =
38 elem_.borrow_mut().bindings.remove("commands").map(RefCell::into_inner);
39
40 let path_data_binding = if let Some(commands_expr) = commands_binding {
41 if let Some(path_child) = elem_.borrow().children.iter().find(|child| {
42 element_types
43 .contains_key(&child.borrow().base_type.as_builtin().native_class.class_name)
44 }) {
45 diag.push_error(
46 "Path elements cannot be mixed with the use of the SVG commands property"
47 .into(),
48 &*path_child.borrow(),
49 );
50 return;
51 }
52
53 match &commands_expr.expression {
54 Expression::StringLiteral(commands) => {
55 match compile_path_from_string_literal(commands) {
56 Ok(binding) => binding,
57 Err(e) => {
58 diag.push_error(
59 format!("Error parsing SVG commands ({e})"),
60 &commands_expr,
61 );
62 return;
63 }
64 }
65 }
66 expr if expr.ty() == Type::String => {
67 #[cfg(feature = "software-renderer")]
68 if _embed_resources == EmbedResourcesKind::EmbedTextures {
69 diag.push_warning(
70 "Bindings to the Path element's commands are not supported with the software renderer".into(),
71 &*elem_.borrow(),
72 )
73 }
74
75 Expression::PathData(crate::expression_tree::Path::Commands(Box::new(
76 commands_expr.expression,
77 )))
78 .into()
79 }
80 _ => {
81 diag.push_error(
82 "The commands property only accepts strings".into(),
83 &*elem_.borrow(),
84 );
85 return;
86 }
87 }
88 } else {
89 let mut elem = elem_.borrow_mut();
90 let enclosing_component = elem.enclosing_component.upgrade().unwrap();
91 let new_children = Vec::with_capacity(elem.children.len());
92 let old_children = std::mem::replace(&mut elem.children, new_children);
93
94 let mut path_data = Vec::new();
95
96 for child in old_children {
97 let element_name =
98 &child.borrow().base_type.as_builtin().native_class.class_name.clone();
99
100 if let Some(element_type) = element_types.get(element_name).cloned() {
101 if child.borrow().repeated.is_some() {
102 diag.push_error(
103 "Path elements are not supported with `for`-`in` syntax, yet (https://github.com/slint-ui/slint/issues/754)".into(),
104 &*child.borrow(),
105 );
106 } else {
107 let mut bindings = std::collections::BTreeMap::new();
108 {
109 let mut child = child.borrow_mut();
110 for k in element_type.properties.keys() {
111 if let Some(binding) = child.bindings.remove(k) {
112 bindings.insert(k.clone(), binding);
113 }
114 }
115 }
116 path_data.push(PathElement { element_type, bindings });
117 enclosing_component.optimized_elements.borrow_mut().push(child);
118 }
119 } else {
120 elem.children.push(child);
121 }
122 }
123
124 if elem.is_binding_set("elements", false) {
125 if path_data.is_empty() {
126 return;
129 } else {
130 diag.push_error(
131 "The Path was already populated in the base type and it can't be re-populated again"
132 .into(),
133 &*elem,
134 );
135 return;
136 }
137 }
138
139 Expression::PathData(crate::expression_tree::Path::Elements(path_data)).into()
140 };
141
142 elem_
143 .borrow_mut()
144 .bindings
145 .insert(SmolStr::new_static("elements"), RefCell::new(path_data_binding));
146 });
147}
148
149fn compile_path_from_string_literal(
150 commands: &str,
151) -> Result<BindingExpression, lyon_extra::parser::ParseError> {
152 let mut builder = lyon_path::Path::builder();
153 let mut parser = lyon_extra::parser::PathParser::new();
154 parser.parse(
155 &lyon_extra::parser::ParserOptions::DEFAULT,
156 &mut lyon_extra::parser::Source::new(commands.chars()),
157 &mut builder,
158 )?;
159 let path = builder.build();
160
161 let event_enum = crate::typeregister::BUILTIN.with(|e| e.enums.PathEvent.clone());
162 let point_type = Rc::new(Struct {
163 fields: IntoIterator::into_iter([
164 (SmolStr::new_static("x"), Type::Float32),
165 (SmolStr::new_static("y"), Type::Float32),
166 ])
167 .collect(),
168 name: BuiltinPrivateStruct::Point.into(),
169 });
170
171 let mut points = Vec::new();
172 let events = path
173 .into_iter()
174 .map(|event| {
175 Expression::EnumerationValue(match event {
176 lyon_path::Event::Begin { at } => {
177 points.push(at);
178 event_enum.clone().try_value_from_string("begin").unwrap()
179 }
180 lyon_path::Event::Line { from, to } => {
181 points.push(from);
182 points.push(to);
183
184 event_enum.clone().try_value_from_string("line").unwrap()
185 }
186 lyon_path::Event::Quadratic { from, ctrl, to } => {
187 points.push(from);
188 points.push(ctrl);
189 points.push(to);
190
191 event_enum.clone().try_value_from_string("quadratic").unwrap()
192 }
193 lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => {
194 points.push(from);
195 points.push(ctrl1);
196 points.push(ctrl2);
197 points.push(to);
198 event_enum.clone().try_value_from_string("cubic").unwrap()
199 }
200 lyon_path::Event::End { first: _, last: _, close } => {
201 if close {
202 event_enum.clone().try_value_from_string("end-closed").unwrap()
203 } else {
204 event_enum.clone().try_value_from_string("end-open").unwrap()
205 }
206 }
207 })
208 })
209 .collect();
210
211 let points = points
212 .into_iter()
213 .map(|point| Expression::Struct {
214 ty: point_type.clone(),
215 values: IntoIterator::into_iter([
216 (SmolStr::new_static("x"), Expression::NumberLiteral(point.x as _, Unit::None)),
217 (SmolStr::new_static("y"), Expression::NumberLiteral(point.y as _, Unit::None)),
218 ])
219 .collect(),
220 })
221 .collect();
222
223 Ok(Expression::PathData(Path::Events(events, points)).into())
224}