Polish of the rotation feature

- Add a check that this only Applies to Image element without children
 - Default the origin to the center of the Image
 - Add docs and test

cc: #1481
This commit is contained in:
Olivier Goffart 2022-08-30 12:10:42 +02:00 committed by GitHub
parent 09e685ef51
commit 6d12b276a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 174 additions and 43 deletions

View file

@ -6,6 +6,7 @@ mod binding_analysis;
mod check_aliases;
mod check_expressions;
mod check_public_api;
mod check_rotation;
mod clip;
mod collect_custom_fonts;
mod collect_globals;
@ -42,9 +43,11 @@ mod unique_id;
mod visible;
mod z_order;
use std::{collections::HashSet, rc::Rc};
use crate::expression_tree::Expression;
use crate::langtype::Type;
use crate::namedreference::NamedReference;
use std::collections::HashSet;
use std::rc::Rc;
pub async fn run_passes(
doc: &crate::object_tree::Document,
@ -75,6 +78,7 @@ pub async fn run_passes(
infer_aliases_types::resolve_aliases(doc, diag);
resolving::resolve_expressions(doc, type_loader, diag);
check_expressions::check_expressions(doc, diag);
check_rotation::check_rotation(doc, diag);
unique_id::check_unique_id(doc, diag);
check_public_api::check_public_api(doc, diag);
@ -128,6 +132,7 @@ pub async fn run_passes(
component,
"opacity",
core::iter::empty(),
None,
"Opacity",
&global_type_registry.borrow(),
diag,
@ -136,6 +141,7 @@ pub async fn run_passes(
component,
"cache-rendering-hint",
core::iter::empty(),
None,
"Layer",
&global_type_registry.borrow(),
diag,
@ -147,6 +153,20 @@ pub async fn run_passes(
crate::typeregister::RESERVED_ROTATION_PROPERTIES[1..]
.iter()
.map(|(prop_name, _)| *prop_name),
Some(&|e, prop| Expression::BinaryExpression {
lhs: Expression::PropertyReference(NamedReference::new(
e,
match prop {
"rotation-origin-x" => "width",
"rotation-origin-y" => "height",
"rotation-angle" => return Expression::Invalid,
_ => unreachable!(),
},
))
.into(),
op: '/',
rhs: Expression::NumberLiteral(2., Default::default()).into(),
}),
"Rotate",
&global_type_registry.borrow(),
diag,
@ -244,5 +264,6 @@ pub fn run_import_passes(
infer_aliases_types::resolve_aliases(doc, diag);
resolving::resolve_expressions(doc, type_loader, diag);
check_expressions::check_expressions(doc, diag);
check_rotation::check_rotation(doc, diag);
unique_id::check_unique_id(doc, diag);
}

View file

@ -0,0 +1,51 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use crate::diagnostics::BuildDiagnostics;
use crate::diagnostics::Spanned;
use crate::langtype::Type;
use crate::object_tree::Element;
/// Check that the rotation is only on Image
pub fn check_rotation(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) {
for cmp in &doc.inner_components {
crate::object_tree::recurse_elem_including_sub_components(cmp, &(), &mut |elem, _| {
let e = elem.borrow();
if crate::typeregister::RESERVED_ROTATION_PROPERTIES
.iter()
.any(|(property_name, _)| is_property_set(&*e, *&property_name))
{
if matches!(e.native_class(), Some(native) if native.class_name != "ClippedImage") {
let span = e
.bindings
.get("rotation-angle")
.and_then(|e| e.borrow().span.clone())
.unwrap_or_else(|| e.to_source_location());
diag.push_error_with_span(
"rotation properties can only be applied to the Image element".into(),
span,
);
} else if has_any_children(&*e) {
diag.push_error_with_span(
"Elements with rotation properties cannot have children elements".into(),
e.to_source_location(),
);
}
}
});
}
}
/// Returns true if this element or its base have any children.
fn has_any_children(e: &Element) -> bool {
!e.children.is_empty()
|| matches!(&e.base_type, Type::Component(base) if has_any_children(&base.root_element.borrow()))
}
/// Returns true if the property is set.
fn is_property_set(e: &Element, property_name: &str) -> bool {
e.bindings.contains_key(property_name)
|| e.property_analysis.borrow().get(property_name).map_or(false, |a| a.is_set)
|| matches!(&e.base_type, Type::Component(base) if is_property_set(&base.root_element.borrow(), property_name))
}

View file

@ -8,20 +8,20 @@ use std::cell::RefCell;
use std::rc::Rc;
use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::{BindingExpression, NamedReference};
use crate::expression_tree::{BindingExpression, Expression, NamedReference};
use crate::langtype::Type;
use crate::object_tree::{self, Component, Element, ElementRc};
use crate::typeregister::TypeRegister;
// If any element in `component` declares a binding to the first property in `properties`, then a new
// element of type `element_name` is created, injected as a parent to the element and bindings to all
// remaining properties in `properties` are mapped. This way for example ["rotation-angle", "rotation-origin-x", "rotation-origin-y"]
// creates a `Rotate` element when `rotation-angle` is used and any optional `rotation-origin-*` bindings are redirected to the
// `Rotate` element.
/// If any element in `component` declares a binding to `property_name`, then a new
/// element of type `element_name` is created, injected as a parent to the element and bindings
/// to property_name and all properties in extra_properties are mapped.
/// Default balue for the property extra_properties is queried witht the `default_value_for_extra_properties`
pub(crate) fn lower_property_to_element(
component: &Rc<Component>,
property_name: &'static str,
extra_properties: impl Iterator<Item = &'static str> + Clone,
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
element_name: &str,
type_register: &TypeRegister,
diag: &mut BuildDiagnostics,
@ -67,6 +67,7 @@ pub(crate) fn lower_property_to_element(
&root_elem,
property_name,
extra_properties.clone(),
default_value_for_extra_properties,
element_name,
type_register,
),
@ -77,6 +78,7 @@ pub(crate) fn lower_property_to_element(
&child,
property_name,
extra_properties.clone(),
default_value_for_extra_properties,
element_name,
type_register,
);
@ -94,6 +96,7 @@ fn create_property_element(
child: &ElementRc,
property_name: &'static str,
extra_properties: impl Iterator<Item = &'static str>,
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
element_name: &str,
type_register: &TypeRegister,
) -> ElementRc {
@ -107,7 +110,12 @@ fn create_property_element(
.into(),
))
} else {
None
default_value_for_extra_properties.map(|f| {
(
property_name.to_string(),
BindingExpression::from(f(child, property_name)).into(),
)
})
}
})
.collect();

View file

@ -0,0 +1,64 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
Ex1 := Rectangle {
Rectangle {
rotation-origin-x: width / 2;
rotation-angle: 45deg;
// ^error{rotation properties can only be applied to the Image element}
rotation-origin-y: width / 2;
}
Rectangle {
// ^error{rotation properties can only be applied to the Image element}
rotation-origin-x: width / 2;
rotation-origin-y: width / 2;
}
}
RotImg := Image {
rotation-angle: 45deg;
}
JustAnImage := Image {}
ImageWithChild := Image {
Rectangle {}
}
Ex2 := Rectangle {
Image {
// ^error{Elements with rotation properties cannot have children elements}
rotation-angle: 45deg;
Rectangle {}
}
RotImg {
// ^error{Elements with rotation properties cannot have children elements}
Rectangle {}
}
ImageWithChild {
// ^error{Elements with rotation properties cannot have children elements}
rotation-origin-x: 45px;
}
JustAnImage {
// ^error{Elements with rotation properties cannot have children elements}
rotation-angle: 45deg;
Rectangle {}
}
}
Ex3 := Rectangle {
i1 := Image {
// ^error{Elements with rotation properties cannot have children elements}
Rectangle {}
}
i2 := Rectangle {}
// ^error{rotation properties can only be applied to the Image element}
TouchArea {
clicked => {
i1.rotation-angle = 60deg;
i2.rotation-origin-x = 10px;
}
}
}