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