use super::{ThemeShadowState, Visual};
use crate::media::{DropShadowRecipe, ThemeShadow, ThemeShadowTheme};
use crate::rendering::composition::{CompositionVisual, ThemeShadowVisual};
use crate::{CornerRadius, FerroPropertyRegistry, Ref, Vector};
use std::rc::Rc;
impl Visual {
pub fn shadow(&self) -> Option<Ref<crate::media::Shadow>> {
self.get_value(Self::shadow_property())
}
pub fn set_shadow(&self, value: Option<Ref<crate::media::Shadow>>) {
self.set_value(Self::shadow_property(), value)
}
pub fn theme_shadow_caster(&self) -> Option<(Ref<ThemeShadow>, Ref<Visual>)> {
let (popup, delegates) = self
.read_composition_extras(|extras| {
extras.theme_shadow.as_ref().map(|state| (state.popup.clone(), state.delegates_to_child))
})
.flatten()?;
if delegates {
return None;
}
if let Some(popup) = popup.and_then(|popup| popup.upgrade()) {
if let Some(shadow) = popup.shadow().and_then(|shadow| shadow.downcast::<ThemeShadow>().ok()) {
return Some((shadow, popup));
}
}
let shadow = self.shadow()?.downcast::<ThemeShadow>().ok()?;
Some((shadow, self.to_ref()))
}
pub(super) fn may_cast_theme_shadow(&self) -> bool {
self.read_composition_extras(|extras| extras.theme_shadow.is_some()).unwrap_or(false)
}
pub fn theme_shadow_visual(&self) -> Option<Rc<ThemeShadowVisual>> {
self.read_composition_extras(|extras| extras.theme_shadow.as_ref().and_then(|state| state.visual.clone()))
.flatten()
}
pub fn set_theme_shadow_delegated_to_child(&self, value: bool) {
self.with_composition_extras(|extras| {
extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).delegates_to_child = value;
});
self.theme_shadow_changed();
}
pub fn set_theme_shadow_popup(&self, popup: Option<&Visual>) {
self.with_composition_extras(|extras| {
extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).popup =
popup.map(|popup| popup.to_ref().downgrade());
});
self.theme_shadow_changed();
}
pub(super) fn on_shadow_changed(
&self,
old: Option<&Ref<crate::media::Shadow>>,
new: Option<&Ref<crate::media::Shadow>>,
) {
if let Some(old) = old.and_then(|old| old.as_theme_shadow()) {
old.remove_owner(self);
}
if let Some(new) = new.and_then(|new| new.as_theme_shadow()) {
new.add_owner(self);
}
if new.is_some() {
self.with_composition_extras(|extras| {
extras.theme_shadow.get_or_insert_with(ThemeShadowState::default);
});
}
self.theme_shadow_changed();
}
fn theme_shadow_changed(&self) {
if self.theme_shadow_caster().is_none() {
self.release_theme_shadow_visual();
}
self.invalidate_visual();
if let (Some(parent), Some(source)) = (self.visual_parent(), self.presentation_source()) {
source.renderer().recalculate_children(&parent);
}
}
pub(super) fn ensure_theme_shadow_composition_visual(&self) -> Option<Rc<CompositionVisual>> {
let (shadow, elevation) = self.theme_shadow_caster()?;
let caster = self.composition_visual()?;
let existing = self.theme_shadow_visual().filter(|existing| {
Rc::ptr_eq(existing.visual().compositor(), caster.compositor()) && !existing.visual().is_disposed()
});
let shadow_visual = match existing {
Some(existing) => existing,
None => {
self.release_theme_shadow_visual();
let created = Rc::new(ThemeShadowVisual::new(&caster, &self.theme_shadow_recipe(&elevation)));
self.with_composition_extras(|extras| {
extras.theme_shadow.get_or_insert_with(ThemeShadowState::default).visual = Some(created.clone());
});
created
}
};
self.configure_theme_shadow_visual(&shadow_visual, &shadow, &elevation);
Some(shadow_visual.visual().clone())
}
pub(super) fn update_theme_shadow_visual(&self) {
let Some(shadow_visual) = self.theme_shadow_visual() else { return };
match self.theme_shadow_caster() {
Some((shadow, elevation)) => self.configure_theme_shadow_visual(&shadow_visual, &shadow, &elevation),
None => shadow_visual.update(&DropShadowRecipe::new(0.0, ThemeShadowTheme::Light), Vector::default(), false),
}
}
pub(super) fn release_theme_shadow_visual(&self) {
let released = self
.with_composition_extras(|extras| extras.theme_shadow.as_mut().and_then(|state| state.visual.take()));
if let Some(released) = released {
released.dispose();
}
}
fn configure_theme_shadow_visual(
&self,
shadow_visual: &ThemeShadowVisual,
_shadow: &Ref<ThemeShadow>,
elevation: &Ref<Visual>,
) {
let theme = ThemeShadowTheme::of(self);
let recipe = self.theme_shadow_recipe(elevation);
let size = self.bounds().size();
let visible = theme != ThemeShadowTheme::HighContrast
&& self.composition_visual().is_some_and(|visual| visual.visible());
shadow_visual.update(&recipe, Vector::new(size.width, size.height), visible);
}
fn theme_shadow_recipe(&self, elevation: &Ref<Visual>) -> DropShadowRecipe {
let (radius_x, radius_y) = self.theme_shadow_corner_radius();
DropShadowRecipe::new(elevation.translation().z as f32, ThemeShadowTheme::of(self))
.with_corner_radius(radius_x, radius_y)
}
fn theme_shadow_corner_radius(&self) -> (f64, f64) {
let registry = FerroPropertyRegistry::instance();
let mut current = Some(self.to_ref());
while let Some(element) = current {
let object: &crate::FerroObject = &element;
if let (Some(radius_x), Some(radius_y)) =
(registry.find_registered_for(object, "RadiusX"), registry.find_registered_for(object, "RadiusY"))
{
let x = object.get_value_untyped(radius_x).downcast_ref::<f64>().copied().unwrap_or(0.0);
let y = object.get_value_untyped(radius_y).downcast_ref::<f64>().copied().unwrap_or(0.0);
if x != 0.0 || y != 0.0 {
return (x, y);
}
} else if let Some(property) = registry.find_registered_for(object, "CornerRadius") {
if let Some(radius) = object.get_value_untyped(property).downcast_ref::<CornerRadius>() {
let largest =
radius.top_left.max(radius.top_right).max(radius.bottom_left.max(radius.bottom_right));
if largest != 0.0 {
return (largest, largest);
}
}
}
current = element.visual_parent();
}
(0.0, 0.0)
}
}