Skip to main content

ferroui_base/media/
i_affects_render.rs

1use crate::media::effects::{Effect, IEffect};
2use crate::media::imaging::CroppedBitmap;
3use crate::media::{Brush, DrawingImage, Geometry, IBrush, IImage, IPen, ITransform, Pen, Transform};
4use crate::reactive::IDisposable;
5use crate::Ref;
6use std::any::{Any, TypeId};
7use std::rc::Rc;
8
9/// Signals that a change of the object requires everything drawn with it to
10/// be redrawn.
11///
12/// Implemented by the mutable media classes: brushes, pens, geometries,
13/// transforms, effects and the images that can change.
14pub trait IAffectsRender {
15    /// Subscribes to invalidation of the object. Disposing the returned
16    /// handle unsubscribes.
17    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable>;
18}
19
20impl IAffectsRender for Brush {
21    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
22        Brush::invalidated(self, move || handler())
23    }
24}
25
26impl IAffectsRender for Pen {
27    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
28        Pen::invalidated(self, move || handler())
29    }
30}
31
32impl IAffectsRender for Geometry {
33    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
34        self.changed(move || handler())
35    }
36}
37
38impl IAffectsRender for Transform {
39    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
40        self.changed(move || handler())
41    }
42}
43
44impl IAffectsRender for Effect {
45    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
46        Effect::invalidated(self, move || handler())
47    }
48}
49
50impl IAffectsRender for CroppedBitmap {
51    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
52        CroppedBitmap::invalidated(self, move || handler())
53    }
54}
55
56impl IAffectsRender for DrawingImage {
57    fn invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
58        DrawingImage::invalidated(self, move || handler())
59    }
60}
61
62/// Subscribes `handler` to the invalidation of a property value, when the
63/// value is a media object that can change.
64///
65/// `value` is the untyped value of a property as carried by a property change
66/// notification; the property types that can hold an [`IAffectsRender`] are
67/// recognised: optional brushes, pens, transforms, effects and images (as
68/// interface handles) and optional geometries and transforms (as class
69/// handles). Returns `None` when the value is of another type, is absent or
70/// is immutable.
71///
72/// This is the counterpart of testing a property value for the interface;
73/// it lets a visual subscribe uniformly to whatever its render-affecting
74/// properties hold.
75pub fn subscribe_invalidated(value: &dyn Any, handler: Rc<dyn Fn()>) -> Option<Rc<dyn IDisposable>> {
76    if let Some(value) = value.downcast_ref::<Option<Rc<dyn IBrush>>>() {
77        let brush = value.as_ref()?.as_object()?.downcast_ref::<Brush>()?;
78        return Some(IAffectsRender::invalidated(brush, handler));
79    }
80    if let Some(value) = value.downcast_ref::<Option<Rc<dyn IPen>>>() {
81        let pen = value.as_ref()?.as_object()?.downcast_ref::<Pen>()?;
82        return Some(IAffectsRender::invalidated(pen, handler));
83    }
84    if let Some(value) = value.downcast_ref::<Option<Rc<dyn ITransform>>>() {
85        let transform = value.as_ref()?.as_object()?.downcast_ref::<Transform>()?;
86        return Some(IAffectsRender::invalidated(transform, handler));
87    }
88    if let Some(value) = value.downcast_ref::<Option<Ref<Transform>>>() {
89        let transform: &Transform = value.as_ref()?;
90        return Some(IAffectsRender::invalidated(transform, handler));
91    }
92    if let Some(value) = value.downcast_ref::<Option<Ref<Geometry>>>() {
93        let geometry: &Geometry = value.as_ref()?;
94        return Some(IAffectsRender::invalidated(geometry, handler));
95    }
96    if let Some(value) = value.downcast_ref::<Option<Rc<dyn IEffect>>>() {
97        let effect = value.as_ref()?.as_object()?.downcast_ref::<Effect>()?;
98        return Some(IAffectsRender::invalidated(effect, handler));
99    }
100    if let Some(value) = value.downcast_ref::<Option<Rc<dyn IImage>>>() {
101        return Some(value.as_ref()?.as_affects_render()?.invalidated(handler));
102    }
103    None
104}
105
106/// Whether a property with values of the given type can hold a media object
107/// whose changes affect rendering, i.e. whether
108/// [`subscribe_invalidated`] can ever subscribe to one of its values.
109pub fn can_value_affect_render(property_type: TypeId) -> bool {
110    property_type == TypeId::of::<Option<Rc<dyn IBrush>>>()
111        || property_type == TypeId::of::<Option<Rc<dyn IPen>>>()
112        || property_type == TypeId::of::<Option<Rc<dyn ITransform>>>()
113        || property_type == TypeId::of::<Option<Ref<Transform>>>()
114        || property_type == TypeId::of::<Option<Ref<Geometry>>>()
115        || property_type == TypeId::of::<Option<Rc<dyn IEffect>>>()
116        || property_type == TypeId::of::<Option<Rc<dyn IImage>>>()
117}
118
119#[cfg(test)]
120mod tests {
121    // Not from upstream.
122    use super::*;
123    use crate::media::effects::{BlurEffect, ImmutableBlurEffect};
124    use crate::media::immutable::ImmutableSolidColorBrush;
125    use crate::media::{Colors, DrawingImage, RectangleGeometry, SolidColorBrush, TranslateTransform};
126    use crate::Rect;
127    use std::cell::Cell;
128
129    fn counting() -> (Rc<Cell<i32>>, Rc<dyn Fn()>) {
130        let count = Rc::new(Cell::new(0));
131        let c = count.clone();
132        (count, Rc::new(move || c.set(c.get() + 1)))
133    }
134
135    #[test]
136    fn subscribes_to_mutable_media_values() {
137        let (count, handler) = counting();
138
139        let brush = SolidColorBrush::new();
140        let value: Option<Rc<dyn IBrush>> = Some(brush.clone().into());
141        let subscription = subscribe_invalidated(&value, handler.clone()).unwrap();
142        brush.set_color(Colors::RED);
143        assert_eq!(1, count.get());
144        subscription.dispose();
145        brush.set_color(Colors::BLUE);
146        assert_eq!(1, count.get());
147
148        let pen = Pen::new();
149        let value: Option<Rc<dyn IPen>> = Some(pen.clone().into());
150        subscribe_invalidated(&value, handler.clone()).unwrap();
151        pen.set_thickness(3.0);
152        assert_eq!(2, count.get());
153
154        let transform = TranslateTransform::new();
155        let value: Option<Rc<dyn ITransform>> = Some(transform.clone().into());
156        subscribe_invalidated(&value, handler.clone()).unwrap();
157        let value: Option<Ref<Transform>> = Some(transform.clone().upcast());
158        subscribe_invalidated(&value, handler.clone()).unwrap();
159        transform.set_x(1.0);
160        assert_eq!(4, count.get());
161
162        let geometry = RectangleGeometry::new();
163        let value: Option<Ref<Geometry>> = Some(geometry.clone().upcast());
164        subscribe_invalidated(&value, handler.clone()).unwrap();
165        geometry.set_rect(Rect::new(0.0, 0.0, 1.0, 1.0));
166        assert_eq!(5, count.get());
167
168        let effect = BlurEffect::new();
169        let value: Option<Rc<dyn IEffect>> = Some(effect.clone().into());
170        subscribe_invalidated(&value, handler.clone()).unwrap();
171        effect.set_radius(1.0);
172        assert_eq!(6, count.get());
173
174        let image = DrawingImage::new();
175        let value: Option<Rc<dyn IImage>> = Some(image.clone().into());
176        subscribe_invalidated(&value, handler).unwrap();
177        image.set_viewbox(Some(Rect::new(0.0, 0.0, 1.0, 1.0)));
178        assert_eq!(7, count.get());
179    }
180
181    #[test]
182    fn ignores_immutable_absent_and_unrelated_values() {
183        let (_, handler) = counting();
184
185        let value: Option<Rc<dyn IBrush>> = Some(Rc::new(ImmutableSolidColorBrush::new(Colors::RED)));
186        assert!(subscribe_invalidated(&value, handler.clone()).is_none());
187        let value: Option<Rc<dyn IBrush>> = None;
188        assert!(subscribe_invalidated(&value, handler.clone()).is_none());
189        let value: Option<Rc<dyn IEffect>> = Some(Rc::new(ImmutableBlurEffect::new(1.0)));
190        assert!(subscribe_invalidated(&value, handler.clone()).is_none());
191        assert!(subscribe_invalidated(&1.0f64, handler.clone()).is_none());
192        assert!(subscribe_invalidated(&Rect::default(), handler).is_none());
193
194        assert!(can_value_affect_render(TypeId::of::<Option<Rc<dyn IBrush>>>()));
195        assert!(can_value_affect_render(TypeId::of::<Option<Ref<Geometry>>>()));
196        assert!(!can_value_affect_render(TypeId::of::<f64>()));
197        assert!(!can_value_affect_render(TypeId::of::<Rect>()));
198    }
199}