Skip to main content

ferroui_base/media/effects/
drop_shadow_effect.rs

1use crate::media::effects::{
2    Effect, IImmutableEffect, ImmutableDropShadowDirectionEffect, ImmutableDropShadowEffect,
3};
4use crate::media::{Color, Colors};
5use crate::{
6    ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroProperty, Ref, StyledProperty,
7};
8use std::f64::consts::PI;
9
10/// The properties shared by the drop shadow effects.
11#[repr(C)]
12pub struct DropShadowEffectBase {
13    base: Effect,
14}
15
16ferro_class!(DropShadowEffectBase: Effect);
17
18crate::ferro_impl_classes!(DropShadowEffectBase: FerroObjectImpl);
19
20crate::ferro_properties! { impl DropShadowEffectBase {
21    ferro_property!(pub fn blur_radius_property() -> StyledProperty<f64> {
22        FerroProperty::register::<DropShadowEffectBase, _>("BlurRadius", 5.0)
23    });
24
25    ferro_property!(pub fn color_property() -> StyledProperty<Color> {
26        FerroProperty::register::<DropShadowEffectBase, _>("Color", Colors::BLACK)
27    });
28
29    ferro_property!(pub fn opacity_property() -> StyledProperty<f64> {
30        FerroProperty::register::<DropShadowEffectBase, _>("Opacity", 1.0)
31    });
32} }
33
34impl DropShadowEffectBase {
35    fn static_constructor() {
36        Effect::affects_render::<DropShadowEffectBase>(&[
37            Self::blur_radius_property().as_property(),
38            Self::color_property().as_property(),
39            Self::opacity_property().as_property(),
40        ]);
41    }
42
43    /// Creates the class data.
44    pub fn construct() -> Self {
45        Self { base: Effect::construct() }
46    }
47
48    /// The blur radius of the shadow.
49    pub fn blur_radius(&self) -> f64 {
50        self.get_value(Self::blur_radius_property())
51    }
52
53    pub fn set_blur_radius(&self, value: f64) {
54        self.set_value(Self::blur_radius_property(), value)
55    }
56
57    /// The color of the shadow.
58    pub fn color(&self) -> Color {
59        self.get_value(Self::color_property())
60    }
61
62    pub fn set_color(&self, value: Color) {
63        self.set_value(Self::color_property(), value)
64    }
65
66    /// The opacity of the shadow.
67    pub fn opacity(&self) -> f64 {
68        self.get_value(Self::opacity_property())
69    }
70
71    pub fn set_opacity(&self, value: f64) {
72        self.set_value(Self::opacity_property(), value)
73    }
74}
75
76/// An effect that draws a shadow behind its content, positioned by an
77/// offset.
78#[repr(C)]
79pub struct DropShadowEffect {
80    base: DropShadowEffectBase,
81}
82
83ferro_class!(DropShadowEffect: DropShadowEffectBase);
84crate::ferro_class_info!(DropShadowEffect { new: DropShadowEffect::new });
85
86crate::ferro_impl_classes!(DropShadowEffect: FerroObjectImpl);
87
88crate::ferro_properties! { impl DropShadowEffect {
89    ferro_property!(pub fn offset_x_property() -> StyledProperty<f64> {
90        FerroProperty::register::<DropShadowEffect, _>("OffsetX", 3.5355)
91    });
92
93    ferro_property!(pub fn offset_y_property() -> StyledProperty<f64> {
94        FerroProperty::register::<DropShadowEffect, _>("OffsetY", 3.5355)
95    });
96} }
97
98impl DropShadowEffect {
99    fn static_constructor() {
100        Effect::affects_render::<DropShadowEffect>(&[
101            Self::offset_x_property().as_property(),
102            Self::offset_y_property().as_property(),
103        ]);
104    }
105
106    /// Creates the class data.
107    pub fn construct() -> Self {
108        Self { base: DropShadowEffectBase::construct() }
109    }
110
111    pub fn new() -> Ref<Self> {
112        instantiate(Self::construct())
113    }
114
115    /// The horizontal offset of the shadow.
116    pub fn offset_x(&self) -> f64 {
117        self.get_value(Self::offset_x_property())
118    }
119
120    pub fn set_offset_x(&self, value: f64) {
121        self.set_value(Self::offset_x_property(), value)
122    }
123
124    /// The vertical offset of the shadow.
125    pub fn offset_y(&self) -> f64 {
126        self.get_value(Self::offset_y_property())
127    }
128
129    pub fn set_offset_y(&self, value: f64) {
130        self.set_value(Self::offset_y_property(), value)
131    }
132
133    /// Creates an immutable clone of the effect.
134    pub fn to_immutable(&self) -> std::sync::Arc<dyn IImmutableEffect> {
135        std::sync::Arc::new(ImmutableDropShadowEffect::new(
136            self.offset_x(),
137            self.offset_y(),
138            self.blur_radius(),
139            self.color(),
140            self.opacity(),
141        ))
142    }
143}
144
145/// An effect that draws a shadow behind its content, positioned by a
146/// direction and a depth.
147#[repr(C)]
148pub struct DropShadowDirectionEffect {
149    base: DropShadowEffectBase,
150}
151
152ferro_class!(DropShadowDirectionEffect: DropShadowEffectBase);
153crate::ferro_class_info!(DropShadowDirectionEffect { new: DropShadowDirectionEffect::new });
154
155crate::ferro_impl_classes!(DropShadowDirectionEffect: FerroObjectImpl);
156
157crate::ferro_properties! { impl DropShadowDirectionEffect {
158    ferro_property!(pub fn shadow_depth_property() -> StyledProperty<f64> {
159        FerroProperty::register::<DropShadowDirectionEffect, _>("ShadowDepth", 5.0)
160    });
161
162    ferro_property!(pub fn direction_property() -> StyledProperty<f64> {
163        FerroProperty::register::<DropShadowDirectionEffect, _>("Direction", 315.0)
164    });
165} }
166
167impl DropShadowDirectionEffect {
168    fn static_constructor() {
169        Effect::affects_render::<DropShadowDirectionEffect>(&[
170            Self::shadow_depth_property().as_property(),
171            Self::direction_property().as_property(),
172        ]);
173    }
174
175    /// Creates the class data.
176    pub fn construct() -> Self {
177        Self { base: DropShadowEffectBase::construct() }
178    }
179
180    pub fn new() -> Ref<Self> {
181        instantiate(Self::construct())
182    }
183
184    /// The distance of the shadow from the content.
185    pub fn shadow_depth(&self) -> f64 {
186        self.get_value(Self::shadow_depth_property())
187    }
188
189    pub fn set_shadow_depth(&self, value: f64) {
190        self.set_value(Self::shadow_depth_property(), value)
191    }
192
193    /// The direction of the shadow, in degrees.
194    pub fn direction(&self) -> f64 {
195        self.get_value(Self::direction_property())
196    }
197
198    pub fn set_direction(&self, value: f64) {
199        self.set_value(Self::direction_property(), value)
200    }
201
202    /// The horizontal offset of the shadow.
203    pub fn offset_x(&self) -> f64 {
204        (self.direction() * PI / 180.0).cos() * self.shadow_depth()
205    }
206
207    /// The vertical offset of the shadow.
208    pub fn offset_y(&self) -> f64 {
209        (self.direction() * PI / 180.0).sin() * self.shadow_depth()
210    }
211
212    /// Creates an immutable clone of the effect.
213    ///
214    /// As in the reference implementation, the computed offsets are handed
215    /// to the immutable effect as its direction and depth.
216    pub fn to_immutable(&self) -> std::sync::Arc<dyn IImmutableEffect> {
217        std::sync::Arc::new(ImmutableDropShadowDirectionEffect::new(
218            self.offset_x(),
219            self.offset_y(),
220            self.blur_radius(),
221            self.color(),
222            self.opacity(),
223        ))
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use std::rc::Rc;
230    // Not from upstream: there are no upstream unit tests for these classes.
231    use super::*;
232    use std::cell::Cell;
233    use crate::media::effects::{BlurEffect, EffectExtensions, IEffect};
234
235    #[test]
236    fn changing_a_property_raises_invalidated() {
237        let count = Rc::new(Cell::new(0));
238
239        let blur = BlurEffect::new();
240        let c = count.clone();
241        let subscription = blur.invalidated(move || c.set(c.get() + 1));
242        blur.set_radius(10.0);
243        assert_eq!(1, count.get());
244        subscription.dispose();
245        blur.set_radius(11.0);
246        assert_eq!(1, count.get());
247
248        let shadow = DropShadowEffect::new();
249        let c = count.clone();
250        shadow.invalidated(move || c.set(c.get() + 1));
251        shadow.set_offset_x(1.0);
252        shadow.set_blur_radius(2.0);
253        shadow.set_color(Colors::RED);
254        assert_eq!(4, count.get());
255
256        let direction = DropShadowDirectionEffect::new();
257        let c = count.clone();
258        direction.invalidated(move || c.set(c.get() + 1));
259        direction.set_direction(0.0);
260        direction.set_opacity(0.5);
261        assert_eq!(6, count.get());
262    }
263
264    #[test]
265    fn defaults_interfaces_and_immutable_copies() {
266        let shadow = DropShadowEffect::new();
267        assert_eq!(3.5355, shadow.offset_x());
268        assert_eq!(5.0, shadow.blur_radius());
269        assert_eq!(Colors::BLACK, shadow.color());
270
271        let effect: Rc<dyn IEffect> = shadow.clone().into();
272        assert!(effect.as_blur_effect().is_none());
273        assert!(effect.as_direction_drop_shadow_effect().is_none());
274        assert_eq!(3.5355, effect.as_drop_shadow_effect().unwrap().offset_y());
275        assert!(effect.as_object().unwrap().is::<DropShadowEffect>());
276
277        let immutable = EffectExtensions::to_immutable(&effect);
278        assert!(immutable.equals(Some(&*effect)));
279        shadow.set_opacity(0.5);
280        assert!(!immutable.equals(Some(&*effect)));
281        assert!(!immutable.equals(None));
282        // An immutable effect converts to an equal one (a value in a handle
283        // of its own, where the original returns the same object).
284        let as_effect: Rc<dyn IEffect> = Rc::new(*immutable.as_any().downcast_ref::<ImmutableDropShadowEffect>().unwrap());
285        let same = EffectExtensions::to_immutable(&as_effect);
286        assert!(same.equals(Some(&*as_effect)));
287
288        let direction = DropShadowDirectionEffect::new();
289        direction.set_direction(0.0);
290        direction.set_shadow_depth(4.0);
291        assert_eq!(4.0, direction.offset_x());
292        assert_eq!(0.0, direction.offset_y());
293        let effect: Rc<dyn IEffect> = direction.into();
294        assert_eq!(0.0, effect.as_direction_drop_shadow_effect().unwrap().direction());
295
296        let blur: Rc<dyn IEffect> = BlurEffect::new().into();
297        assert_eq!(5.0, blur.as_blur_effect().unwrap().radius());
298        let other: Rc<dyn IEffect> = BlurEffect::new().into();
299        assert!(*blur != *other);
300        assert!(*blur == *blur.clone());
301    }
302}