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,
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.bindings.insert(
96                target_property,
97                RefCell::new(
98                    Expression::PropertyReference(NamedReference::new(
99                        sibling_element,
100                        source_property,
101                    ))
102                    .into(),
103                ),
104            );
105        }
106    }
107
108    Some(element)
109}
110
111fn prepend_inner_shadow_child(parent: &ElementRc, inner_elem: Element) {
112    let inner_rc = ElementRc::new(inner_elem.into());
113    inner_rc.borrow_mut().geometry_props = Some(GeometryProps::new(&inner_rc));
114    for property_name in ["width", "height"] {
115        inner_rc.borrow_mut().bindings.insert(
116            property_name.into(),
117            RefCell::new(
118                Expression::PropertyReference(NamedReference::new(
119                    parent,
120                    SmolStr::new_static(property_name),
121                ))
122                .into(),
123            ),
124        );
125    }
126    parent.borrow_mut().children.insert(0, inner_rc);
127}
128
129// For a repeated element with a drop shadow, the shadow becomes the new root so it renders below the repeated
130// element. This is only used for drop shadows; inner shadows on a repeated root are prepended as a child instead.
131fn inject_shadow_element_in_repeated_element(
132    shadow_property_bindings: HashMap<SmolStr, BindingExpression>,
133    repeated_element: &ElementRc,
134    type_register: &TypeRegister,
135    diag: &mut BuildDiagnostics,
136) {
137    let element_with_shadow_property =
138        &repeated_element.borrow().base_type.as_component().root_element.clone();
139
140    let shadow_element = match create_box_shadow_element(
141        shadow_property_bindings,
142        element_with_shadow_property,
143        ShadowKind::Drop,
144        type_register,
145        diag,
146    ) {
147        Some(element) => element,
148        None => return,
149    };
150
151    crate::object_tree::inject_element_as_repeated_element(
152        repeated_element,
153        Element::make_rc(shadow_element),
154    );
155}
156
157fn take_shadow_property_bindings(
158    element: &ElementRc,
159    kind: ShadowKind,
160) -> HashMap<SmolStr, BindingExpression> {
161    kind.property_list()
162        .iter()
163        .flat_map(|(shadow_property_name, _)| {
164            let shadow_property_name = SmolStr::new(shadow_property_name);
165            let mut element = element.borrow_mut();
166            element.bindings.remove(&shadow_property_name).map(|binding| {
167                // Remove the shadow property that was also materialized as a fake property by now.
168                element.property_declarations.remove(&shadow_property_name);
169                (shadow_property_name, binding.into_inner())
170            })
171        })
172        .collect()
173}
174
175pub fn lower_shadow_properties(
176    component: &Rc<Component>,
177    type_register: &TypeRegister,
178    diag: &mut BuildDiagnostics,
179) {
180    for kind in [ShadowKind::Drop, ShadowKind::Inner] {
181        for (shadow_prop_name, shadow_prop_binding) in
182            take_shadow_property_bindings(&component.root_element, kind)
183        {
184            diag.push_warning(
185                format!("The {shadow_prop_name} property cannot be used on the root element, the shadow will not be visible"),
186                &shadow_prop_binding,
187            );
188        }
189    }
190
191    recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
192        // Repeater handling: drop shadow becomes the new root (so it renders underneath); inner
193        // shadow is prepended as a child of the repeater's root rectangle (so it renders above
194        // the background but below the rectangle's original children).
195        if elem.borrow().repeated.is_some() {
196            // Take both binding sets up front, then release every Rc clone before
197            // `inject_element_as_repeated_element`, which asserts the component has strong_count == 2.
198            let (drop_shadow_properties, inner_shadow_properties) = {
199                let component = elem.borrow().base_type.as_component().clone();
200                let drop = take_shadow_property_bindings(&component.root_element, ShadowKind::Drop);
201                let inner =
202                    take_shadow_property_bindings(&component.root_element, ShadowKind::Inner);
203                (drop, inner)
204            };
205
206            if !drop_shadow_properties.is_empty() {
207                inject_shadow_element_in_repeated_element(
208                    drop_shadow_properties,
209                    elem,
210                    type_register,
211                    diag,
212                );
213                // After injection the original rectangle is a child of the new shadow root.
214                // Prepend the inner BoxShadow as a child of that rectangle.
215                if !inner_shadow_properties.is_empty() {
216                    let rect_child = elem
217                        .borrow()
218                        .base_type
219                        .as_component()
220                        .root_element
221                        .borrow()
222                        .children
223                        .first()
224                        .cloned();
225                    if let Some(rect_child) = rect_child
226                        && let Some(inner_elem) = create_box_shadow_element(
227                            inner_shadow_properties,
228                            &rect_child,
229                            ShadowKind::Inner,
230                            type_register,
231                            diag,
232                        )
233                    {
234                        prepend_inner_shadow_child(&rect_child, inner_elem);
235                    }
236                }
237            } else if !inner_shadow_properties.is_empty() {
238                // No drop shadow: prepend inner shadow as a child of the repeater root rectangle.
239                let root = elem.borrow().base_type.as_component().root_element.clone();
240                if let Some(inner_elem) = create_box_shadow_element(
241                    inner_shadow_properties,
242                    &root,
243                    ShadowKind::Inner,
244                    type_register,
245                    diag,
246                ) {
247                    prepend_inner_shadow_child(&root, inner_elem);
248                }
249            }
250        }
251
252        let old_children = {
253            let mut elem = elem.borrow_mut();
254            let new_children = Vec::with_capacity(elem.children.len());
255            std::mem::replace(&mut elem.children, new_children)
256        };
257
258        // For each child: drop shadow renders BEFORE (underneath); inner shadow is prepended as
259        // the child's first child (above background, below the original child content).
260        for child in old_children {
261            let drop_shadow_properties = take_shadow_property_bindings(&child, ShadowKind::Drop);
262            let inner_shadow_properties = take_shadow_property_bindings(&child, ShadowKind::Inner);
263
264            if !drop_shadow_properties.is_empty()
265                && let Some(mut shadow_elem) = create_box_shadow_element(
266                    drop_shadow_properties,
267                    &child,
268                    ShadowKind::Drop,
269                    type_register,
270                    diag,
271                )
272            {
273                shadow_elem.geometry_props.clone_from(&child.borrow().geometry_props);
274                elem.borrow_mut().children.push(ElementRc::new(shadow_elem.into()));
275            }
276
277            if !inner_shadow_properties.is_empty()
278                && let Some(shadow_elem) = create_box_shadow_element(
279                    inner_shadow_properties,
280                    &child,
281                    ShadowKind::Inner,
282                    type_register,
283                    diag,
284                )
285            {
286                prepend_inner_shadow_child(&child, shadow_elem);
287            }
288
289            elem.borrow_mut().children.push(child);
290        }
291    });
292}