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