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::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                    // Just Path subclass that had elements declared earlier, since path_data is empty we should retain the
115                    // existing elements
116                    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}