Skip to main content

i_slint_compiler/passes/
compile_paths.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This pass converts the verbose markup used for paths, such as
5//!    Path {
6//!        LineTo { ... } ArcTo { ... }
7//!    }
8//! to a vector of path elements (PathData) that is assigned to the
9//! elements property of the Path element. That way the generators have to deal
10//! with path embedding only as part of the property assignment.
11
12use 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                    // Just Path subclass that had elements declared earlier, since path_data is empty we should retain the
127                    // existing elements
128                    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}