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