ferroui_base/visual/
theme_shadow.rs1use super::{ThemeShadowState, Visual};
12use crate::media::{DropShadowRecipe, ThemeShadow, ThemeShadowTheme};
13use crate::rendering::composition::{CompositionVisual, ThemeShadowVisual};
14use crate::{CornerRadius, FerroPropertyRegistry, Ref, Vector};
15use std::rc::Rc;
16
17impl Visual {
18 pub fn shadow(&self) -> Option<Ref<crate::media::Shadow>> {
22 self.get_value(Self::shadow_property())
23 }
24
25 pub fn set_shadow(&self, value: Option<Ref<crate::media::Shadow>>) {
26 self.set_value(Self::shadow_property(), value)
27 }
28
29 pub fn theme_shadow_caster(&self) -> Option<(Ref<ThemeShadow>, Ref<Visual>)> {
36 let (popup, delegates) = self
37 .read_composition_extras(|extras| {
38 extras.theme_shadow.as_ref().map(|state| (state.popup.clone(), state.delegates_to_child))
39 })
40 .flatten()?;
41 if delegates {
42 return None;
43 }
44 if let Some(popup) = popup.and_then(|popup| popup.upgrade()) {
45 if let Some(shadow) = popup.shadow().and_then(|shadow| shadow.downcast::<ThemeShadow>().ok()) {
46 return Some((shadow, popup));
47 }
48 }
49 let shadow = self.shadow()?.downcast::<ThemeShadow>().ok()?;
50 Some((shadow, self.to_ref()))
51 }
52
53 pub(super) fn may_cast_theme_shadow(&self) -> bool {
56 self.read_composition_extras(|extras| extras.theme_shadow.is_some()).unwrap_or(false)
57 }
58
59 pub fn theme_shadow_visual(&self) -> Option<Rc<ThemeShadowVisual>> {
62 self.read_composition_extras(|extras| extras.theme_shadow.as_ref().and_then(|state| state.visual.clone()))
63 .flatten()
64 }
65
66 pub fn set_theme_shadow_delegated_to_child(&self, value: bool) {
70 self.with_composition_extras(|extras| {
71 extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).delegates_to_child = value;
72 });
73 self.theme_shadow_changed();
74 }
75
76 pub fn set_theme_shadow_popup(&self, popup: Option<&Visual>) {
80 self.with_composition_extras(|extras| {
81 extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).popup =
82 popup.map(|popup| popup.to_ref().downgrade());
83 });
84 self.theme_shadow_changed();
85 }
86
87 pub(super) fn on_shadow_changed(
91 &self,
92 old: Option<&Ref<crate::media::Shadow>>,
93 new: Option<&Ref<crate::media::Shadow>>,
94 ) {
95 if let Some(old) = old.and_then(|old| old.as_theme_shadow()) {
96 old.remove_owner(self);
97 }
98 if let Some(new) = new.and_then(|new| new.as_theme_shadow()) {
99 new.add_owner(self);
100 }
101 if new.is_some() {
102 self.with_composition_extras(|extras| {
103 extras.theme_shadow.get_or_insert_with(ThemeShadowState::default);
104 });
105 }
106 self.theme_shadow_changed();
107 }
108
109 fn theme_shadow_changed(&self) {
114 if self.theme_shadow_caster().is_none() {
115 self.release_theme_shadow_visual();
116 }
117 self.invalidate_visual();
118 if let (Some(parent), Some(source)) = (self.visual_parent(), self.presentation_source()) {
119 source.renderer().recalculate_children(&parent);
120 }
121 }
122
123 pub(super) fn ensure_theme_shadow_composition_visual(&self) -> Option<Rc<CompositionVisual>> {
128 let (shadow, elevation) = self.theme_shadow_caster()?;
129 let caster = self.composition_visual()?;
130 let existing = self.theme_shadow_visual().filter(|existing| {
131 Rc::ptr_eq(existing.visual().compositor(), caster.compositor()) && !existing.visual().is_disposed()
132 });
133 let shadow_visual = match existing {
134 Some(existing) => existing,
135 None => {
136 self.release_theme_shadow_visual();
137 let created = Rc::new(ThemeShadowVisual::new(&caster, &self.theme_shadow_recipe(&elevation)));
138 self.with_composition_extras(|extras| {
139 extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).visual = Some(created.clone());
140 });
141 created
142 }
143 };
144 self.configure_theme_shadow_visual(&shadow_visual, &shadow, &elevation);
145 Some(shadow_visual.visual().clone())
146 }
147
148 pub(super) fn update_theme_shadow_visual(&self) {
151 let Some(shadow_visual) = self.theme_shadow_visual() else { return };
152 match self.theme_shadow_caster() {
153 Some((shadow, elevation)) => self.configure_theme_shadow_visual(&shadow_visual, &shadow, &elevation),
154 None => shadow_visual.update(&DropShadowRecipe::new(0.0, ThemeShadowTheme::Light), Vector::default(), false),
155 }
156 }
157
158 pub(super) fn release_theme_shadow_visual(&self) {
161 let released = self
162 .with_composition_extras(|extras| extras.theme_shadow.as_mut().and_then(|state| state.visual.take()));
163 if let Some(released) = released {
164 released.dispose();
165 }
166 }
167
168 fn configure_theme_shadow_visual(
174 &self,
175 shadow_visual: &ThemeShadowVisual,
176 _shadow: &Ref<ThemeShadow>,
177 elevation: &Ref<Visual>,
178 ) {
179 let theme = ThemeShadowTheme::of(self);
180 let recipe = self.theme_shadow_recipe(elevation);
181 let size = self.bounds().size();
182 let visible = theme != ThemeShadowTheme::HighContrast
183 && self.composition_visual().is_some_and(|visual| visual.visible());
184 shadow_visual.update(&recipe, Vector::new(size.width, size.height), visible);
185 }
186
187 fn theme_shadow_recipe(&self, elevation: &Ref<Visual>) -> DropShadowRecipe {
190 let (radius_x, radius_y) = self.theme_shadow_corner_radius();
191 DropShadowRecipe::new(elevation.translation().z as f32, ThemeShadowTheme::of(self))
192 .with_corner_radius(radius_x, radius_y)
193 }
194
195 fn theme_shadow_corner_radius(&self) -> (f64, f64) {
200 let registry = FerroPropertyRegistry::instance();
201 let mut current = Some(self.to_ref());
202 while let Some(element) = current {
203 let object: &crate::FerroObject = &element;
204 if let (Some(radius_x), Some(radius_y)) =
205 (registry.find_registered_for(object, "RadiusX"), registry.find_registered_for(object, "RadiusY"))
206 {
207 let x = object.get_value_untyped(radius_x).downcast_ref::<f64>().copied().unwrap_or(0.0);
208 let y = object.get_value_untyped(radius_y).downcast_ref::<f64>().copied().unwrap_or(0.0);
209 if x != 0.0 || y != 0.0 {
210 return (x, y);
211 }
212 } else if let Some(property) = registry.find_registered_for(object, "CornerRadius") {
213 if let Some(radius) = object.get_value_untyped(property).downcast_ref::<CornerRadius>() {
214 let largest =
215 radius.top_left.max(radius.top_right).max(radius.bottom_left.max(radius.bottom_right));
216 if largest != 0.0 {
217 return (largest, largest);
218 }
219 }
220 }
221 current = element.visual_parent();
222 }
223 (0.0, 0.0)
224 }
225}