Skip to main content

ferroui_base/rendering/composition/brushes/
composition_brush.rs

1//! The composition brushes: brushes that live on a compositor and whose
2//! properties can be animated.
3//!
4//! Upstream these are the classes `CompositionBrush`,
5//! `CompositionSolidColorBrush`, `CompositionGradientBrush` and the
6//! linear, radial and conic gradient brushes, generated from the schema and
7//! completed by hand. Here every composition brush is one
8//! [`CompositionBrush`] behind an `Rc`, which holds what the concrete class
9//! adds in a [`CompositionBrushKind`]; the concrete classes are typed
10//! handles around the same `Rc` ([`CompositionSolidColorBrush`],
11//! [`CompositionLinearGradientBrush`], ...), as for the composition visuals.
12
13use super::{
14    ServerCompositionConicGradientBrush, ServerCompositionLinearGradientBrush, ServerCompositionRadialGradientBrush,
15    ServerCompositionSolidColorBrush,
16};
17use crate::collections::FerroList;
18use crate::media::immutable::ImmutableGradientStop;
19use crate::media::{
20    Color, GradientSpreadMethod, IBrush, IConicGradientBrush, IGradientBrush, IGradientStop, ILinearGradientBrush,
21    IRadialGradientBrush, ISolidColorBrush, ITransform,
22};
23use crate::rendering::composition::animations::{
24    ICompositionAnimation, ICompositionAnimationBase, ImplicitAnimationCollection,
25};
26use crate::rendering::composition::drawing::transform_get_server;
27use crate::rendering::composition::expressions::ExpressionVariant;
28use crate::rendering::composition::generated::{
29    CompositionBrushHooks, CompositionBrushProps, CompositionConicGradientBrushHooks,
30    CompositionConicGradientBrushProps, CompositionLinearGradientBrushHooks, CompositionLinearGradientBrushProps,
31    CompositionRadialGradientBrushHooks, CompositionRadialGradientBrushProps, CompositionSolidColorBrushHooks,
32    CompositionSolidColorBrushProps, ServerCompositionBrushProps, ServerCompositionConicGradientBrushProps,
33    ServerCompositionLinearGradientBrushProps, ServerCompositionRadialGradientBrushProps,
34    ServerCompositionSolidColorBrushProps,
35};
36use crate::rendering::composition::server::{CompositionProperty, IServerObject, ServerCompositor, ServerObjectId};
37use crate::rendering::composition::transport::{BatchResource, BatchStreamWriter, IRegisterForSerialization};
38use crate::rendering::composition::{
39    AsCompositionObject, CompositionObject, Compositor, ICompositionObject, ICompositionObjectAnimations,
40    ICompositionObjectHost, ICompositorSerializable, PendingAnimations,
41};
42use crate::{RelativePoint, RelativeScalar};
43use std::any::Any;
44use std::cell::{Cell, RefCell};
45use std::ops::Deref;
46use std::rc::{Rc, Weak};
47
48/// What the hand-written `CompositionGradientBrush` adds to the generated
49/// `CompositionBrush`: the stops and the spread method.
50pub(crate) struct CompositionGradientBrushData {
51    brush: CompositionBrushProps,
52    gradient_stops: RefCell<FerroList<Rc<dyn IGradientStop>>>,
53    spread_method: Cell<GradientSpreadMethod>,
54}
55
56impl CompositionGradientBrushData {
57    fn new() -> Self {
58        Self {
59            brush: CompositionBrushProps::new(),
60            gradient_stops: RefCell::new(FerroList::new()),
61            spread_method: Cell::new(GradientSpreadMethod::default()),
62        }
63    }
64
65    /// `SerializeChangesCore` of `CompositionGradientBrush`: the generated
66    /// base class, then the spread method and the stops.
67    fn serialize_changes_core(&self, host: &dyn CompositionBrushHooks, writer: &mut BatchStreamWriter<'_>) {
68        self.brush.serialize_changes_core(host, writer);
69        writer.write(self.spread_method.get());
70        let stops = self.gradient_stops.borrow().clone();
71        writer.write(stops.count() as i32);
72        for stop in stops.to_vec() {
73            let item: BatchResource<dyn IGradientStop> = match stop.as_composition_gradient_stop() {
74                Some(comp) => BatchResource::Server(comp.server()),
75                // A mutable UI-thread stop must not cross to the render thread
76                // by reference; ship its current values instead.
77                None if stop.is_immutable_gradient_stop() => BatchResource::Value(stop.clone()),
78                None => BatchResource::Value(Rc::new(ImmutableGradientStop::new(stop.offset(), stop.color()))),
79            };
80            writer.write_resource(Some(item));
81        }
82    }
83}
84
85/// What the concrete class of a composition brush adds to
86/// [`CompositionBrush`].
87pub(crate) enum CompositionBrushKind {
88    SolidColor(CompositionSolidColorBrushProps),
89    LinearGradient(CompositionGradientBrushData, CompositionLinearGradientBrushProps),
90    RadialGradient(CompositionGradientBrushData, CompositionRadialGradientBrushProps),
91    ConicGradient(CompositionGradientBrushData, CompositionConicGradientBrushProps),
92}
93
94/// A brush of the composition API.
95pub struct CompositionBrush {
96    this: Weak<CompositionBrush>,
97    object: CompositionObject,
98    kind: CompositionBrushKind,
99    transform: RefCell<Option<Rc<dyn ITransform>>>,
100    relative_transform: RefCell<Option<Rc<dyn ITransform>>>,
101}
102
103impl CompositionBrush {
104    fn create(
105        compositor: &Rc<Compositor>,
106        kind: CompositionBrushKind,
107        server: impl FnOnce(&Rc<ServerCompositor>) -> Rc<dyn IServerObject> + Send + 'static,
108    ) -> Rc<CompositionBrush> {
109        let server = compositor.create_server_object(move |compositor, _| server(compositor));
110        Rc::new_cyclic(|this: &Weak<CompositionBrush>| CompositionBrush {
111            this: this.clone(),
112            object: CompositionObject::new(compositor, Some(server)),
113            kind,
114            transform: RefCell::new(None),
115            relative_transform: RefCell::new(None),
116        })
117    }
118
119    /// The constructor chain of the gradient brushes: `CompositionBrush`,
120    /// then the class.
121    fn initialize_defaults(&self) {
122        match &self.kind {
123            CompositionBrushKind::SolidColor(props) => props.initialize_defaults(self),
124            CompositionBrushKind::LinearGradient(data, props) => {
125                data.brush.initialize_defaults(self);
126                props.initialize_defaults(self);
127            }
128            CompositionBrushKind::RadialGradient(data, props) => {
129                data.brush.initialize_defaults(self);
130                props.initialize_defaults(self);
131            }
132            CompositionBrushKind::ConicGradient(data, props) => {
133                data.brush.initialize_defaults(self);
134                props.initialize_defaults(self);
135            }
136        }
137    }
138
139    /// The generated properties of `CompositionBrush`.
140    pub fn brush_props(&self) -> &CompositionBrushProps {
141        match &self.kind {
142            CompositionBrushKind::SolidColor(props) => props.base(),
143            CompositionBrushKind::LinearGradient(data, _)
144            | CompositionBrushKind::RadialGradient(data, _)
145            | CompositionBrushKind::ConicGradient(data, _) => &data.brush,
146        }
147    }
148
149    fn gradient_data(&self) -> Option<&CompositionGradientBrushData> {
150        match &self.kind {
151            CompositionBrushKind::SolidColor(_) => None,
152            CompositionBrushKind::LinearGradient(data, _)
153            | CompositionBrushKind::RadialGradient(data, _)
154            | CompositionBrushKind::ConicGradient(data, _) => Some(data),
155        }
156    }
157
158    pub fn compositor(&self) -> &Rc<Compositor> {
159        self.object.compositor()
160    }
161
162    /// The embedded `CompositionObject`.
163    pub fn object(&self) -> &CompositionObject {
164        &self.object
165    }
166
167    /// The id of the server-side brush.
168    pub fn server(&self) -> ServerObjectId {
169        self.object.required_server()
170    }
171
172    pub fn is_disposed(&self) -> bool {
173        self.object.is_disposed()
174    }
175
176    /// The name of the concrete class, for messages.
177    pub fn type_name(&self) -> &'static str {
178        match &self.kind {
179            CompositionBrushKind::SolidColor(_) => "CompositionSolidColorBrush",
180            CompositionBrushKind::LinearGradient(..) => "CompositionLinearGradientBrush",
181            CompositionBrushKind::RadialGradient(..) => "CompositionRadialGradientBrush",
182            CompositionBrushKind::ConicGradient(..) => "CompositionConicGradientBrush",
183        }
184    }
185
186    /// The handle of the brush, from a reference to it (an `IBrush` seen as
187    /// a `CompositionBrush` through `IBrush::as_composition_brush`).
188    pub fn to_rc(&self) -> Rc<CompositionBrush> {
189        self.this.upgrade().expect("the brush is alive while it is used")
190    }
191
192    /// The brush as an `IBrush` handle.
193    pub fn as_brush(&self) -> Rc<dyn IBrush> {
194        self.this.upgrade().expect("the brush is alive while it is used") as Rc<dyn IBrush>
195    }
196
197    /// The collection of implicit animations attached to this object.
198    pub fn implicit_animations(&self) -> Option<Rc<ImplicitAnimationCollection>> {
199        self.object.implicit_animations()
200    }
201
202    pub fn set_implicit_animations(&self, value: Option<Rc<ImplicitAnimationCollection>>) {
203        self.object.set_implicit_animations(value)
204    }
205
206    pub fn opacity(&self) -> f64 {
207        self.brush_props().opacity()
208    }
209
210    pub fn set_opacity(&self, value: f64) {
211        self.brush_props().set_opacity(self, value)
212    }
213
214    pub fn transform_origin(&self) -> RelativePoint {
215        self.brush_props().transform_origin()
216    }
217
218    pub fn set_transform_origin(&self, value: RelativePoint) {
219        self.brush_props().set_transform_origin(self, value)
220    }
221
222    pub fn transform(&self) -> Option<Rc<dyn ITransform>> {
223        self.transform.borrow().clone()
224    }
225
226    /// Sets the transform. What the server receives is the form a
227    /// transform takes on a compositor (`GetServer`): an immutable
228    /// transform as it is, a mutable one as a copy of its current value.
229    pub fn set_transform(&self, value: Option<Rc<dyn ITransform>>) {
230        let transport = transform_get_server(value.as_ref(), Some(self.compositor()));
231        *self.transform.borrow_mut() = value;
232        self.brush_props().set_transform(self, transport)
233    }
234
235    pub fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
236        self.relative_transform.borrow().clone()
237    }
238
239    /// Sets the relative transform; see [`set_transform`](Self::set_transform).
240    pub fn set_relative_transform(&self, value: Option<Rc<dyn ITransform>>) {
241        let transport = transform_get_server(value.as_ref(), Some(self.compositor()));
242        *self.relative_transform.borrow_mut() = value;
243        self.brush_props().set_relative_transform(self, transport)
244    }
245
246    fn serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
247        match &self.kind {
248            CompositionBrushKind::SolidColor(props) => props.serialize_changes_core(self, writer),
249            CompositionBrushKind::LinearGradient(_, props) => props.serialize_changes_core(self, writer),
250            CompositionBrushKind::RadialGradient(_, props) => props.serialize_changes_core(self, writer),
251            CompositionBrushKind::ConicGradient(_, props) => props.serialize_changes_core(self, writer),
252        }
253    }
254
255    /// The `base.SerializeChangesCore` of the gradient brushes.
256    fn serialize_gradient_base(&self, writer: &mut BatchStreamWriter<'_>) {
257        if let Some(data) = self.gradient_data() {
258            data.serialize_changes_core(self, writer);
259        }
260    }
261}
262
263/// `InitializeDefaultsExtra` activates the server object: done when the
264/// server object is created (see the constructors of the server brushes).
265impl CompositionBrushHooks for CompositionBrush {}
266
267impl CompositionSolidColorBrushHooks for CompositionBrush {}
268
269impl CompositionLinearGradientBrushHooks for CompositionBrush {
270    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
271        self.serialize_gradient_base(writer)
272    }
273}
274
275impl CompositionRadialGradientBrushHooks for CompositionBrush {
276    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
277        self.serialize_gradient_base(writer)
278    }
279}
280
281impl CompositionConicGradientBrushHooks for CompositionBrush {
282    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
283        self.serialize_gradient_base(writer)
284    }
285}
286
287impl IRegisterForSerialization for CompositionBrush {
288    fn register_for_serialization(&self) {
289        self.object
290            .register_for_serialization(|| self.this.upgrade().map(|this| this as Rc<dyn ICompositorSerializable>));
291    }
292}
293
294impl ICompositionObjectHost for CompositionBrush {
295    fn server(&self) -> ServerObjectId {
296        self.object.required_server()
297    }
298
299    fn pending_animations(&self) -> &PendingAnimations {
300        self.object.pending_animations()
301    }
302
303    fn implicit_animation(&self, property_name: &str) -> Option<Rc<dyn ICompositionAnimationBase>> {
304        self.object.implicit_animation(property_name)
305    }
306
307    fn start_animation_group(
308        &self,
309        grp: &Rc<dyn ICompositionAnimationBase>,
310        target: &str,
311        final_value: ExpressionVariant,
312    ) -> bool {
313        self.start_animation_group_for(&**grp, target, final_value)
314    }
315}
316
317impl ICompositionObjectAnimations for CompositionBrush {
318    fn try_start_animation(
319        &self,
320        property_name: &str,
321        animation: &dyn ICompositionAnimation,
322        final_value: Option<ExpressionVariant>,
323    ) -> bool {
324        // The gradient brushes have a hand-written base class, which the
325        // generated code does not chain to: the properties of
326        // `CompositionBrush` are tried after those of the class.
327        match &self.kind {
328            CompositionBrushKind::SolidColor(props) => props.start_animation(self, property_name, animation, final_value),
329            CompositionBrushKind::LinearGradient(data, props) => {
330                props.start_animation(self, property_name, animation, final_value)
331                    || data.brush.start_animation(self, property_name, animation, final_value)
332            }
333            CompositionBrushKind::RadialGradient(data, props) => {
334                props.start_animation(self, property_name, animation, final_value)
335                    || data.brush.start_animation(self, property_name, animation, final_value)
336            }
337            CompositionBrushKind::ConicGradient(data, props) => {
338                props.start_animation(self, property_name, animation, final_value)
339                    || data.brush.start_animation(self, property_name, animation, final_value)
340            }
341        }
342    }
343
344    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
345        match &self.kind {
346            CompositionBrushKind::SolidColor(_) => {
347                ServerCompositionSolidColorBrushProps::get_composition_property(property_name)
348            }
349            CompositionBrushKind::LinearGradient(..) => {
350                ServerCompositionLinearGradientBrushProps::get_composition_property(property_name)
351            }
352            CompositionBrushKind::RadialGradient(..) => {
353                ServerCompositionRadialGradientBrushProps::get_composition_property(property_name)
354            }
355            CompositionBrushKind::ConicGradient(..) => {
356                ServerCompositionConicGradientBrushProps::get_composition_property(property_name)
357            }
358        }
359        .or_else(|| ServerCompositionBrushProps::get_composition_property(property_name))
360    }
361
362    fn composition_object(&self) -> &CompositionObject {
363        &self.object
364    }
365}
366
367impl AsCompositionObject for CompositionBrush {
368    fn as_composition_object(&self) -> &CompositionObject {
369        &self.object
370    }
371
372    fn composition_type_name(&self) -> &'static str {
373        self.type_name()
374    }
375}
376
377impl ICompositionObject for CompositionBrush {
378    fn server(&self) -> ServerObjectId {
379        self.object.required_server()
380    }
381
382    fn as_any(&self) -> &dyn Any {
383        self
384    }
385
386    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
387        self
388    }
389}
390
391impl ICompositorSerializable for CompositionBrush {
392    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
393        self.object.try_get_server(c)
394    }
395
396    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
397        self.object.begin_serialize_changes(c);
398        self.serialize_changes_core(writer);
399    }
400}
401
402// --- IBrush --------------------------------------------------------------------
403//
404// The brush is drawn as itself where there is no compositor, and through its
405// server object on its own compositor. The members of the interfaces of the
406// concrete classes are answered only by the matching kind (the `as_*`
407// members of `IBrush` select it).
408
409impl IBrush for CompositionBrush {
410    fn opacity(&self) -> f64 {
411        CompositionBrush::opacity(self)
412    }
413
414    fn transform(&self) -> Option<Rc<dyn ITransform>> {
415        CompositionBrush::transform(self)
416    }
417
418    fn transform_origin(&self) -> RelativePoint {
419        CompositionBrush::transform_origin(self)
420    }
421
422    fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
423        CompositionBrush::relative_transform(self)
424    }
425
426    fn as_any(&self) -> &dyn Any {
427        self
428    }
429
430    fn as_solid_color_brush(&self) -> Option<&dyn ISolidColorBrush> {
431        matches!(self.kind, CompositionBrushKind::SolidColor(_)).then_some(self as &dyn ISolidColorBrush)
432    }
433
434    fn as_gradient_brush(&self) -> Option<&dyn IGradientBrush> {
435        self.gradient_data().map(|_| self as &dyn IGradientBrush)
436    }
437
438    fn as_linear_gradient_brush(&self) -> Option<&dyn ILinearGradientBrush> {
439        matches!(self.kind, CompositionBrushKind::LinearGradient(..)).then_some(self as &dyn ILinearGradientBrush)
440    }
441
442    fn as_radial_gradient_brush(&self) -> Option<&dyn IRadialGradientBrush> {
443        matches!(self.kind, CompositionBrushKind::RadialGradient(..)).then_some(self as &dyn IRadialGradientBrush)
444    }
445
446    fn as_conic_gradient_brush(&self) -> Option<&dyn IConicGradientBrush> {
447        matches!(self.kind, CompositionBrushKind::ConicGradient(..)).then_some(self as &dyn IConicGradientBrush)
448    }
449
450    fn as_composition_brush(&self) -> Option<&CompositionBrush> {
451        Some(self)
452    }
453}
454
455fn not_of_kind(member: &str) -> ! {
456    panic!("{member} is not a member of this composition brush");
457}
458
459impl ISolidColorBrush for CompositionBrush {
460    fn color(&self) -> Color {
461        match &self.kind {
462            CompositionBrushKind::SolidColor(props) => props.color(),
463            _ => not_of_kind("Color"),
464        }
465    }
466}
467
468impl IGradientBrush for CompositionBrush {
469    fn gradient_stops(&self) -> Vec<Rc<dyn IGradientStop>> {
470        match self.gradient_data() {
471            Some(data) => data.gradient_stops.borrow().to_vec(),
472            None => not_of_kind("GradientStops"),
473        }
474    }
475
476    fn spread_method(&self) -> GradientSpreadMethod {
477        match self.gradient_data() {
478            Some(data) => data.spread_method.get(),
479            None => not_of_kind("SpreadMethod"),
480        }
481    }
482}
483
484impl ILinearGradientBrush for CompositionBrush {
485    fn start_point(&self) -> RelativePoint {
486        match &self.kind {
487            CompositionBrushKind::LinearGradient(_, props) => props.start_point(),
488            _ => not_of_kind("StartPoint"),
489        }
490    }
491
492    fn end_point(&self) -> RelativePoint {
493        match &self.kind {
494            CompositionBrushKind::LinearGradient(_, props) => props.end_point(),
495            _ => not_of_kind("EndPoint"),
496        }
497    }
498}
499
500impl IRadialGradientBrush for CompositionBrush {
501    fn center(&self) -> RelativePoint {
502        match &self.kind {
503            CompositionBrushKind::RadialGradient(_, props) => props.center(),
504            _ => not_of_kind("Center"),
505        }
506    }
507
508    fn gradient_origin(&self) -> RelativePoint {
509        match &self.kind {
510            CompositionBrushKind::RadialGradient(_, props) => props.gradient_origin(),
511            _ => not_of_kind("GradientOrigin"),
512        }
513    }
514
515    fn radius_x(&self) -> RelativeScalar {
516        match &self.kind {
517            CompositionBrushKind::RadialGradient(_, props) => props.radius_x(),
518            _ => not_of_kind("RadiusX"),
519        }
520    }
521
522    fn radius_y(&self) -> RelativeScalar {
523        match &self.kind {
524            CompositionBrushKind::RadialGradient(_, props) => props.radius_y(),
525            _ => not_of_kind("RadiusY"),
526        }
527    }
528}
529
530impl IConicGradientBrush for CompositionBrush {
531    fn center(&self) -> RelativePoint {
532        match &self.kind {
533            CompositionBrushKind::ConicGradient(_, props) => props.center(),
534            _ => not_of_kind("Center"),
535        }
536    }
537
538    fn angle(&self) -> f64 {
539        match &self.kind {
540            CompositionBrushKind::ConicGradient(_, props) => props.angle(),
541            _ => not_of_kind("Angle"),
542        }
543    }
544}
545
546// --- the concrete classes ------------------------------------------------------
547
548/// A composition brush that paints with a color.
549#[derive(Clone)]
550pub struct CompositionSolidColorBrush(Rc<CompositionBrush>);
551
552impl Deref for CompositionSolidColorBrush {
553    type Target = Rc<CompositionBrush>;
554
555    fn deref(&self) -> &Rc<CompositionBrush> {
556        &self.0
557    }
558}
559
560impl CompositionSolidColorBrush {
561    fn create(compositor: &Rc<Compositor>) -> Rc<CompositionBrush> {
562        CompositionBrush::create(
563            compositor,
564            CompositionBrushKind::SolidColor(CompositionSolidColorBrushProps::new()),
565            |c| ServerCompositionSolidColorBrush::new(c),
566        )
567    }
568
569    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionSolidColorBrush {
570        let brush = Self::create(compositor);
571        brush.initialize_defaults();
572        CompositionSolidColorBrush(brush)
573    }
574
575    pub(crate) fn with_color(compositor: &Rc<Compositor>, color: Color) -> CompositionSolidColorBrush {
576        let brush = Self::create(compositor);
577        let this = CompositionSolidColorBrush(brush);
578        // The constructor of the base class, then the color, then the
579        // defaults of the class.
580        this.props().base().initialize_defaults(&*this.0);
581        this.set_color(color);
582        this.props().initialize_own_defaults(&*this.0);
583        this
584    }
585
586    /// The handle of `brush` as a solid color brush, if it is one.
587    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionSolidColorBrush> {
588        matches!(brush.kind, CompositionBrushKind::SolidColor(_)).then(|| CompositionSolidColorBrush(brush.clone()))
589    }
590
591    fn props(&self) -> &CompositionSolidColorBrushProps {
592        match &self.0.kind {
593            CompositionBrushKind::SolidColor(props) => props,
594            _ => unreachable!("a solid color brush handle wraps a solid color brush"),
595        }
596    }
597
598    pub fn color(&self) -> Color {
599        self.props().color()
600    }
601
602    pub fn set_color(&self, value: Color) {
603        self.props().set_color(&*self.0, value)
604    }
605}
606
607/// The base of the composition gradient brushes: the gradient stops and the
608/// spread method.
609#[derive(Clone)]
610pub struct CompositionGradientBrush(Rc<CompositionBrush>);
611
612impl Deref for CompositionGradientBrush {
613    type Target = Rc<CompositionBrush>;
614
615    fn deref(&self) -> &Rc<CompositionBrush> {
616        &self.0
617    }
618}
619
620impl CompositionGradientBrush {
621    /// The handle of `brush` as a gradient brush, if it is one.
622    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionGradientBrush> {
623        brush.gradient_data().map(|_| CompositionGradientBrush(brush.clone()))
624    }
625
626    fn data(&self) -> &CompositionGradientBrushData {
627        match self.0.gradient_data() {
628            Some(data) => data,
629            None => unreachable!("a gradient brush handle wraps a gradient brush"),
630        }
631    }
632
633    /// The gradient stops. Mutations of the list itself are not tracked - assign
634    /// the property to ship in-place edits made after a commit. Stops created via
635    /// [`Compositor::create_gradient_stop_with`] stay live
636    /// on the server and animate individually; other stops are snapshotted at
637    /// serialization time.
638    pub fn gradient_stops(&self) -> FerroList<Rc<dyn IGradientStop>> {
639        self.data().gradient_stops.borrow().clone()
640    }
641
642    pub fn set_gradient_stops(&self, value: FerroList<Rc<dyn IGradientStop>>) {
643        if self.data().gradient_stops.borrow().ptr_eq(&value) {
644            return;
645        }
646        *self.data().gradient_stops.borrow_mut() = value;
647        self.0.register_for_serialization();
648    }
649
650    /// How the gradient repeats outside the stop range.
651    pub fn spread_method(&self) -> GradientSpreadMethod {
652        self.data().spread_method.get()
653    }
654
655    pub fn set_spread_method(&self, value: GradientSpreadMethod) {
656        if self.data().spread_method.get() == value {
657            return;
658        }
659        self.data().spread_method.set(value);
660        self.0.register_for_serialization();
661    }
662}
663
664macro_rules! gradient_brush_class {
665    ($(#[$meta:meta])* $name:ident, $variant:ident, $props:ident, $server:ident) => {
666        $(#[$meta])*
667        #[derive(Clone)]
668        pub struct $name(CompositionGradientBrush);
669
670        impl Deref for $name {
671            type Target = CompositionGradientBrush;
672
673            fn deref(&self) -> &CompositionGradientBrush {
674                &self.0
675            }
676        }
677
678        impl $name {
679            pub(crate) fn new(compositor: &Rc<Compositor>) -> $name {
680                let brush = CompositionBrush::create(
681                    compositor,
682                    CompositionBrushKind::$variant(CompositionGradientBrushData::new(), $props::new()),
683                    |c| $server::new(c),
684                );
685                brush.initialize_defaults();
686                $name(CompositionGradientBrush(brush))
687            }
688
689            /// The handle of `brush` as this class, if it is one.
690            pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<$name> {
691                matches!(brush.kind, CompositionBrushKind::$variant(..))
692                    .then(|| $name(CompositionGradientBrush(brush.clone())))
693            }
694
695            fn props(&self) -> &$props {
696                match &self.0 .0.kind {
697                    CompositionBrushKind::$variant(_, props) => props,
698                    _ => unreachable!("the handle wraps a brush of its class"),
699                }
700            }
701
702            fn host(&self) -> &CompositionBrush {
703                &self.0 .0
704            }
705        }
706    };
707}
708
709gradient_brush_class!(
710    /// A composition brush that paints with a linear gradient.
711    CompositionLinearGradientBrush,
712    LinearGradient,
713    CompositionLinearGradientBrushProps,
714    ServerCompositionLinearGradientBrush
715);
716
717impl CompositionLinearGradientBrush {
718    pub fn start_point(&self) -> RelativePoint {
719        self.props().start_point()
720    }
721
722    pub fn set_start_point(&self, value: RelativePoint) {
723        self.props().set_start_point(self.host(), value)
724    }
725
726    pub fn end_point(&self) -> RelativePoint {
727        self.props().end_point()
728    }
729
730    pub fn set_end_point(&self, value: RelativePoint) {
731        self.props().set_end_point(self.host(), value)
732    }
733}
734
735gradient_brush_class!(
736    /// A composition brush that paints with a radial gradient.
737    CompositionRadialGradientBrush,
738    RadialGradient,
739    CompositionRadialGradientBrushProps,
740    ServerCompositionRadialGradientBrush
741);
742
743impl CompositionRadialGradientBrush {
744    pub fn center(&self) -> RelativePoint {
745        self.props().center()
746    }
747
748    pub fn set_center(&self, value: RelativePoint) {
749        self.props().set_center(self.host(), value)
750    }
751
752    pub fn gradient_origin(&self) -> RelativePoint {
753        self.props().gradient_origin()
754    }
755
756    pub fn set_gradient_origin(&self, value: RelativePoint) {
757        self.props().set_gradient_origin(self.host(), value)
758    }
759
760    pub fn radius_x(&self) -> RelativeScalar {
761        self.props().radius_x()
762    }
763
764    pub fn set_radius_x(&self, value: RelativeScalar) {
765        self.props().set_radius_x(self.host(), value)
766    }
767
768    pub fn radius_y(&self) -> RelativeScalar {
769        self.props().radius_y()
770    }
771
772    pub fn set_radius_y(&self, value: RelativeScalar) {
773        self.props().set_radius_y(self.host(), value)
774    }
775
776    pub fn radius(&self) -> f64 {
777        self.radius_x().scalar
778    }
779}
780
781gradient_brush_class!(
782    /// A composition brush that paints with a conic gradient.
783    CompositionConicGradientBrush,
784    ConicGradient,
785    CompositionConicGradientBrushProps,
786    ServerCompositionConicGradientBrush
787);
788
789impl CompositionConicGradientBrush {
790    pub fn angle(&self) -> f64 {
791        self.props().angle()
792    }
793
794    pub fn set_angle(&self, value: f64) {
795        self.props().set_angle(self.host(), value)
796    }
797
798    pub fn center(&self) -> RelativePoint {
799        self.props().center()
800    }
801
802    pub fn set_center(&self, value: RelativePoint) {
803        self.props().set_center(self.host(), value)
804    }
805}