diff --git a/demos/home-automation/ui/components/mainView/widgetLoader.slint b/demos/home-automation/ui/components/mainView/widgetLoader.slint index b2898ece575..ee64abc826f 100644 --- a/demos/home-automation/ui/components/mainView/widgetLoader.slint +++ b/demos/home-automation/ui/components/mainView/widgetLoader.slint @@ -192,8 +192,7 @@ export component WidgetLoader { id: AppState.component-details[root.index].id; width: root.width; height: root.height; - scale-x: scaler; - scale-y: scaler; + transform-scale: scaler; } if root.type == WidgetType.powerInfo: PowerInfo { diff --git a/demos/home-automation/ui/components/mainView/widgetLoaderSoftwareRenderer.slint b/demos/home-automation/ui/components/mainView/widgetLoaderSoftwareRenderer.slint index f59410033a1..43e9999fe64 100644 --- a/demos/home-automation/ui/components/mainView/widgetLoaderSoftwareRenderer.slint +++ b/demos/home-automation/ui/components/mainView/widgetLoaderSoftwareRenderer.slint @@ -128,8 +128,7 @@ export component WidgetLoaderSoftwareRenderer { id: AppState.component-details[root.index].id; width: root.width; height: root.height; - scale-x: scaler; - scale-y: scaler; + transform-scale: scaler; } if root.type == WidgetType.powerInfo && root.is-visible: PowerInfo { diff --git a/docs/astro/src/content/docs/reference/common.mdx b/docs/astro/src/content/docs/reference/common.mdx index 650b8ad4b94..ed29c51914b 100644 --- a/docs/astro/src/content/docs/reference/common.mdx +++ b/docs/astro/src/content/docs/reference/common.mdx @@ -72,10 +72,21 @@ The y component of the origin to rotate and scale around. In the future this will be deprecated in favour of a transform-origin property. -#### scale-x - -#### scale-y - + +#### transform-scale + +The scale factor to apply to the element and all its children. + +This doesn't affect the geometry (width, height) of the element, but affects the rendering. +The scale is done around the `rotation-origin` point. + +It is also possible to use the `transform-scale-x` and `transform-scale-y` properties to specify the scale factors for the x and y axis. + + +#### transform-scale-x + +#### transform-scale-y + ### opacity diff --git a/examples/fancy-switches/DarkModeSwitch.slint b/examples/fancy-switches/DarkModeSwitch.slint index 809eef45416..b8830edcb3d 100644 --- a/examples/fancy-switches/DarkModeSwitch.slint +++ b/examples/fancy-switches/DarkModeSwitch.slint @@ -123,14 +123,13 @@ export component DarkModeSwitch { frameBacker.background: #2A2A2A; moon.rotation-angle: -20deg; sun.color: #fc7a10; - sun.scale-x: 0.8; - sun.scale-y: 0.8; + sun.transform-scale: 0.8; in { animate thumb.x, thumb.background, frameBacker.background, sun.color { duration: 200ms; easing: ease-out-sine; } - animate moon.rotation-angle, sun.scale-x, sun.scale-y { + animate moon.rotation-angle, sun.transform-scale { duration: 1200ms; easing: ease-out-elastic; } @@ -143,14 +142,13 @@ export component DarkModeSwitch { frameBacker.background: transparent; moon.rotation-angle: 20deg; sun.color: #2A2A2A; - sun.scale-x: 1; - sun.scale-y: 1; + sun.transform-scale: 1; in { animate thumb.x, thumb.background, frameBacker.background, moon.rotation-angle { duration: 200ms; easing: ease-out-sine; } - animate sun.scale-x, sun.scale-y { + animate sun.transform-scale { duration: 1200ms; easing: ease-out-elastic; } diff --git a/internal/compiler/builtins.slint b/internal/compiler/builtins.slint index d2708f131df..11ce1f915ed 100644 --- a/internal/compiler/builtins.slint +++ b/internal/compiler/builtins.slint @@ -83,8 +83,8 @@ export component ComponentContainer inherits Empty { export component Transform inherits Empty { in property rotation-angle; - in property scale-x; - in property scale-y; + in property transform-scale-x; + in property transform-scale-y; in property rotation-origin-x; in property rotation-origin-y; //-default_size_binding:expands_to_parent_geometry diff --git a/internal/compiler/passes.rs b/internal/compiler/passes.rs index 116237c4140..eb841c2b026 100644 --- a/internal/compiler/passes.rs +++ b/internal/compiler/passes.rs @@ -55,7 +55,6 @@ mod visible; mod z_order; use crate::expression_tree::Expression; -use crate::namedreference::NamedReference; use smol_str::SmolStr; pub fn ignore_debug_hooks(expr: &Expression) -> &Expression { @@ -166,30 +165,8 @@ pub async fn run_passes( ); visible::handle_visible(component, &global_type_registry.borrow(), diag); lower_shadows::lower_shadow_properties(component, &doc.local_registry, diag); - lower_property_to_element::lower_property_to_element( + lower_property_to_element::lower_transform_properties( component, - crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[..3] - .iter() - .map(|(prop_name, _)| *prop_name), - crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[3..] - .iter() - .map(|(prop_name, _)| *prop_name), - Some(&|e, prop| { - let prop_div_2 = |prop: &str| Expression::BinaryExpression { - lhs: Expression::PropertyReference(NamedReference::new(e, prop.into())).into(), - op: '/', - rhs: Expression::NumberLiteral(2., Default::default()).into(), - }; - - match prop { - "rotation-origin-x" => prop_div_2("width"), - "rotation-origin-y" => prop_div_2("height"), - "scale-x" | "scale-y" => Expression::NumberLiteral(1., Default::default()), - "rotation-angle" => Expression::NumberLiteral(0., Default::default()), - _ => unreachable!(), - } - }), - &SmolStr::new_static("Transform"), &global_type_registry.borrow(), diag, ); diff --git a/internal/compiler/passes/lower_property_to_element.rs b/internal/compiler/passes/lower_property_to_element.rs index 489c1956e0c..0d3182696ea 100644 --- a/internal/compiler/passes/lower_property_to_element.rs +++ b/internal/compiler/passes/lower_property_to_element.rs @@ -20,7 +20,7 @@ pub(crate) fn lower_property_to_element( component: &Rc, property_names: impl Iterator + Clone, extra_properties: impl Iterator + Clone, - default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>, + default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Option>, element_name: &SmolStr, type_register: &TypeRegister, diag: &mut BuildDiagnostics, @@ -95,7 +95,7 @@ pub(crate) fn lower_property_to_element( fn create_property_element( child: &ElementRc, properties: impl Iterator, - default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Expression>, + default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Option>, element_name: &SmolStr, type_register: &TypeRegister, ) -> ElementRc { @@ -105,7 +105,9 @@ fn create_property_element( BindingExpression::new_two_way(NamedReference::new(child, property_name.into())); if let Some(default_value_for_extra_properties) = default_value_for_extra_properties { if !child.borrow().bindings.contains_key(property_name) { - bind.expression = default_value_for_extra_properties(child, property_name) + if let Some(e) = default_value_for_extra_properties(child, property_name) { + bind.expression = e; + } } } (property_name.into(), bind.into()) @@ -121,3 +123,50 @@ fn create_property_element( }; element.make_rc() } + +/// Wrapper around lower_property_to_element for the Transform element +pub fn lower_transform_properties( + component: &Rc, + tr: &TypeRegister, + diag: &mut BuildDiagnostics, +) { + lower_property_to_element( + component, + crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[..4] + .iter() + .map(|(prop_name, _)| *prop_name), + crate::typeregister::RESERVED_TRANSFORM_PROPERTIES[4..] + .iter() + .map(|(prop_name, _)| *prop_name), + Some(&|e, prop| { + let prop_div_2 = |prop: &str| { + Some(Expression::BinaryExpression { + lhs: Expression::PropertyReference(NamedReference::new(e, prop.into())).into(), + op: '/', + rhs: Expression::NumberLiteral(2., Default::default()).into(), + }) + }; + + match prop { + "rotation-origin-x" => prop_div_2("width"), + "rotation-origin-y" => prop_div_2("height"), + "transform-scale-x" | "transform-scale-y" => { + if e.borrow().is_binding_set("transform-scale", true) { + Some(Expression::PropertyReference(NamedReference::new( + e, + SmolStr::new_static("transform-scale"), + ))) + } else { + Some(Expression::NumberLiteral(1., Default::default())) + } + } + "transform-scale" => None, + "rotation-angle" => Some(Expression::NumberLiteral(0., Default::default())), + _ => unreachable!(), + } + }), + &SmolStr::new_static("Transform"), + tr, + diag, + ); +} diff --git a/internal/compiler/typeregister.rs b/internal/compiler/typeregister.rs index 062cf114afc..750256f4b0d 100644 --- a/internal/compiler/typeregister.rs +++ b/internal/compiler/typeregister.rs @@ -177,8 +177,9 @@ pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[ pub const RESERVED_TRANSFORM_PROPERTIES: &[(&str, Type)] = &[ ("rotation-angle", Type::Angle), - ("scale-x", Type::Float32), - ("scale-y", Type::Float32), + ("transform-scale-x", Type::Float32), + ("transform-scale-y", Type::Float32), + ("transform-scale", Type::Float32), ("rotation-origin-x", Type::LogicalLength), ("rotation-origin-y", Type::LogicalLength), ]; diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index 3f736661d6d..eb61d582ac6 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -825,8 +825,8 @@ impl ItemRc { ItemTransform::translation(-origin.x, -origin.y) .cast() .then_scale( - transform_item.as_pin_ref().scale_x(), - transform_item.as_pin_ref().scale_y(), + transform_item.as_pin_ref().transform_scale_x(), + transform_item.as_pin_ref().transform_scale_y(), ) .then_rotate(euclid::Angle { radians: transform_item.as_pin_ref().rotation_angle().to_radians(), diff --git a/internal/core/items.rs b/internal/core/items.rs index 7d8ff8ff55e..1b49e9d463f 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -1027,8 +1027,8 @@ declare_item_vtable! { /// The implementation of the `Rotate` element pub struct Transform { pub rotation_angle: Property, - pub scale_x: Property, - pub scale_y: Property, + pub transform_scale_x: Property, + pub transform_scale_y: Property, pub rotation_origin_x: Property, pub rotation_origin_y: Property, pub cached_rendering_data: CachedRenderingData, @@ -1100,7 +1100,7 @@ impl Item for Transform { let origin = LogicalVector::from_lengths(self.rotation_origin_x(), self.rotation_origin_y()); (*backend).translate(origin); - (*backend).scale(self.scale_x(), self.scale_y()); + (*backend).scale(self.transform_scale_x(), self.transform_scale_y()); (*backend).rotate(self.rotation_angle()); (*backend).translate(-origin); RenderingResult::ContinueRenderingChildren diff --git a/tests/cases/elements/event_scaling.slint b/tests/cases/elements/event_scaling.slint index 9d93c788d8d..f1e89af7016 100644 --- a/tests/cases/elements/event_scaling.slint +++ b/tests/cases/elements/event_scaling.slint @@ -20,8 +20,8 @@ export component TestCase { y: 0px; width: 50px; height: 50px; - scale-x: 200%; - scale-y: 400%; + transform-scale-x: 200%; + transform-scale-y: 400%; rotation-origin-x: 0px; rotation-origin-y: 0px; area1 := TouchArea { diff --git a/tests/cases/elements/scaling2.slint b/tests/cases/elements/scaling2.slint new file mode 100644 index 00000000000..bb75db32666 --- /dev/null +++ b/tests/cases/elements/scaling2.slint @@ -0,0 +1,64 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component TestCase { + width: 800px; + height: 600px; + + in-out property result; + + r-a := Rectangle { + x: 10px; + y: 10px; + width: 20px; + height: 20px; + rotation-origin-x: 0; + rotation-origin-y: 0; + transform-scale: 2; + TouchArea { + clicked => { result += "A"} + } + } + + + r-b := Rectangle { + x: 100px; + y: 100px; + width: 20px; + height: 20px; + transform-scale: 3; + transform-scale-x: 0.5; + TouchArea { + clicked => { result += "B"} + } + } + + out property test: + r-a.transform-scale == 2 && r-a.transform-scale-x == 2 && r-a.transform-scale-y == 2 && + r-b.transform-scale-x == 0.5 && r-b.transform-scale-y == 3; + + +} + +/* +```rust +let instance = TestCase::new().unwrap(); + +assert_eq!(instance.get_test(), true); + +slint_testing::send_mouse_click(&instance, 10.0 + 39.0 , 10.0 + 39.0); +assert_eq!(instance.get_result(), "A", "was just clicked inside"); +slint_testing::send_mouse_click(&instance, 10.0 + 19.0 , 10.0 + 41.0); +assert_eq!(instance.get_result(), "A", "was just clicked outside"); +slint_testing::send_mouse_click(&instance, 10.0 + 41.0 , 10.0 + 19.0); +assert_eq!(instance.get_result(), "A", "was just clicked outside again"); + + +slint_testing::send_mouse_click(&instance, 100.0 + 6. , 100.0 - 19.0); +assert_eq!(instance.get_result(), "AB", "was just clicked inside"); +slint_testing::send_mouse_click(&instance, 100.0 + 4., 100.0); +assert_eq!(instance.get_result(), "AB", "was just clicked outside"); +slint_testing::send_mouse_click(&instance, 100.0 + 10., 100. - 21.); +assert_eq!(instance.get_result(), "AB", "was just clicked outside again"); +``` +*/