slint/internal/compiler/passes/compile_paths.rs
Amirhossein Akhlaghpour 3a4f3c61d5
Add @rust-attr on scturct (#2785)
Fixes: #2660
2023-06-05 16:29:55 +02:00

198 lines
7.5 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! This pass converts the verbose markup used for paths, such as
//! Path {
//! LineTo { ... } ArcTo { ... }
//! }
//! to a vector of path elements (PathData) that is assigned to the
//! elements property of the Path element. That way the generators have to deal
//! with path embedding only as part of the property assignment.
use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::*;
use crate::langtype::ElementType;
use crate::langtype::Type;
use crate::object_tree::*;
use std::cell::RefCell;
use std::rc::Rc;
pub fn compile_paths(
component: &Rc<Component>,
tr: &crate::typeregister::TypeRegister,
diag: &mut BuildDiagnostics,
) {
let path_type = tr.lookup_element("Path").unwrap();
let path_type = path_type.as_builtin();
recurse_elem(&component.root_element, &(), &mut |elem_, _| {
let accepted_type = match &elem_.borrow().base_type {
ElementType::Builtin(be)
if be.native_class.class_name == path_type.native_class.class_name =>
{
path_type
}
_ => return,
};
let element_types = &accepted_type.additional_accepted_child_types;
let commands_binding =
elem_.borrow_mut().bindings.remove("commands").map(RefCell::into_inner);
let path_data_binding = if let Some(commands_expr) = commands_binding {
if let Some(path_child) = elem_.borrow().children.iter().find(|child| {
element_types
.contains_key(&child.borrow().base_type.as_builtin().native_class.class_name)
}) {
diag.push_error(
"Path elements cannot be mixed with the use of the SVG commands property"
.into(),
&*path_child.borrow(),
);
return;
}
match &commands_expr.expression {
Expression::StringLiteral(commands) => {
match compile_path_from_string_literal(commands) {
Ok(binding) => binding,
Err(e) => {
diag.push_error(
format!("Error parsing SVG commands ({e:?})"),
&commands_expr,
);
return;
}
}
}
expr if expr.ty() == Type::String => Expression::PathData(
crate::expression_tree::Path::Commands(Box::new(commands_expr.expression)),
)
.into(),
_ => {
diag.push_error(
"The commands property only accepts strings".into(),
&*elem_.borrow(),
);
return;
}
}
} else {
let mut elem = elem_.borrow_mut();
let new_children = Vec::with_capacity(elem.children.len());
let old_children = std::mem::replace(&mut elem.children, new_children);
let mut path_data = Vec::new();
for child in old_children {
let element_name =
&child.borrow().base_type.as_builtin().native_class.class_name.clone();
if let Some(path_element) = element_types.get(element_name) {
let element_type = match path_element {
ElementType::Builtin(b) => b.clone(),
_ => panic!(
"Incorrect type registry -- expected built-in type for path element {}",
element_name
),
};
if child.borrow().repeated.is_some() {
diag.push_error(
"Path elements are not supported with `for`-`in` syntax, yet (https://github.com/slint-ui/slint/issues/754)".into(),
&*child.borrow(),
);
} else {
let bindings = std::mem::take(&mut child.borrow_mut().bindings);
path_data.push(PathElement { element_type, bindings });
}
} else {
elem.children.push(child);
}
}
Expression::PathData(crate::expression_tree::Path::Elements(path_data)).into()
};
elem_.borrow_mut().bindings.insert("elements".into(), RefCell::new(path_data_binding));
});
}
fn compile_path_from_string_literal(
commands: &str,
) -> Result<BindingExpression, lyon_extra::parser::ParseError> {
let mut builder = lyon_path::Path::builder();
let mut parser = lyon_extra::parser::PathParser::new();
parser.parse(
&lyon_extra::parser::ParserOptions::DEFAULT,
&mut lyon_extra::parser::Source::new(commands.chars()),
&mut builder,
)?;
let path = builder.build();
let event_enum = crate::typeregister::BUILTIN_ENUMS.with(|e| e.PathEvent.clone());
let point_type = Type::Struct {
fields: IntoIterator::into_iter([
("x".to_owned(), Type::Float32),
("y".to_owned(), Type::Float32),
])
.collect(),
name: Some("Point".into()),
node: None,
rust_attributes: None,
};
let mut points = Vec::new();
let events = path
.into_iter()
.map(|event| {
Expression::EnumerationValue(match event {
lyon_path::Event::Begin { at } => {
points.push(at);
event_enum.clone().try_value_from_string("begin").unwrap()
}
lyon_path::Event::Line { from, to } => {
points.push(from);
points.push(to);
event_enum.clone().try_value_from_string("line").unwrap()
}
lyon_path::Event::Quadratic { from, ctrl, to } => {
points.push(from);
points.push(ctrl);
points.push(to);
event_enum.clone().try_value_from_string("quadratic").unwrap()
}
lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => {
points.push(from);
points.push(ctrl1);
points.push(ctrl2);
points.push(to);
event_enum.clone().try_value_from_string("cubic").unwrap()
}
lyon_path::Event::End { first: _, last: _, close } => {
if close {
event_enum.clone().try_value_from_string("end-closed").unwrap()
} else {
event_enum.clone().try_value_from_string("end-open").unwrap()
}
}
})
})
.collect();
let points = points
.into_iter()
.map(|point| Expression::Struct {
ty: point_type.clone(),
values: IntoIterator::into_iter([
("x".to_owned(), Expression::NumberLiteral(point.x as _, Unit::None)),
("y".to_owned(), Expression::NumberLiteral(point.y as _, Unit::None)),
])
.collect(),
})
.collect();
Ok(Expression::PathData(Path::Events(events, points)).into())
}