Skip to main content

ferroui_base/media/
gradient_brush.rs

1use crate::media::media_collection::track_item_property_changed;
2use crate::media::immutable::ImmutableGradientStop;
3use crate::media::{Brush, BrushImpl, BrushImplExt, GradientSpreadMethod, GradientStops, IImmutableBrush};
4use crate::reactive::IDisposable;
5use crate::rendering::composition::transport::BatchStreamWriter;
6use crate::rendering::composition::Compositor;
7use crate::{
8    ferro_class, ferro_property, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
9    StyledProperty,
10};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14/// Base class for brushes that draw with a gradient.
15#[repr(C)]
16pub struct GradientBrush {
17    base: Brush,
18    gradient_stops_collection_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
19    gradient_stops_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
20}
21
22ferro_class! {
23    GradientBrush: Brush, virtuals GradientBrushImpl: BrushImpl {
24        /// Creates an immutable clone of the brush.
25        fn to_immutable(this) -> Rc<dyn IImmutableBrush>;
26    }
27}
28
29impl FerroObjectImpl for GradientBrush {
30    fn constructed(this: &Self) {
31        Self::parent_constructed(this);
32        this.set_gradient_stops(GradientStops::new());
33    }
34
35    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
36        if change.property() == Self::gradient_stops_property().as_property() {
37            let (old_value, new_value) = change.get_old_and_new_value::<Option<GradientStops>>();
38
39            if old_value.is_some() {
40                if let Some(subscription) = this.gradient_stops_collection_subscription.take() {
41                    subscription.dispose();
42                }
43                if let Some(subscription) = this.gradient_stops_subscription.take() {
44                    subscription.dispose();
45                }
46            }
47
48            if let Some(new_value) = new_value {
49                let weak = this.to_ref().downgrade();
50                let collection_subscription = new_value.collection_changed({
51                    let weak = weak.clone();
52                    move |_| {
53                        if let Some(this) = weak.upgrade() {
54                            this.register_for_serialization();
55                            this.raise_invalidated();
56                        }
57                    }
58                });
59                *this.gradient_stops_collection_subscription.borrow_mut() = Some(collection_subscription);
60
61                let subscription = track_item_property_changed(&new_value, move || {
62                    if let Some(this) = weak.upgrade() {
63                        this.register_for_serialization();
64                        this.raise_invalidated();
65                    }
66                });
67                *this.gradient_stops_subscription.borrow_mut() = Some(subscription);
68            }
69        }
70
71        Self::parent_on_property_changed(this, change);
72    }
73}
74
75impl BrushImpl for GradientBrush {
76    fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
77        Self::parent_serialize_changes(this, c, writer);
78        writer.write(this.spread_method());
79        let gradient_stops = this.gradient_stops();
80        writer.write(gradient_stops.len() as i32);
81        for stop in gradient_stops.iter() {
82            // TODO: Technically it allocates, so it would be better to sync stops individually
83            writer.write_value(Some(ImmutableGradientStop::new(stop.offset(), stop.color())));
84        }
85    }
86}
87
88impl GradientBrushImpl for GradientBrush {
89    fn to_immutable(_this: &Self) -> Rc<dyn IImmutableBrush> {
90        panic!("GradientBrush is abstract: 'to_immutable' must be implemented by the deriving class")
91    }
92}
93
94crate::ferro_properties! { impl GradientBrush {
95    ferro_property!(pub fn spread_method_property() -> StyledProperty<GradientSpreadMethod> {
96        FerroProperty::register::<GradientBrush, _>("SpreadMethod", GradientSpreadMethod::Pad)
97    });
98
99    ferro_property!(pub fn gradient_stops_property() -> StyledProperty<Option<GradientStops>> {
100        FerroProperty::register::<GradientBrush, _>("GradientStops", None)
101    });
102} }
103
104impl GradientBrush {
105    /// Creates the class data.
106    pub fn construct() -> Self {
107        Self {
108            base: Brush::construct(),
109            gradient_stops_collection_subscription: RefCell::new(None),
110            gradient_stops_subscription: RefCell::new(None),
111        }
112    }
113
114    /// The brush's spread method that defines how to draw a gradient that
115    /// doesn't fill the bounds of the destination control.
116    pub fn spread_method(&self) -> GradientSpreadMethod {
117        self.get_value(Self::spread_method_property())
118    }
119
120    pub fn set_spread_method(&self, value: GradientSpreadMethod) {
121        self.set_value(Self::spread_method_property(), value)
122    }
123
124    /// The brush's gradient stops. Panics if the property has been cleared.
125    pub fn gradient_stops(&self) -> GradientStops {
126        self.get_value(Self::gradient_stops_property()).expect("GradientStops is not set")
127    }
128
129    pub fn set_gradient_stops(&self, value: GradientStops) {
130        self.set_value(Self::gradient_stops_property(), Some(value))
131    }
132}