slint/internal/compiler/passes/lower_property_to_element.rs
Olivier Goffart 81e5d3bb36 Fix setting rotation-angle and opacity from a callback
... if they are not set as actual binding before

As reported in #3068

The problem is that the pass will properly create the Rotation or
Opacity item, but will not create the two way binding if there is no
existing binding. This causes code like `img.rotation-angle = ...` to
change the rotation angle of the image, but not its parent Rotation
item.
Fix it by making sure there is always a binding.

Since the change only affect visual representation, I abused one of the
screenshot test to test this feature. And I also patched another bug
that some #[allow(unused_parens)] was not set in the 'init' callback and
would cause a warning in the test
2023-07-11 20:07:49 +02:00

125 lines
5.1 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
//! Pass that lowers synthetic properties such as `opacity` and `layer` properties to their corresponding elements.
//! For example `f := Foo { opacity: <some float>; }` is mapped to `Opacity { opacity <=> f.opacity; f := Foo { ... } }`
use std::cell::RefCell;
use std::rc::Rc;
use crate::diagnostics::BuildDiagnostics;
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 `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 value for the property extra_properties is queried with 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,
) {
if let Some(b) = component.root_element.borrow().bindings.get(property_name) {
diag.push_warning(
format!(
"The {} property cannot be used on the root element, it will not be applied",
property_name
),
&*b.borrow(),
);
}
object_tree::recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
if elem.borrow().base_type.to_string() == element_name {
return;
}
let old_children = {
let mut elem = elem.borrow_mut();
let new_children = Vec::with_capacity(elem.children.len());
std::mem::replace(&mut elem.children, new_children)
};
let has_property_binding = |e: &ElementRc| {
e.borrow().base_type.lookup_property(property_name).property_type != Type::Invalid
&& (e.borrow().bindings.contains_key(property_name)
|| e.borrow()
.property_analysis
.borrow()
.get(property_name)
.map_or(false, |a| a.is_set))
};
for mut child in old_children {
if child.borrow().repeated.is_some() {
let root_elem = child.borrow().base_type.as_component().root_element.clone();
if has_property_binding(&root_elem) {
object_tree::inject_element_as_repeated_element(
&child,
create_property_element(
&root_elem,
property_name,
extra_properties.clone(),
default_value_for_extra_properties,
element_name,
type_register,
),
)
}
} else if has_property_binding(&child) {
let new_child = create_property_element(
&child,
property_name,
extra_properties.clone(),
default_value_for_extra_properties,
element_name,
type_register,
);
crate::object_tree::adjust_geometry_for_injected_parent(&new_child, &child);
new_child.borrow_mut().children.push(child);
child = new_child;
}
elem.borrow_mut().children.push(child);
}
});
}
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 {
let bindings = core::iter::once(property_name)
.chain(extra_properties)
.map(|property_name| {
let mut bind =
BindingExpression::new_two_way(NamedReference::new(child, property_name));
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)
}
}
(property_name.to_string(), bind.into())
})
.collect();
let element = Element {
id: format!("{}-{}", child.borrow().id, property_name),
base_type: type_register.lookup_element(element_name).unwrap(),
enclosing_component: child.borrow().enclosing_component.clone(),
bindings,
..Default::default()
};
Rc::new(RefCell::new(element))
}