| 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 | |
| 8 | use crate::diagnostics::BuildDiagnostics; |
| 9 | use crate::expression_tree::BindingExpression; |
| 10 | use crate::{expression_tree::Expression, object_tree::*}; |
| 11 | use crate::{expression_tree::NamedReference, typeregister::TypeRegister}; |
| 12 | use smol_str::{format_smolstr, SmolStr}; |
| 13 | use std::cell::RefCell; |
| 14 | use std::collections::HashMap; |
| 15 | use 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. |
| 19 | fn 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. |
| 65 | fn 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: &Rc> = |
| 72 | &repeated_element.borrow().base_type.as_component().root_element.clone(); |
| 73 | |
| 74 | let shadow_element: Element = match create_box_shadow_element( |
| 75 | shadow_property_bindings, |
| 76 | sibling_element:element_with_shadow_property, |
| 77 | type_register, |
| 78 | diag, |
| 79 | ) { |
| 80 | Some(element: Element) => element, |
| 81 | None => return, |
| 82 | }; |
| 83 | |
| 84 | crate::object_tree::inject_element_as_repeated_element( |
| 85 | repeated_element, |
| 86 | new_root:Element::make_rc(self:shadow_element), |
| 87 | ); |
| 88 | } |
| 89 | |
| 90 | fn take_shadow_property_bindings(element: &ElementRc) -> HashMap<SmolStr, BindingExpression> { |
| 91 | crateimpl Iterator ::typeregister::RESERVED_DROP_SHADOW_PROPERTIES |
| 92 | .iter() |
| 93 | .flat_map(|(shadow_property_name: &&str, _)| { |
| 94 | let shadow_property_name: SmolStr = SmolStr::new(text:shadow_property_name); |
| 95 | let mut element: RefMut<'_, Element> = element.borrow_mut(); |
| 96 | element.bindings.remove(&shadow_property_name).map(|binding: RefCell| { |
| 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 | |
| 105 | pub 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 | } |
| 172 | |