Skip to main content

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-*` and `inner-shadow-*` properties to proper shadow elements.
5// At the moment only shadows on `Rectangle` elements are supported.
6
7use crate::diagnostics::BuildDiagnostics;
8use crate::expression_tree::BindingExpression;
9use crate::{expression_tree::Expression, object_tree::*};
10use crate::{expression_tree::NamedReference, typeregister::TypeRegister};
11use smol_str::{SmolStr, format_smolstr};
12use std::cell::RefCell;
13use std::collections::HashMap;
14use std::rc::Rc;
15
16#[derive(Copy, Clone)]
17enum ShadowKind {
18    Drop,
19    Inner,
20}
21
22impl ShadowKind {
23    fn prefix(self) -> &'static str {
24        match self {
25            ShadowKind::Drop => "drop-shadow-",
26            ShadowKind::Inner => "inner-shadow-",
27        }
28    }
29
30    fn property_list(self) -> &'static [(&'static str, crate::langtype::Type)] {
31        match self {
32            ShadowKind::Drop => crate::typeregister::RESERVED_DROP_SHADOW_PROPERTIES,
33            ShadowKind::Inner => crate::typeregister::RESERVED_INNER_SHADOW_PROPERTIES,
34        }
35    }
36}
37
38// Creates a new BoxShadow element holding the supplied bindings, sized to follow `sibling_element`'s geometry.
39fn create_box_shadow_element(
40    shadow_property_bindings: HashMap<SmolStr, BindingExpression>,
41    sibling_element: &ElementRc,
42    kind: ShadowKind,
43    type_register: &TypeRegister,
44    diag: &mut BuildDiagnostics,
45) -> Option<Element> {
46    if matches!(sibling_element.borrow().builtin_type(), Some(b) if b.name != "Rectangle") {
47        for (shadow_prop_name, shadow_prop_binding) in shadow_property_bindings {
48            diag.push_error(
49                format!("The {shadow_prop_name} property is only supported on Rectangle elements right now"),
50                &shadow_prop_binding,
51            );
52        }
53        return None;
54    }
55
56    let prefix = kind.prefix();
57    let id_suffix = match kind {
58        ShadowKind::Drop => "shadow",
59        ShadowKind::Inner => "inner-shadow",
60    };
61
62    let mut bindings: crate::object_tree::BindingsMap = shadow_property_bindings
63        .into_iter()
64        .map(|(shadow_prop_name, expr)| {
65            (shadow_prop_name.strip_prefix(prefix).unwrap().into(), expr.into())
66        })
67        .collect();
68
69    if matches!(kind, ShadowKind::Inner) {
70        bindings.insert(
71            SmolStr::new_static("inset"),
72            RefCell::new(Expression::BoolLiteral(true).into()),
73        );
74    }
75
76    let mut element = Element {
77        id: format_smolstr!("{}-{}", sibling_element.borrow().id, id_suffix),
78        base_type: type_register.lookup_builtin_element("BoxShadow").unwrap(),
79        enclosing_component: sibling_element.borrow().enclosing_component.clone(),
80        bindings: bindings.into(),
81        ..Default::default()
82    };
83
84    for property_name in super::border_radius::BORDER_RADIUS_PROPERTIES {
85        let source_property = if sibling_element.borrow().is_binding_set(property_name, true) {
86            Some(SmolStr::new_static(property_name))
87        } else if sibling_element.borrow().is_binding_set("border-radius", true) {
88            Some(SmolStr::new_static("border-radius"))
89        } else {
90            None
91        };
92
93        if let Some(source_property) = source_property {
94            let target_property = SmolStr::new_static(property_name);
95            element.set_binding(
96                target_property,
97                Expression::PropertyReference(NamedReference::new(
98                    sibling_element,
99                    source_property,
100                ))
101                .into(),
102            );
103        }
104    }
105
106    Some(element)
107}
108
109fn prepend_inner_shadow_child(parent: &ElementRc, inner_elem: Element) {
110    let inner_rc = ElementRc::new(inner_elem.into());
111    inner_rc.borrow_mut().geometry_props = Some(GeometryProps::new(&inner_rc));
112    for property_name in ["width", "height"] {
113        inner_rc.borrow_mut().set_binding(
114            property_name.into(),
115            Expression::PropertyReference(NamedReference::new(
116                parent,
117                SmolStr::new_static(property_name),
118            ))
119            .into(),
120        );
121    }
122    parent.borrow_mut().children.insert(0, inner_rc);
123}
124
125// For a repeated element with a drop shadow, the shadow becomes the new root so it renders below the repeated
126// element. This is only used for drop shadows; inner shadows on a repeated root are prepended as a child instead.
127fn inject_shadow_element_in_repeated_element(
128    shadow_property_bindings: HashMap<SmolStr, BindingExpression>,
129    repeated_element: &ElementRc,
130    type_register: &TypeRegister,
131    diag: &mut BuildDiagnostics,
132) {
133    let element_with_shadow_property =
134        &repeated_element.borrow().base_type.as_component().root_element.clone();
135
136    let shadow_element = match create_box_shadow_element(
137        shadow_property_bindings,
138        element_with_shadow_property,
139        ShadowKind::Drop,
140        type_register,
141        diag,
142    ) {
143        Some(element) => element,
144        None => return,
145    };
146
147    crate::object_tree::inject_element_as_repeated_element(
148        repeated_element,
149        Element::make_rc(shadow_element),
150    );
151}
152
153fn take_shadow_property_bindings(
154    element: &ElementRc,
155    kind: ShadowKind,
156) -> HashMap<SmolStr, BindingExpression> {
157    kind.property_list()
158        .iter()
159        .flat_map(|(shadow_property_name, _)| {
160            let shadow_property_name = SmolStr::new(shadow_property_name);
161            let mut element = element.borrow_mut();
162            element.take_binding(&shadow_property_name).map(|binding| {
163                // Remove the shadow property that was also materialized as a fake property by now.
164                element.property_declarations.remove(&shadow_property_name);
165                (shadow_property_name, binding)
166            })
167        })
168        .collect()
169}
170
171pub fn lower_shadow_properties(
172    component: &Rc<Component>,
173    type_register: &TypeRegister,
174    diag: &mut BuildDiagnostics,
175) {
176    for kind in [ShadowKind::Drop, ShadowKind::Inner] {
177        for (shadow_prop_name, shadow_prop_binding) in
178            take_shadow_property_bindings(&component.root_element, kind)
179        {
180            diag.push_warning(
181                format!("The {shadow_prop_name} property cannot be used on the root element, the shadow will not be visible"),
182                &shadow_prop_binding,
183            );
184        }
185    }
186
187    recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
188        // Repeater handling: drop shadow becomes the new root (so it renders underneath); inner
189        // shadow is prepended as a child of the repeater's root rectangle (so it renders above
190        // the background but below the rectangle's original children).
191        if elem.borrow().repeated.is_some() {
192            // Take both binding sets up front, then release every Rc clone before
193            // `inject_element_as_repeated_element`, which asserts the component has strong_count == 2.
194            let (drop_shadow_properties, inner_shadow_properties) = {
195                let component = elem.borrow().base_type.as_component().clone();
196                let drop = take_shadow_property_bindings(&component.root_element, ShadowKind::Drop);
197                let inner =
198                    take_shadow_property_bindings(&component.root_element, ShadowKind::Inner);
199                (drop, inner)
200            };
201
202            if !drop_shadow_properties.is_empty() {
203                inject_shadow_element_in_repeated_element(
204                    drop_shadow_properties,
205                    elem,
206                    type_register,
207                    diag,
208                );
209                // After injection the original rectangle is a child of the new shadow root.
210                // Prepend the inner BoxShadow as a child of that rectangle.
211                if !inner_shadow_properties.is_empty() {
212                    let rect_child = elem
213                        .borrow()
214                        .base_type
215                        .as_component()
216                        .root_element
217                        .borrow()
218                        .children
219                        .first()
220                        .cloned();
221                    if let Some(rect_child) = rect_child
222                        && let Some(inner_elem) = create_box_shadow_element(
223                            inner_shadow_properties,
224                            &rect_child,
225                            ShadowKind::Inner,
226                            type_register,
227                            diag,
228                        )
229                    {
230                        prepend_inner_shadow_child(&rect_child, inner_elem);
231                    }
232                }
233            } else if !inner_shadow_properties.is_empty() {
234                // No drop shadow: prepend inner shadow as a child of the repeater root rectangle.
235                let root = elem.borrow().base_type.as_component().root_element.clone();
236                if let Some(inner_elem) = create_box_shadow_element(
237                    inner_shadow_properties,
238                    &root,
239                    ShadowKind::Inner,
240                    type_register,
241                    diag,
242                ) {
243                    prepend_inner_shadow_child(&root, inner_elem);
244                }
245            }
246        }
247
248        let old_children = {
249            let mut elem = elem.borrow_mut();
250            let new_children = Vec::with_capacity(elem.children.len());
251            std::mem::replace(&mut elem.children, new_children)
252        };
253
254        // For each child: drop shadow renders BEFORE (underneath); inner shadow is prepended as
255        // the child's first child (above background, below the original child content).
256        for child in old_children {
257            let drop_shadow_properties = take_shadow_property_bindings(&child, ShadowKind::Drop);
258            let inner_shadow_properties = take_shadow_property_bindings(&child, ShadowKind::Inner);
259
260            if !drop_shadow_properties.is_empty()
261                && let Some(mut shadow_elem) = create_box_shadow_element(
262                    drop_shadow_properties,
263                    &child,
264                    ShadowKind::Drop,
265                    type_register,
266                    diag,
267                )
268            {
269                shadow_elem.geometry_props.clone_from(&child.borrow().geometry_props);
270                // Sort the shadow with the same z as its element: ties keep declaration
271                // order and the shadow is inserted right before its element, so it stays beneath.
272                shadow_elem.z_order = child.borrow().z_order.clone();
273                elem.borrow_mut().children.push(ElementRc::new(shadow_elem.into()));
274            }
275
276            if !inner_shadow_properties.is_empty()
277                && let Some(shadow_elem) = create_box_shadow_element(
278                    inner_shadow_properties,
279                    &child,
280                    ShadowKind::Inner,
281                    type_register,
282                    diag,
283                )
284            {
285                prepend_inner_shadow_child(&child, shadow_elem);
286            }
287
288            elem.borrow_mut().children.push(child);
289        }
290    });
291}