i_slint_compiler/passes/
lower_shadows.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Pass that lowers synthetic `drop-shadow-*` properties to proper shadow elements
5// At the moment only shadows on `Rectangle` elements are supported, i.e. the drop shadow
6// of a rectangle is a box shadow.
7
8use crate::diagnostics::BuildDiagnostics;
9use crate::expression_tree::BindingExpression;
10use crate::{expression_tree::Expression, object_tree::*};
11use crate::{expression_tree::NamedReference, typeregister::TypeRegister};
12use smol_str::{format_smolstr, SmolStr};
13use std::cell::RefCell;
14use std::collections::HashMap;
15use std::rc::Rc;
16
17// Creates a new element for the drop shadow properties that'll be a sibling to the specified
18// sibling element.
19fn create_box_shadow_element(
20    shadow_property_bindings: HashMap<SmolStr, BindingExpression>,
21    sibling_element: &ElementRc,
22    type_register: &TypeRegister,
23    diag: &mut BuildDiagnostics,
24) -> Option<Element> {
25    if matches!(sibling_element.borrow().builtin_type(), Some(b) if b.name != "Rectangle") {
26        for (shadow_prop_name, shadow_prop_binding) in shadow_property_bindings {
27            diag.push_error(
28                format!("The {shadow_prop_name} property is only supported on Rectangle elements right now"),
29                &shadow_prop_binding,
30            );
31        }
32        return None;
33    }
34
35    let mut element = Element {
36        id: format_smolstr!("{}-shadow", sibling_element.borrow().id),
37        base_type: type_register.lookup_builtin_element("BoxShadow").unwrap(),
38        enclosing_component: sibling_element.borrow().enclosing_component.clone(),
39        bindings: shadow_property_bindings
40            .into_iter()
41            .map(|(shadow_prop_name, expr)| {
42                (shadow_prop_name.strip_prefix("drop-shadow-").unwrap().into(), expr.into())
43            })
44            .collect(),
45        ..Default::default()
46    };
47
48    // FIXME: remove the border-radius manual mapping.
49    let border_radius = SmolStr::new_static("border-radius");
50    if sibling_element.borrow().bindings.contains_key(&border_radius) {
51        element.bindings.insert(
52            border_radius.clone(),
53            RefCell::new(
54                Expression::PropertyReference(NamedReference::new(sibling_element, border_radius))
55                    .into(),
56            ),
57        );
58    }
59
60    Some(element)
61}
62
63// For a repeated element, this function creates a new element for the drop shadow properties that
64// will act as the new root element in the repeater. The former root will become a child.
65fn inject_shadow_element_in_repeated_element(
66    shadow_property_bindings: HashMap<SmolStr, BindingExpression>,
67    repeated_element: &ElementRc,
68    type_register: &TypeRegister,
69    diag: &mut BuildDiagnostics,
70) {
71    let element_with_shadow_property =
72        &repeated_element.borrow().base_type.as_component().root_element.clone();
73
74    let shadow_element = match create_box_shadow_element(
75        shadow_property_bindings,
76        element_with_shadow_property,
77        type_register,
78        diag,
79    ) {
80        Some(element) => element,
81        None => return,
82    };
83
84    crate::object_tree::inject_element_as_repeated_element(
85        repeated_element,
86        Element::make_rc(shadow_element),
87    );
88}
89
90fn take_shadow_property_bindings(element: &ElementRc) -> HashMap<SmolStr, BindingExpression> {
91    crate::typeregister::RESERVED_DROP_SHADOW_PROPERTIES
92        .iter()
93        .flat_map(|(shadow_property_name, _)| {
94            let shadow_property_name = SmolStr::new(shadow_property_name);
95            let mut element = element.borrow_mut();
96            element.bindings.remove(&shadow_property_name).map(|binding| {
97                // Remove the drop-shadow property that was also materialized as a fake property by now.
98                element.property_declarations.remove(&shadow_property_name);
99                (shadow_property_name, binding.into_inner())
100            })
101        })
102        .collect()
103}
104
105pub fn lower_shadow_properties(
106    component: &Rc<Component>,
107    type_register: &TypeRegister,
108    diag: &mut BuildDiagnostics,
109) {
110    for (shadow_prop_name, shadow_prop_binding) in
111        take_shadow_property_bindings(&component.root_element)
112    {
113        diag.push_warning(
114            format!("The {shadow_prop_name} property cannot be used on the root element, the shadow will not be visible"),
115            &shadow_prop_binding,
116        );
117    }
118
119    recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
120        // When encountering a repeater where the repeated element has a `drop-shadow` property, we create a new
121        // dedicated shadow element and make the previously repeated element a child of that. This ensures rendering
122        // underneath while maintaining the hierarchy for the repeater.
123        // The geometry properties are aliased using two-way bindings (which may be eliminated in a later pass).
124
125        if elem.borrow().repeated.is_some() {
126            let component = elem.borrow().base_type.as_component().clone(); // CHECK if clone can be removed if we change borrow
127
128            let drop_shadow_properties = take_shadow_property_bindings(&component.root_element);
129            if !drop_shadow_properties.is_empty() {
130                drop(component);
131                inject_shadow_element_in_repeated_element(
132                    drop_shadow_properties,
133                    elem,
134                    type_register,
135                    diag,
136                );
137            }
138        }
139
140        let old_children = {
141            let mut elem = elem.borrow_mut();
142            let new_children = Vec::with_capacity(elem.children.len());
143            std::mem::replace(&mut elem.children, new_children)
144        };
145
146        // When encountering a `drop-shadow` property in a supported element, we create a new dedicated
147        // shadow element and insert it *before* the element that had the `drop-shadow` property, to ensure
148        // that it is rendered underneath.
149        for child in old_children {
150            let drop_shadow_properties = take_shadow_property_bindings(&child);
151            if !drop_shadow_properties.is_empty() {
152                let mut shadow_elem = match create_box_shadow_element(
153                    drop_shadow_properties,
154                    &child,
155                    type_register,
156                    diag,
157                ) {
158                    Some(element) => element,
159                    None => {
160                        elem.borrow_mut().children.push(child);
161                        continue;
162                    }
163                };
164
165                shadow_elem.geometry_props.clone_from(&child.borrow().geometry_props);
166                elem.borrow_mut().children.push(ElementRc::new(shadow_elem.into()));
167            }
168            elem.borrow_mut().children.push(child);
169        }
170    });
171}