mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-03 07:04:34 +00:00
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:
parent
09e685ef51
commit
6d12b276a9
7 changed files with 174 additions and 43 deletions
|
@ -27,6 +27,7 @@ All notable changes to this project are documented in this file.
|
||||||
send a mouse or touch event to a window.
|
send a mouse or touch event to a window.
|
||||||
- Added `animation-tick()`
|
- Added `animation-tick()`
|
||||||
- `SharedString` implements `std::fmt::Write` and added `slint::format!`
|
- `SharedString` implements `std::fmt::Write` and added `slint::format!`
|
||||||
|
- `Image` can now be rotated with the `rotation-*` properties
|
||||||
|
|
||||||
## [0.2.5] - 2022-07-06
|
## [0.2.5] - 2022-07-06
|
||||||
|
|
||||||
|
|
|
@ -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
|
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.
|
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
|
### Example
|
||||||
|
|
||||||
```slint
|
```slint
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod binding_analysis;
|
||||||
mod check_aliases;
|
mod check_aliases;
|
||||||
mod check_expressions;
|
mod check_expressions;
|
||||||
mod check_public_api;
|
mod check_public_api;
|
||||||
|
mod check_rotation;
|
||||||
mod clip;
|
mod clip;
|
||||||
mod collect_custom_fonts;
|
mod collect_custom_fonts;
|
||||||
mod collect_globals;
|
mod collect_globals;
|
||||||
|
@ -42,9 +43,11 @@ mod unique_id;
|
||||||
mod visible;
|
mod visible;
|
||||||
mod z_order;
|
mod z_order;
|
||||||
|
|
||||||
use std::{collections::HashSet, rc::Rc};
|
use crate::expression_tree::Expression;
|
||||||
|
|
||||||
use crate::langtype::Type;
|
use crate::langtype::Type;
|
||||||
|
use crate::namedreference::NamedReference;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub async fn run_passes(
|
pub async fn run_passes(
|
||||||
doc: &crate::object_tree::Document,
|
doc: &crate::object_tree::Document,
|
||||||
|
@ -75,6 +78,7 @@ pub async fn run_passes(
|
||||||
infer_aliases_types::resolve_aliases(doc, diag);
|
infer_aliases_types::resolve_aliases(doc, diag);
|
||||||
resolving::resolve_expressions(doc, type_loader, diag);
|
resolving::resolve_expressions(doc, type_loader, diag);
|
||||||
check_expressions::check_expressions(doc, diag);
|
check_expressions::check_expressions(doc, diag);
|
||||||
|
check_rotation::check_rotation(doc, diag);
|
||||||
unique_id::check_unique_id(doc, diag);
|
unique_id::check_unique_id(doc, diag);
|
||||||
check_public_api::check_public_api(doc, diag);
|
check_public_api::check_public_api(doc, diag);
|
||||||
|
|
||||||
|
@ -128,6 +132,7 @@ pub async fn run_passes(
|
||||||
component,
|
component,
|
||||||
"opacity",
|
"opacity",
|
||||||
core::iter::empty(),
|
core::iter::empty(),
|
||||||
|
None,
|
||||||
"Opacity",
|
"Opacity",
|
||||||
&global_type_registry.borrow(),
|
&global_type_registry.borrow(),
|
||||||
diag,
|
diag,
|
||||||
|
@ -136,6 +141,7 @@ pub async fn run_passes(
|
||||||
component,
|
component,
|
||||||
"cache-rendering-hint",
|
"cache-rendering-hint",
|
||||||
core::iter::empty(),
|
core::iter::empty(),
|
||||||
|
None,
|
||||||
"Layer",
|
"Layer",
|
||||||
&global_type_registry.borrow(),
|
&global_type_registry.borrow(),
|
||||||
diag,
|
diag,
|
||||||
|
@ -147,6 +153,20 @@ pub async fn run_passes(
|
||||||
crate::typeregister::RESERVED_ROTATION_PROPERTIES[1..]
|
crate::typeregister::RESERVED_ROTATION_PROPERTIES[1..]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(prop_name, _)| *prop_name),
|
.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",
|
"Rotate",
|
||||||
&global_type_registry.borrow(),
|
&global_type_registry.borrow(),
|
||||||
diag,
|
diag,
|
||||||
|
@ -244,5 +264,6 @@ pub fn run_import_passes(
|
||||||
infer_aliases_types::resolve_aliases(doc, diag);
|
infer_aliases_types::resolve_aliases(doc, diag);
|
||||||
resolving::resolve_expressions(doc, type_loader, diag);
|
resolving::resolve_expressions(doc, type_loader, diag);
|
||||||
check_expressions::check_expressions(doc, diag);
|
check_expressions::check_expressions(doc, diag);
|
||||||
|
check_rotation::check_rotation(doc, diag);
|
||||||
unique_id::check_unique_id(doc, diag);
|
unique_id::check_unique_id(doc, diag);
|
||||||
}
|
}
|
||||||
|
|
51
internal/compiler/passes/check_rotation.rs
Normal file
51
internal/compiler/passes/check_rotation.rs
Normal 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))
|
||||||
|
}
|
|
@ -8,20 +8,20 @@ use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diagnostics::BuildDiagnostics;
|
use crate::diagnostics::BuildDiagnostics;
|
||||||
use crate::expression_tree::{BindingExpression, NamedReference};
|
use crate::expression_tree::{BindingExpression, Expression, NamedReference};
|
||||||
use crate::langtype::Type;
|
use crate::langtype::Type;
|
||||||
use crate::object_tree::{self, Component, Element, ElementRc};
|
use crate::object_tree::{self, Component, Element, ElementRc};
|
||||||
use crate::typeregister::TypeRegister;
|
use crate::typeregister::TypeRegister;
|
||||||
|
|
||||||
// If any element in `component` declares a binding to the first property in `properties`, then a new
|
/// 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 all
|
/// element of type `element_name` is created, injected as a parent to the element and bindings
|
||||||
// remaining properties in `properties` are mapped. This way for example ["rotation-angle", "rotation-origin-x", "rotation-origin-y"]
|
/// to property_name and all properties in extra_properties are mapped.
|
||||||
// creates a `Rotate` element when `rotation-angle` is used and any optional `rotation-origin-*` bindings are redirected to the
|
/// Default balue for the property extra_properties is queried witht the `default_value_for_extra_properties`
|
||||||
// `Rotate` element.
|
|
||||||
pub(crate) fn lower_property_to_element(
|
pub(crate) fn lower_property_to_element(
|
||||||
component: &Rc<Component>,
|
component: &Rc<Component>,
|
||||||
property_name: &'static str,
|
property_name: &'static str,
|
||||||
extra_properties: impl Iterator<Item = &'static str> + Clone,
|
extra_properties: impl Iterator<Item = &'static str> + Clone,
|
||||||
|
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
|
||||||
element_name: &str,
|
element_name: &str,
|
||||||
type_register: &TypeRegister,
|
type_register: &TypeRegister,
|
||||||
diag: &mut BuildDiagnostics,
|
diag: &mut BuildDiagnostics,
|
||||||
|
@ -67,6 +67,7 @@ pub(crate) fn lower_property_to_element(
|
||||||
&root_elem,
|
&root_elem,
|
||||||
property_name,
|
property_name,
|
||||||
extra_properties.clone(),
|
extra_properties.clone(),
|
||||||
|
default_value_for_extra_properties,
|
||||||
element_name,
|
element_name,
|
||||||
type_register,
|
type_register,
|
||||||
),
|
),
|
||||||
|
@ -77,6 +78,7 @@ pub(crate) fn lower_property_to_element(
|
||||||
&child,
|
&child,
|
||||||
property_name,
|
property_name,
|
||||||
extra_properties.clone(),
|
extra_properties.clone(),
|
||||||
|
default_value_for_extra_properties,
|
||||||
element_name,
|
element_name,
|
||||||
type_register,
|
type_register,
|
||||||
);
|
);
|
||||||
|
@ -94,6 +96,7 @@ fn create_property_element(
|
||||||
child: &ElementRc,
|
child: &ElementRc,
|
||||||
property_name: &'static str,
|
property_name: &'static str,
|
||||||
extra_properties: impl Iterator<Item = &'static str>,
|
extra_properties: impl Iterator<Item = &'static str>,
|
||||||
|
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
|
||||||
element_name: &str,
|
element_name: &str,
|
||||||
type_register: &TypeRegister,
|
type_register: &TypeRegister,
|
||||||
) -> ElementRc {
|
) -> ElementRc {
|
||||||
|
@ -107,7 +110,12 @@ fn create_property_element(
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
default_value_for_extra_properties.map(|f| {
|
||||||
|
(
|
||||||
|
property_name.to_string(),
|
||||||
|
BindingExpression::from(f(child, property_name)).into(),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
64
internal/compiler/tests/syntax/basic/rotation.slint
Normal file
64
internal/compiler/tests/syntax/basic/rotation.slint
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,18 +17,15 @@ TestCase := Window {
|
||||||
x: 50px;
|
x: 50px;
|
||||||
y: 50px;
|
y: 50px;
|
||||||
|
|
||||||
Rotate {
|
Image {
|
||||||
angle: 45deg;
|
rotation-angle: 45deg;
|
||||||
origin-x: r.x;
|
x: 50px;
|
||||||
origin-y: r.y;
|
y: 50px;
|
||||||
|
rotation-origin-x: 0;
|
||||||
r := Rectangle {
|
rotation-origin-y: 0;
|
||||||
x: 50px;
|
width: 50px;
|
||||||
y: 50px;
|
height: 50px;
|
||||||
width: 50px;
|
source: @image-url("../../../logo/slint-logo-square-light-128x128.png");
|
||||||
height: 50px;
|
|
||||||
background: green;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -48,29 +45,14 @@ TestCase := Window {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: 200px;
|
x: 200px;
|
||||||
y: 50px;
|
y: 50px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: black;
|
||||||
|
width: i2.width;
|
||||||
|
height: i2.height;
|
||||||
|
|
||||||
Rotate {
|
i2 := Image {
|
||||||
angle: 315deg;
|
rotation-angle: 315deg;
|
||||||
origin-x: r2.x;
|
source: @image-url("../../../logo/slint-logo-square-light-128x128.png");
|
||||||
origin-y: r2.y;
|
|
||||||
|
|
||||||
r2 := Rectangle {
|
|
||||||
x: 50px;
|
|
||||||
y: 50px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background: @linear-gradient(90deg, red, blue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
x: 50px;
|
|
||||||
y: 50px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue