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

@ -27,6 +27,7 @@ All notable changes to this project are documented in this file.
send a mouse or touch event to a window.
- Added `animation-tick()`
- `SharedString` implements `std::fmt::Write` and added `slint::format!`
- `Image` can now be rotated with the `rotation-*` properties
## [0.2.5] - 2022-07-06

View file

@ -174,6 +174,10 @@ An Image can be used to represent an image loaded from an image file.
the sizes provided by the **`source`** image. If the `Image` is **not** in a layout and only **one** of the two sizes are
specified, then the other defaults to the specified value scaled according to the aspect ratio of the **`source`** image.
* **`rotation-angle`** (*angle*), **`rotation-origin-x`** (*length*), **`rotation-origin-y`** (*length*):
Rotate the image by the given angle around the specified origin point. The default origin point is the center of the element.
When these properties are present, the Image cannot have any children elements.
### Example
```slint

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;
}
}
}

View file

@ -17,18 +17,15 @@ TestCase := Window {
x: 50px;
y: 50px;
Rotate {
angle: 45deg;
origin-x: r.x;
origin-y: r.y;
r := Rectangle {
x: 50px;
y: 50px;
width: 50px;
height: 50px;
background: green;
}
Image {
rotation-angle: 45deg;
x: 50px;
y: 50px;
rotation-origin-x: 0;
rotation-origin-y: 0;
width: 50px;
height: 50px;
source: @image-url("../../../logo/slint-logo-square-light-128x128.png");
}
Rectangle {
@ -48,29 +45,14 @@ TestCase := Window {
Rectangle {
x: 200px;
y: 50px;
border-width: 1px;
border-color: black;
width: i2.width;
height: i2.height;
Rotate {
angle: 315deg;
origin-x: r2.x;
origin-y: r2.y;
r2 := Rectangle {
x: 50px;
y: 50px;
width: 50px;
height: 50px;
background: @linear-gradient(90deg, red, blue);
}
i2 := Image {
rotation-angle: 315deg;
source: @image-url("../../../logo/slint-logo-square-light-128x128.png");
}
Rectangle {
x: 50px;
y: 50px;
width: 50px;
height: 50px;
border-width: 1px;
border-color: black;
}
}
}