Skip to content

Commit 241964d

Browse files
authored
Change the Rotate item to a Transform that supports scaling (#9387)
* Add scale method to backends * Rename Rotate to Transform * Add scaling to Transform * Fix tests * Insert Transform if any of scale-x, scale-y or rotation-angle are set * Add scaling to child_transform and handle events as a result * Cargo fmt * Femtovg clipping * Fix femotovg clipping * Add newline to actual_render.scale * Cargo fmt tools/lsp changes * Modify docs * Change type to a float instead of percent and fix defaults * Add note about software renderer * Add basic event scaling test
1 parent 8266f7b commit 241964d

File tree

20 files changed

+245
-114
lines changed

20 files changed

+245
-114
lines changed

api/cpp/cbindgen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ fn gen_corelib(
308308
"TextInput",
309309
"Clip",
310310
"BoxShadow",
311-
"Rotate",
311+
"Transform",
312312
"Opacity",
313313
"Layer",
314314
"ContextMenu",

api/rs/slint/private_unstable_api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ pub mod re_exports {
204204
visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak,
205205
TraversalOrder, VisitChildrenResult,
206206
};
207-
pub use i_slint_core::items::*;
207+
pub use i_slint_core::items::{Transform, *};
208208
pub use i_slint_core::layout::*;
209209
pub use i_slint_core::lengths::{
210210
logical_position_to_api, LogicalLength, LogicalPoint, LogicalRect,

docs/astro/src/content/docs/guide/backends-and-renderers/backends_and_renderers.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The Qt renderer comes with the <Link type="QtBackend" label="Qt backend" /> and
8080
- Suitable for Microcontrollers.
8181
- Some features haven't been implemented yet:
8282
* No support for `Path`.
83-
* No image rotation or smooth scaling.
83+
* No support for rotations or scaling.
8484
* No support for `drop-shadow-*` properties.
8585
* No support for `border-radius` in combination with `clip: true`.
8686
* No text stroking/outlining.

docs/astro/src/content/docs/reference/common.mdx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,42 @@ The width and height of the element. When set, this overrides the default size.
4141
</SlintProperty>
4242

4343

44+
### Transforms
45+
46+
Transforms allow for rotating and scaling items around a specified origin point. The default origin point is the center of the element.
47+
48+
Transforms are not available for the software renderer.
49+
50+
```slint {5-7}
51+
Image {
52+
x: 0;
53+
y: 0;
54+
source: @image-url("images/slint-logo.svg");
55+
rotation-angle: 45deg;
56+
rotation-origin-x: 0;
57+
rotation-origin-y: 0;
58+
}
59+
```
60+
61+
#### rotation-angle
62+
<SlintProperty propName="rotation-angle" typeName="angle" defaultValue='0deg'/>
63+
#### rotation-origin-x
64+
<SlintProperty propName="rotation-origin-x" typeName="length" defaultValue='self.width / 2'>
65+
The x component of the origin to rotate and scale around.
66+
67+
In the future this will be deprecated in favour of a transform-origin property.
68+
</SlintProperty>
69+
#### rotation-origin-y
70+
<SlintProperty propName="rotation-origin-y" typeName="length" defaultValue='self.height / 2'>
71+
The y component of the origin to rotate and scale around.
72+
73+
In the future this will be deprecated in favour of a transform-origin property.
74+
</SlintProperty>
75+
#### scale-x
76+
<SlintProperty propName="scale-x" typeName="percent" defaultValue='100%'/>
77+
#### scale-y
78+
<SlintProperty propName="scale-y" typeName="percent" defaultValue='100%'/>
79+
4480
### opacity
4581
<CodeSnippetMD imagePath="/src/assets/generated/rectangle-opacity.png" scale="3" imageWidth="100" imageHeight="310" imageAlt='rectangle opacity'>
4682
```slint playground

docs/astro/src/content/docs/reference/elements/image.mdx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -172,29 +172,6 @@ Image {
172172

173173
</SlintProperty>
174174

175-
## Rotation
176-
177-
Rotates the text by the given angle around the specified origin point. The default origin point is the center of the element.
178-
When these properties are set, the `Image` can't have children.
179-
180-
```slint {5-7}
181-
Image {
182-
x: 0;
183-
y: 0;
184-
source: @image-url("images/slint-logo.svg");
185-
rotation-angle: 45deg;
186-
rotation-origin-x: 0;
187-
rotation-origin-y: 0;
188-
}
189-
```
190-
191-
### rotation-angle
192-
<SlintProperty propName="rotation-angle" typeName="angle" defaultValue='0deg'/>
193-
### rotation-origin-x
194-
<SlintProperty propName="rotation-origin-x" typeName="length" defaultValue='self.width / 2'/>
195-
### rotation-origin-y
196-
<SlintProperty propName="rotation-origin-y" typeName="length" defaultValue='self.height / 2'/>
197-
198175
## Source Properties
199176

200177
### source

docs/astro/src/content/docs/reference/elements/text.mdx

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -233,35 +233,6 @@ Text {
233233
</CodeSnippetMD>
234234
</SlintProperty>
235235

236-
## Rotation
237-
238-
Rotates the text by the given angle around the specified origin point. The default origin point is the center of the element.
239-
:::caution[Caution]
240-
When these properties are set, the `Text` can't have children.
241-
:::
242-
243-
<CodeSnippetMD imagePath="/src/assets/generated/text-rotation.png" imageWidth="200" needsBackground="true" imageHeight="200" imageAlt='rotation properties'>
244-
245-
```slint {3-5}
246-
Text {
247-
text: "I'm dizzy";
248-
rotation-angle: 45deg;
249-
rotation-origin-x: self.width / 2;
250-
rotation-origin-y: self.height / 2;
251-
font-size: 30pt;
252-
}
253-
```
254-
</CodeSnippetMD>
255-
256-
### rotation-angle
257-
<SlintProperty propName="rotation-angle" typeName="angle"/>
258-
259-
### rotation-origin-x
260-
<SlintProperty propName="rotation-origin-x" typeName="length" defaultValue='self.width / 2'/>
261-
262-
### rotation-origin-y
263-
<SlintProperty propName="rotation-origin-y" typeName="length" defaultValue='self.height / 2'/>
264-
265236
## Accessibility
266237

267238
By default, `Text` elements have the following accessibility properties set:

internal/backends/qt/qt_window.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,13 @@ impl ItemRenderer for QtItemRenderer<'_> {
13231323
}}
13241324
}
13251325

1326+
fn scale(&mut self, x_factor: f32, y_factor: f32) {
1327+
let painter: &mut QPainterPtr = &mut self.painter;
1328+
cpp! { unsafe [painter as "QPainterPtr*", x_factor as "float", y_factor as "float"] {
1329+
(*painter)->scale(x_factor, y_factor);
1330+
}}
1331+
}
1332+
13261333
fn apply_opacity(&mut self, opacity: f32) {
13271334
let painter: &mut QPainterPtr = &mut self.painter;
13281335
cpp! { unsafe [painter as "QPainterPtr*", opacity as "float"] {

internal/compiler/builtins.slint

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ export component ComponentContainer inherits Empty {
8181
//-accepts_focus
8282
}
8383

84-
export component Rotate inherits Empty {
84+
export component Transform inherits Empty {
8585
in property <angle> rotation-angle;
86+
in property <percent> scale-x;
87+
in property <percent> scale-y;
8688
in property <length> rotation-origin-x;
8789
in property <length> rotation-origin-y;
8890
//-default_size_binding:expands_to_parent_geometry

internal/compiler/passes.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub async fn run_passes(
146146
z_order::reorder_by_z_order(component, diag);
147147
lower_property_to_element::lower_property_to_element(
148148
component,
149-
"opacity",
149+
core::iter::once("opacity"),
150150
core::iter::empty(),
151151
None,
152152
&SmolStr::new_static("Opacity"),
@@ -155,7 +155,7 @@ pub async fn run_passes(
155155
);
156156
lower_property_to_element::lower_property_to_element(
157157
component,
158-
"cache-rendering-hint",
158+
core::iter::once("cache-rendering-hint"),
159159
core::iter::empty(),
160160
None,
161161
&SmolStr::new_static("Layer"),
@@ -166,25 +166,28 @@ pub async fn run_passes(
166166
lower_shadows::lower_shadow_properties(component, &doc.local_registry, diag);
167167
lower_property_to_element::lower_property_to_element(
168168
component,
169-
crate::typeregister::RESERVED_ROTATION_PROPERTIES[0].0,
170-
crate::typeregister::RESERVED_ROTATION_PROPERTIES[1..]
169+
crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[..3]
171170
.iter()
172171
.map(|(prop_name, _)| *prop_name),
173-
Some(&|e, prop| Expression::BinaryExpression {
174-
lhs: Expression::PropertyReference(NamedReference::new(
175-
e,
176-
match prop {
177-
"rotation-origin-x" => SmolStr::new_static("width"),
178-
"rotation-origin-y" => SmolStr::new_static("height"),
179-
"rotation-angle" => return Expression::Invalid,
180-
_ => unreachable!(),
181-
},
182-
))
183-
.into(),
184-
op: '/',
185-
rhs: Expression::NumberLiteral(2., Default::default()).into(),
172+
crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[3..]
173+
.iter()
174+
.map(|(prop_name, _)| *prop_name),
175+
Some(&|e, prop| {
176+
let prop_div_2 = |prop: &str| Expression::BinaryExpression {
177+
lhs: Expression::PropertyReference(NamedReference::new(e, prop.into())).into(),
178+
op: '/',
179+
rhs: Expression::NumberLiteral(2., Default::default()).into(),
180+
};
181+
182+
match prop {
183+
"rotation-origin-x" => prop_div_2("width"),
184+
"rotation-origin-y" => prop_div_2("height"),
185+
"scale-x" | "scale-y" => Expression::NumberLiteral(1., Default::default()),
186+
"rotation-angle" => Expression::NumberLiteral(0., Default::default()),
187+
_ => unreachable!(),
188+
}
186189
}),
187-
&SmolStr::new_static("Rotate"),
190+
&SmolStr::new_static("Transform"),
188191
&global_type_registry.borrow(),
189192
diag,
190193
);

internal/compiler/passes/lower_property_to_element.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,28 @@ use crate::typeregister::TypeRegister;
1212
use smol_str::{format_smolstr, SmolStr, ToSmolStr};
1313
use std::rc::Rc;
1414

15-
/// If any element in `component` declares a binding to `property_name`, then a new
15+
/// If any element in `component` declares a binding to any of `property_names`, then a new
1616
/// element of type `element_name` is created, injected as a parent to the element and bindings
17-
/// to property_name and all properties in extra_properties are mapped.
17+
/// to all properties in property_names and extra_properties are mapped.
1818
/// Default value for the property extra_properties is queried with the `default_value_for_extra_properties`
1919
pub(crate) fn lower_property_to_element(
2020
component: &Rc<Component>,
21-
property_name: &'static str,
21+
property_names: impl Iterator<Item = &'static str> + Clone,
2222
extra_properties: impl Iterator<Item = &'static str> + Clone,
2323
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
2424
element_name: &SmolStr,
2525
type_register: &TypeRegister,
2626
diag: &mut BuildDiagnostics,
2727
) {
28-
if let Some(b) = component.root_element.borrow().bindings.get(property_name) {
29-
diag.push_warning(
30-
format!(
31-
"The {property_name} property cannot be used on the root element, it will not be applied"
32-
),
33-
&*b.borrow(),
34-
);
28+
for property_name in property_names.clone() {
29+
if let Some(b) = component.root_element.borrow().bindings.get(property_name) {
30+
diag.push_warning(
31+
format!(
32+
"The {property_name} property cannot be used on the root element, it will not be applied"
33+
),
34+
&*b.borrow(),
35+
);
36+
}
3537
}
3638

3739
object_tree::recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
@@ -46,13 +48,15 @@ pub(crate) fn lower_property_to_element(
4648
};
4749

4850
let has_property_binding = |e: &ElementRc| {
49-
e.borrow().base_type.lookup_property(property_name).property_type != Type::Invalid
50-
&& (e.borrow().bindings.contains_key(property_name)
51-
|| e.borrow()
52-
.property_analysis
53-
.borrow()
54-
.get(property_name)
55-
.is_some_and(|a| a.is_set || a.is_linked))
51+
property_names.clone().any(|property_name| {
52+
e.borrow().base_type.lookup_property(property_name).property_type != Type::Invalid
53+
&& (e.borrow().bindings.contains_key(property_name)
54+
|| e.borrow()
55+
.property_analysis
56+
.borrow()
57+
.get(property_name)
58+
.is_some_and(|a| a.is_set || a.is_linked))
59+
})
5660
};
5761

5862
for mut child in old_children {
@@ -63,8 +67,7 @@ pub(crate) fn lower_property_to_element(
6367
&child,
6468
create_property_element(
6569
&root_elem,
66-
property_name,
67-
extra_properties.clone(),
70+
property_names.clone().chain(extra_properties.clone()),
6871
default_value_for_extra_properties,
6972
element_name,
7073
type_register,
@@ -74,8 +77,7 @@ pub(crate) fn lower_property_to_element(
7477
} else if has_property_binding(&child) {
7578
let new_child = create_property_element(
7679
&child,
77-
property_name,
78-
extra_properties.clone(),
80+
property_names.clone().chain(extra_properties.clone()),
7981
default_value_for_extra_properties,
8082
element_name,
8183
type_register,
@@ -92,14 +94,12 @@ pub(crate) fn lower_property_to_element(
9294

9395
fn create_property_element(
9496
child: &ElementRc,
95-
property_name: &'static str,
96-
extra_properties: impl Iterator<Item = &'static str>,
97+
properties: impl Iterator<Item = &'static str>,
9798
default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>,
9899
element_name: &SmolStr,
99100
type_register: &TypeRegister,
100101
) -> ElementRc {
101-
let bindings = core::iter::once(property_name)
102-
.chain(extra_properties)
102+
let bindings = properties
103103
.map(|property_name| {
104104
let mut bind =
105105
BindingExpression::new_two_way(NamedReference::new(child, property_name.into()));
@@ -113,7 +113,7 @@ fn create_property_element(
113113
.collect();
114114

115115
let element = Element {
116-
id: format_smolstr!("{}-{}", child.borrow().id, property_name),
116+
id: format_smolstr!("{}-{}", child.borrow().id, element_name),
117117
base_type: type_register.lookup_element(element_name).unwrap(),
118118
enclosing_component: child.borrow().enclosing_component.clone(),
119119
bindings,

0 commit comments

Comments
 (0)