Skip to main content

ferroui_base/media/
linear_gradient_brush.rs

1use crate::media::immutable::ImmutableLinearGradientBrush;
2use crate::media::{BrushImpl, BrushImplExt, GradientBrush, GradientBrushImpl, IImmutableBrush, ServerBrushFactory};
3use crate::rendering::composition::generated::ServerCompositionSimpleLinearGradientBrushProps;
4use crate::rendering::composition::server::ServerCompositionSimpleLinearGradientBrush;
5use crate::rendering::composition::transport::BatchStreamWriter;
6use crate::rendering::composition::Compositor;
7use crate::{
8    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty, Ref, RelativePoint,
9    StyledProperty,
10};
11use std::rc::Rc;
12
13/// A brush that draws with a linear gradient.
14#[repr(C)]
15pub struct LinearGradientBrush {
16    base: GradientBrush,
17}
18
19ferro_class!(LinearGradientBrush: GradientBrush);
20crate::ferro_class_info!(LinearGradientBrush { new: LinearGradientBrush::new });
21ferro_impl_classes!(LinearGradientBrush: FerroObjectImpl);
22
23impl BrushImpl for LinearGradientBrush {
24    fn factory(_this: &Self) -> Option<ServerBrushFactory> {
25        Some(|c| ServerCompositionSimpleLinearGradientBrush::new(c))
26    }
27
28    fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
29        Self::parent_serialize_changes(this, c, writer);
30        ServerCompositionSimpleLinearGradientBrushProps::serialize_all_changes(writer, this.start_point(), this.end_point());
31    }
32}
33
34impl GradientBrushImpl for LinearGradientBrush {
35    fn to_immutable(this: &Self) -> Rc<dyn IImmutableBrush> {
36        Rc::new(ImmutableLinearGradientBrush::from_brush(this))
37    }
38}
39
40crate::ferro_properties! { impl LinearGradientBrush {
41    ferro_property!(pub fn start_point_property() -> StyledProperty<RelativePoint> {
42        FerroProperty::register::<LinearGradientBrush, _>("StartPoint", RelativePoint::TOP_LEFT)
43    });
44
45    ferro_property!(pub fn end_point_property() -> StyledProperty<RelativePoint> {
46        FerroProperty::register::<LinearGradientBrush, _>("EndPoint", RelativePoint::BOTTOM_RIGHT)
47    });
48} }
49
50impl LinearGradientBrush {
51    /// Creates the class data.
52    pub fn construct() -> Self {
53        Self { base: GradientBrush::construct() }
54    }
55
56    pub fn new() -> Ref<Self> {
57        instantiate(Self::construct())
58    }
59
60    /// The start point for the gradient.
61    pub fn start_point(&self) -> RelativePoint {
62        self.get_value(Self::start_point_property())
63    }
64
65    pub fn set_start_point(&self, value: RelativePoint) {
66        self.set_value(Self::start_point_property(), value)
67    }
68
69    /// The end point for the gradient.
70    pub fn end_point(&self) -> RelativePoint {
71        self.get_value(Self::end_point_property())
72    }
73
74    pub fn set_end_point(&self, value: RelativePoint) {
75        self.set_value(Self::end_point_property(), value)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::media::{Colors, GradientSpreadMethod, GradientStop, GradientStops, IBrush};
83    use crate::RelativeUnit;
84    use std::cell::Cell;
85
86    fn assert_invalidated(target: &Ref<LinearGradientBrush>, action: impl FnOnce()) {
87        let raised = Rc::new(Cell::new(false));
88        let r = raised.clone();
89        let subscription = target.invalidated(move || r.set(true));
90        action();
91        assert!(raised.get());
92        subscription.dispose();
93    }
94
95    #[test]
96    fn changing_start_point_raises_invalidated() {
97        let target = LinearGradientBrush::new();
98        target.set_start_point(RelativePoint::default());
99        assert_invalidated(&target, || target.set_start_point(RelativePoint::new(10.0, 10.0, RelativeUnit::Absolute)));
100    }
101
102    #[test]
103    fn changing_end_point_raises_invalidated() {
104        let target = LinearGradientBrush::new();
105        target.set_end_point(RelativePoint::default());
106        assert_invalidated(&target, || target.set_end_point(RelativePoint::new(10.0, 10.0, RelativeUnit::Absolute)));
107    }
108
109    #[test]
110    fn changing_gradient_stops_raises_invalidated() {
111        let target = LinearGradientBrush::new();
112        target.set_gradient_stops(GradientStops::from_items([GradientStop::with_color_and_offset(Colors::RED, 0.0)]));
113        assert_invalidated(&target, || {
114            target
115                .set_gradient_stops(GradientStops::from_items([GradientStop::with_color_and_offset(Colors::GREEN, 0.0)]))
116        });
117    }
118
119    #[test]
120    fn adding_gradient_stop_raises_invalidated() {
121        let target = LinearGradientBrush::new();
122        target.set_gradient_stops(GradientStops::from_items([GradientStop::with_color_and_offset(Colors::RED, 0.0)]));
123        assert_invalidated(&target, || {
124            target.gradient_stops().add(GradientStop::with_color_and_offset(Colors::GREEN, 1.0))
125        });
126    }
127
128    #[test]
129    fn changing_gradient_stop_offset_raises_invalidated() {
130        let target = LinearGradientBrush::new();
131        target.set_gradient_stops(GradientStops::from_items([GradientStop::with_color_and_offset(Colors::RED, 0.0)]));
132        assert_invalidated(&target, || target.gradient_stops().get(0).set_offset(0.5));
133    }
134
135    #[test]
136    fn replaced_gradient_stops_no_longer_invalidate() {
137        let target = LinearGradientBrush::new();
138        let old = target.gradient_stops();
139        let stop = GradientStop::with_color_and_offset(Colors::RED, 0.0);
140        old.add(stop.clone());
141        target.set_gradient_stops(GradientStops::new());
142        let raised = Rc::new(Cell::new(false));
143        let r = raised.clone();
144        target.invalidated(move || r.set(true));
145        old.add(GradientStop::new());
146        stop.set_offset(1.0);
147        assert!(!raised.get());
148    }
149
150    #[test]
151    fn defaults_and_immutable_copy() {
152        let target = LinearGradientBrush::new();
153        assert_eq!(RelativePoint::TOP_LEFT, target.start_point());
154        assert_eq!(RelativePoint::BOTTOM_RIGHT, target.end_point());
155        target.gradient_stops().add(GradientStop::with_color_and_offset(Colors::RED, 2.0));
156        target.set_spread_method(GradientSpreadMethod::Reflect);
157        target.set_opacity(0.25);
158
159        let brush: Rc<dyn IBrush> = target.clone().into();
160        let linear = brush.as_linear_gradient_brush().unwrap();
161        assert_eq!(RelativePoint::BOTTOM_RIGHT, linear.end_point());
162        assert_eq!(1, linear.gradient_stops().len());
163        assert_eq!(2.0, linear.gradient_stops()[0].offset());
164
165        let immutable = crate::media::BrushExtensions::to_immutable(&brush);
166        let linear = immutable.as_linear_gradient_brush().unwrap();
167        assert_eq!(0.25, linear.opacity());
168        assert_eq!(GradientSpreadMethod::Reflect, linear.spread_method());
169        // Offsets are clamped to [0, 1] by the immutable stop.
170        assert_eq!(1.0, linear.gradient_stops()[0].offset());
171        assert_eq!(Colors::RED, linear.gradient_stops()[0].color());
172        assert!(immutable.as_mutable_brush().is_none());
173    }
174}