ferroui_base/rendering/composition/
theme_shadow_visual.rs1use super::{
21 CompositionCustomVisual, CompositionCustomVisualHandler, CompositionVisual, ICompositionCustomVisualHandler,
22 ICompositionObjectAnimations,
23};
24use crate::media::{BoxShadow, BoxShadows, DropShadowRecipe, ImmediateDrawingContext};
25use crate::{Rect, RoundedRect, Vector};
26use std::any::Any;
27use std::cell::{Cell, RefCell};
28use std::rc::Rc;
29use std::sync::Arc;
30
31#[derive(Clone, Debug, PartialEq)]
34struct ShadowShape {
35 shadows: Vec<BoxShadow>,
36 radius_x: f64,
37 radius_y: f64,
38}
39
40impl ShadowShape {
41 fn of(recipe: &DropShadowRecipe) -> ShadowShape {
42 ShadowShape { shadows: recipe.box_shadows(), radius_x: recipe.radius_x, radius_y: recipe.radius_y }
43 }
44}
45
46pub struct ThemeShadowVisual {
50 visual: CompositionCustomVisual,
51 shape: RefCell<Option<ShadowShape>>,
53 size: Cell<Vector>,
54}
55
56impl ThemeShadowVisual {
57 pub fn new(caster: &Rc<CompositionVisual>, recipe: &DropShadowRecipe) -> ThemeShadowVisual {
66 let compositor = caster.compositor().clone();
67 let shape = ShadowShape::of(recipe);
68 let initial = shape.clone();
69 let visual = compositor.create_custom_visual(move || {
70 Rc::new(ThemeShadowHandler { base: CompositionCustomVisualHandler::default(), shape: RefCell::new(Some(initial)) })
71 as Rc<dyn ICompositionCustomVisualHandler>
72 });
73 visual.set_is_hit_test_visible(false);
74 visual.set_clip_to_bounds(false);
76 visual.set_parent_for_transform(Some(caster.clone()));
77 let opacity = compositor.create_expression_animation_with("caster.Opacity");
78 opacity.set_reference_parameter("caster", caster.clone());
79 visual.start_animation("Opacity", &*opacity);
80 ThemeShadowVisual { visual, shape: RefCell::new(Some(shape)), size: Cell::new(Vector::default()) }
81 }
82
83 pub fn visual(&self) -> &Rc<CompositionVisual> {
85 &self.visual
86 }
87
88 pub fn update(&self, recipe: &DropShadowRecipe, size: Vector, visible: bool) {
91 let visible = visible && recipe.is_visible() && size.x > 0.0 && size.y > 0.0;
92 self.visual.set_visible(visible);
93 if self.size.get() != size {
94 self.size.set(size);
95 self.visual.set_size(size);
96 }
97 let shape = ShadowShape::of(recipe);
98 if self.shape.borrow().as_ref() == Some(&shape) {
99 return;
100 }
101 *self.shape.borrow_mut() = Some(shape.clone());
102 self.visual.send_handler_message(Arc::new(shape));
103 }
104
105 pub fn box_shadows(&self) -> Vec<BoxShadow> {
107 self.shape.borrow().as_ref().map(|shape| shape.shadows.clone()).unwrap_or_default()
108 }
109
110 pub fn dispose(&self) {
112 self.visual.dispose();
113 }
114}
115
116#[derive(Default)]
118struct ThemeShadowHandler {
119 base: CompositionCustomVisualHandler,
120 shape: RefCell<Option<ShadowShape>>,
121}
122
123impl ThemeShadowHandler {
124 fn caster_rect(&self) -> Option<RoundedRect> {
128 let size = self.base.effective_size();
129 if size.x <= 2.0 || size.y <= 2.0 {
130 return None;
131 }
132 let shape = self.shape.borrow();
133 let shape = shape.as_ref()?;
134 let rect = Rect::new(1.0, 1.0, size.x - 2.0, size.y - 2.0);
135 Some(RoundedRect::from_radius_xy(rect, shape.radius_x, shape.radius_y))
136 }
137
138 fn box_shadows(&self) -> Option<BoxShadows> {
139 let shape = self.shape.borrow();
140 let shadows = &shape.as_ref()?.shadows;
141 let (first, rest) = shadows.split_first()?;
142 Some(BoxShadows::with_rest(*first, rest))
143 }
144}
145
146impl ICompositionCustomVisualHandler for ThemeShadowHandler {
147 fn handler_base(&self) -> &CompositionCustomVisualHandler {
148 &self.base
149 }
150
151 fn on_message(&self, message: Arc<dyn Any + Send + Sync>) {
152 if let Some(shape) = message.downcast_ref::<ShadowShape>() {
153 *self.shape.borrow_mut() = Some(shape.clone());
154 self.base.invalidate();
155 }
156 }
157
158 fn on_render(&self, drawing_context: &mut ImmediateDrawingContext<'_>) {
159 let (Some(rect), Some(shadows)) = (self.caster_rect(), self.box_shadows()) else { return };
160 drawing_context.platform_impl().draw_rectangle(None, None, rect, &shadows);
161 }
162
163 fn get_render_bounds(&self) -> Rect {
165 let size = self.base.effective_size();
166 let caster = Rect::new(0.0, 0.0, size.x, size.y);
167 match (self.caster_rect(), self.box_shadows()) {
168 (Some(rect), Some(shadows)) => caster.union(shadows.transform_bounds(rect.rect)),
169 _ => caster,
170 }
171 }
172}