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, ServerCompositionSurfaceBrush,
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, CompositionSurfaceBrushHooks, CompositionSurfaceBrushProps,
33    ServerCompositionBrushProps, ServerCompositionConicGradientBrushProps, ServerCompositionLinearGradientBrushProps,
34    ServerCompositionRadialGradientBrushProps, ServerCompositionSolidColorBrushProps,
35    ServerCompositionSurfaceBrushProps,
36};
37use crate::rendering::composition::generated::{
38    CompositionMaskBrushHooks, CompositionMaskBrushProps, CompositionNineGridBrushHooks,
39    CompositionNineGridBrushProps, ServerCompositionMaskBrushProps, ServerCompositionNineGridBrushProps,
40};
41use crate::rendering::composition::animations::{parse_subchannel, CompositionColorSpace};
42use super::composition_effect_brush::CompositionEffectBrushData;
43use crate::rendering::composition::CompositionMappingMode;
44use crate::rendering::composition::server::{CompositionProperty, IServerObject, ServerCompositor, ServerObjectId};
45use crate::rendering::composition::transport::{BatchResource, BatchStreamWriter, IRegisterForSerialization};
46use crate::rendering::composition::{
47    AsCompositionObject, CompositionBitmapInterpolationMode, CompositionObject, CompositionStretch, Compositor,
48    ICompositionObject, ICompositionObjectAnimations, ICompositionObjectHost, ICompositionSurface,
49    ICompositorSerializable, PendingAnimations,
50};
51use crate::{Matrix, RelativePoint, RelativeScalar, Vector};
52use std::any::Any;
53use std::cell::{Cell, RefCell};
54use std::ops::Deref;
55use std::rc::{Rc, Weak};
56
57/// What the hand-written `CompositionGradientBrush` adds to the generated
58/// `CompositionBrush`: the stops and the spread method.
59pub(crate) struct CompositionGradientBrushData {
60    brush: CompositionBrushProps,
61    gradient_stops: RefCell<FerroList<Rc<dyn IGradientStop>>>,
62    spread_method: Cell<GradientSpreadMethod>,
63    /// The WinUI `MappingMode`: the unit of the points the WinUI members set.
64    pub(super) mapping_mode: Cell<CompositionMappingMode>,
65    /// The WinUI `InterpolationSpace`, stored only.
66    pub(super) interpolation_space: Cell<CompositionColorSpace>,
67}
68
69impl CompositionGradientBrushData {
70    fn new() -> Self {
71        Self {
72            brush: CompositionBrushProps::new(),
73            gradient_stops: RefCell::new(FerroList::new()),
74            spread_method: Cell::new(GradientSpreadMethod::default()),
75            mapping_mode: Cell::new(CompositionMappingMode::Relative),
76            interpolation_space: Cell::new(CompositionColorSpace::Auto),
77        }
78    }
79
80    /// `SerializeChangesCore` of `CompositionGradientBrush`: the generated
81    /// base class, then the spread method and the stops.
82    fn serialize_changes_core(&self, host: &dyn CompositionBrushHooks, writer: &mut BatchStreamWriter<'_>) {
83        self.brush.serialize_changes_core(host, writer);
84        writer.write(self.spread_method.get());
85        let stops = self.gradient_stops.borrow().clone();
86        writer.write(stops.count() as i32);
87        for stop in stops.to_vec() {
88            let item: BatchResource<dyn IGradientStop> = match stop.as_composition_gradient_stop() {
89                Some(comp) => BatchResource::Server(comp.server()),
90                // A mutable UI-thread stop must not cross to the render thread
91                // by reference; ship its current values instead.
92                None if stop.is_immutable_gradient_stop() => BatchResource::Value(stop.clone()),
93                None => BatchResource::Value(Rc::new(ImmutableGradientStop::new(stop.offset(), stop.color()))),
94            };
95            writer.write_resource(Some(item));
96        }
97    }
98}
99
100/// What the WinUI `CompositionSurfaceBrush` adds to the generated
101/// `CompositionBrush`: its generated properties, and the surface it shows as
102/// the caller gave it (the generated property holds it as a composition
103/// object).
104pub(crate) struct CompositionSurfaceBrushData {
105    props: CompositionSurfaceBrushProps,
106    surface: RefCell<Option<Rc<dyn ICompositionSurface>>>,
107}
108
109/// What the concrete class of a composition brush adds to
110/// [`CompositionBrush`].
111pub(crate) enum CompositionBrushKind {
112    SolidColor(CompositionSolidColorBrushProps),
113    LinearGradient(CompositionGradientBrushData, CompositionLinearGradientBrushProps),
114    RadialGradient(CompositionGradientBrushData, CompositionRadialGradientBrushProps),
115    ConicGradient(CompositionGradientBrushData, CompositionConicGradientBrushProps),
116    Surface(CompositionSurfaceBrushData),
117    /// The WinUI `CompositionNineGridBrush`.
118    NineGrid(CompositionNineGridBrushProps),
119    /// The WinUI `CompositionMaskBrush`.
120    Mask(CompositionMaskBrushProps),
121    /// The WinUI `CompositionEffectBrush`.
122    Effect(CompositionEffectBrushData),
123    /// The WinUI `CompositionBackdropBrush`.
124    Backdrop(CompositionBrushProps),
125    /// The WinUI `CompositionHostBackdropBrush`.
126    HostBackdrop(CompositionBrushProps),
127}
128
129/// A brush of the composition API.
130pub struct CompositionBrush {
131    this: Weak<CompositionBrush>,
132    object: CompositionObject,
133    pub(super) kind: CompositionBrushKind,
134    transform: RefCell<Option<Rc<dyn ITransform>>>,
135    relative_transform: RefCell<Option<Rc<dyn ITransform>>>,
136}
137
138impl CompositionBrush {
139    pub(super) fn create(
140        compositor: &Rc<Compositor>,
141        kind: CompositionBrushKind,
142        server: impl FnOnce(&Rc<ServerCompositor>) -> Rc<dyn IServerObject> + Send + 'static,
143    ) -> Rc<CompositionBrush> {
144        let server = compositor.create_server_object(move |compositor, _| server(compositor));
145        Rc::new_cyclic(|this: &Weak<CompositionBrush>| CompositionBrush {
146            this: this.clone(),
147            object: CompositionObject::new(compositor, Some(server)),
148            kind,
149            transform: RefCell::new(None),
150            relative_transform: RefCell::new(None),
151        })
152    }
153
154    /// The constructor chain of the gradient brushes: `CompositionBrush`,
155    /// then the class.
156    pub(super) fn initialize_defaults(&self) {
157        match &self.kind {
158            CompositionBrushKind::SolidColor(props) => props.initialize_defaults(self),
159            CompositionBrushKind::LinearGradient(data, props) => {
160                data.brush.initialize_defaults(self);
161                props.initialize_defaults(self);
162            }
163            CompositionBrushKind::RadialGradient(data, props) => {
164                data.brush.initialize_defaults(self);
165                props.initialize_defaults(self);
166            }
167            CompositionBrushKind::ConicGradient(data, props) => {
168                data.brush.initialize_defaults(self);
169                props.initialize_defaults(self);
170            }
171            CompositionBrushKind::Surface(data) => data.props.initialize_defaults(self),
172            CompositionBrushKind::NineGrid(props) => props.initialize_defaults(self),
173            CompositionBrushKind::Mask(props) => props.initialize_defaults(self),
174            CompositionBrushKind::Effect(data) => data.brush.initialize_defaults(self),
175            CompositionBrushKind::Backdrop(props) | CompositionBrushKind::HostBackdrop(props) => props.initialize_defaults(self),
176        }
177    }
178
179    /// The generated properties of `CompositionBrush`.
180    pub fn brush_props(&self) -> &CompositionBrushProps {
181        match &self.kind {
182            CompositionBrushKind::SolidColor(props) => props.base(),
183            CompositionBrushKind::LinearGradient(data, _)
184            | CompositionBrushKind::RadialGradient(data, _)
185            | CompositionBrushKind::ConicGradient(data, _) => &data.brush,
186            CompositionBrushKind::Surface(data) => data.props.base(),
187            CompositionBrushKind::NineGrid(props) => props.base(),
188            CompositionBrushKind::Mask(props) => props.base(),
189            CompositionBrushKind::Effect(data) => &data.brush,
190            CompositionBrushKind::Backdrop(props) | CompositionBrushKind::HostBackdrop(props) => props,
191        }
192    }
193
194    fn gradient_data(&self) -> Option<&CompositionGradientBrushData> {
195        match &self.kind {
196            CompositionBrushKind::SolidColor(_)
197            | CompositionBrushKind::Surface(_)
198            | CompositionBrushKind::NineGrid(_)
199            | CompositionBrushKind::Mask(_)
200            | CompositionBrushKind::Effect(_)
201            | CompositionBrushKind::Backdrop(_) | CompositionBrushKind::HostBackdrop(_) => None,
202            CompositionBrushKind::LinearGradient(data, _)
203            | CompositionBrushKind::RadialGradient(data, _)
204            | CompositionBrushKind::ConicGradient(data, _) => Some(data),
205        }
206    }
207
208    pub fn compositor(&self) -> &Rc<Compositor> {
209        self.object.compositor()
210    }
211
212    /// The embedded `CompositionObject`.
213    pub fn object(&self) -> &CompositionObject {
214        &self.object
215    }
216
217    /// The id of the server-side brush.
218    pub fn server(&self) -> ServerObjectId {
219        self.object.required_server()
220    }
221
222    pub fn is_disposed(&self) -> bool {
223        self.object.is_disposed()
224    }
225
226    /// The name of the concrete class, for messages.
227    pub fn type_name(&self) -> &'static str {
228        match &self.kind {
229            CompositionBrushKind::SolidColor(_) => "CompositionSolidColorBrush",
230            CompositionBrushKind::LinearGradient(..) => "CompositionLinearGradientBrush",
231            CompositionBrushKind::RadialGradient(..) => "CompositionRadialGradientBrush",
232            CompositionBrushKind::ConicGradient(..) => "CompositionConicGradientBrush",
233            CompositionBrushKind::Surface(_) => "CompositionSurfaceBrush",
234            CompositionBrushKind::NineGrid(_) => "CompositionNineGridBrush",
235            CompositionBrushKind::Mask(_) => "CompositionMaskBrush",
236            CompositionBrushKind::Effect(_) => "CompositionEffectBrush",
237            CompositionBrushKind::Backdrop(_) => "CompositionBackdropBrush",
238            CompositionBrushKind::HostBackdrop(_) => "CompositionHostBackdropBrush",
239        }
240    }
241
242    /// The handle of the brush, from a reference to it (an `IBrush` seen as
243    /// a `CompositionBrush` through `IBrush::as_composition_brush`).
244    pub fn to_rc(&self) -> Rc<CompositionBrush> {
245        self.this.upgrade().expect("the brush is alive while it is used")
246    }
247
248    /// The brush as an `IBrush` handle.
249    pub fn as_brush(&self) -> Rc<dyn IBrush> {
250        self.this.upgrade().expect("the brush is alive while it is used") as Rc<dyn IBrush>
251    }
252
253    /// The collection of implicit animations attached to this object.
254    pub fn implicit_animations(&self) -> Option<Rc<ImplicitAnimationCollection>> {
255        self.object.implicit_animations()
256    }
257
258    pub fn set_implicit_animations(&self, value: Option<Rc<ImplicitAnimationCollection>>) {
259        self.object.set_implicit_animations(value)
260    }
261
262    pub fn opacity(&self) -> f64 {
263        self.brush_props().opacity()
264    }
265
266    pub fn set_opacity(&self, value: f64) {
267        self.brush_props().set_opacity(self, value)
268    }
269
270    pub fn transform_origin(&self) -> RelativePoint {
271        self.brush_props().transform_origin()
272    }
273
274    pub fn set_transform_origin(&self, value: RelativePoint) {
275        self.brush_props().set_transform_origin(self, value)
276    }
277
278    pub fn transform(&self) -> Option<Rc<dyn ITransform>> {
279        self.transform.borrow().clone()
280    }
281
282    /// Sets the transform. What the server receives is the form a
283    /// transform takes on a compositor (`GetServer`): an immutable
284    /// transform as it is, a mutable one as a copy of its current value.
285    pub fn set_transform(&self, value: Option<Rc<dyn ITransform>>) {
286        let transport = transform_get_server(value.as_ref(), Some(self.compositor()));
287        *self.transform.borrow_mut() = value;
288        self.brush_props().set_transform(self, transport)
289    }
290
291    pub fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
292        self.relative_transform.borrow().clone()
293    }
294
295    /// Sets the relative transform; see [`set_transform`](Self::set_transform).
296    pub fn set_relative_transform(&self, value: Option<Rc<dyn ITransform>>) {
297        let transport = transform_get_server(value.as_ref(), Some(self.compositor()));
298        *self.relative_transform.borrow_mut() = value;
299        self.brush_props().set_relative_transform(self, transport)
300    }
301
302    fn serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
303        match &self.kind {
304            CompositionBrushKind::SolidColor(props) => props.serialize_changes_core(self, writer),
305            CompositionBrushKind::LinearGradient(_, props) => props.serialize_changes_core(self, writer),
306            CompositionBrushKind::RadialGradient(_, props) => props.serialize_changes_core(self, writer),
307            CompositionBrushKind::ConicGradient(_, props) => props.serialize_changes_core(self, writer),
308            CompositionBrushKind::Surface(data) => data.props.serialize_changes_core(self, writer),
309            CompositionBrushKind::NineGrid(props) => props.serialize_changes_core(self, writer),
310            CompositionBrushKind::Mask(props) => props.serialize_changes_core(self, writer),
311            CompositionBrushKind::Effect(data) => data.serialize_changes_core(self, writer),
312            CompositionBrushKind::Backdrop(props) | CompositionBrushKind::HostBackdrop(props) => props.serialize_changes_core(self, writer),
313        }
314    }
315
316    /// The `base.SerializeChangesCore` of the gradient brushes.
317    fn serialize_gradient_base(&self, writer: &mut BatchStreamWriter<'_>) {
318        if let Some(data) = self.gradient_data() {
319            data.serialize_changes_core(self, writer);
320        }
321    }
322}
323
324/// `InitializeDefaultsExtra` activates the server object: done when the
325/// server object is created (see the constructors of the server brushes).
326impl CompositionBrushHooks for CompositionBrush {}
327
328impl CompositionSolidColorBrushHooks for CompositionBrush {}
329
330impl CompositionSurfaceBrushHooks for CompositionBrush {}
331
332impl CompositionNineGridBrushHooks for CompositionBrush {}
333
334impl CompositionMaskBrushHooks for CompositionBrush {}
335
336impl CompositionLinearGradientBrushHooks for CompositionBrush {
337    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
338        self.serialize_gradient_base(writer)
339    }
340}
341
342impl CompositionRadialGradientBrushHooks for CompositionBrush {
343    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
344        self.serialize_gradient_base(writer)
345    }
346}
347
348impl CompositionConicGradientBrushHooks for CompositionBrush {
349    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
350        self.serialize_gradient_base(writer)
351    }
352}
353
354impl IRegisterForSerialization for CompositionBrush {
355    fn register_for_serialization(&self) {
356        self.object
357            .register_for_serialization(|| self.this.upgrade().map(|this| this as Rc<dyn ICompositorSerializable>));
358    }
359}
360
361impl ICompositionObjectHost for CompositionBrush {
362    fn server(&self) -> ServerObjectId {
363        self.object.required_server()
364    }
365
366    fn pending_animations(&self) -> &PendingAnimations {
367        self.object.pending_animations()
368    }
369
370    fn implicit_animation(&self, property_name: &str) -> Option<Rc<dyn ICompositionAnimationBase>> {
371        self.object.implicit_animation(property_name)
372    }
373
374    fn start_animation_group(
375        &self,
376        grp: &Rc<dyn ICompositionAnimationBase>,
377        target: &str,
378        final_value: ExpressionVariant,
379    ) -> bool {
380        self.start_animation_group_for(&**grp, target, final_value)
381    }
382}
383
384impl ICompositionObjectAnimations for CompositionBrush {
385    fn try_start_animation(
386        &self,
387        property_name: &str,
388        animation: &dyn ICompositionAnimation,
389        final_value: Option<ExpressionVariant>,
390    ) -> bool {
391        // The gradient brushes have a hand-written base class, which the
392        // generated code does not chain to: the properties of
393        // `CompositionBrush` are tried after those of the class.
394        match &self.kind {
395            CompositionBrushKind::SolidColor(props) => props.start_animation(self, property_name, animation, final_value),
396            CompositionBrushKind::LinearGradient(data, props) => {
397                props.start_animation(self, property_name, animation, final_value)
398                    || data.brush.start_animation(self, property_name, animation, final_value)
399            }
400            CompositionBrushKind::RadialGradient(data, props) => {
401                props.start_animation(self, property_name, animation, final_value)
402                    || data.brush.start_animation(self, property_name, animation, final_value)
403            }
404            CompositionBrushKind::ConicGradient(data, props) => {
405                props.start_animation(self, property_name, animation, final_value)
406                    || data.brush.start_animation(self, property_name, animation, final_value)
407            }
408            // The generated base class is chained by the generated code.
409            CompositionBrushKind::Surface(data) => {
410                data.props.start_animation(self, property_name, animation, final_value)
411            }
412            CompositionBrushKind::NineGrid(props) => props.start_animation(self, property_name, animation, final_value),
413            CompositionBrushKind::Mask(props) => props.start_animation(self, property_name, animation, final_value),
414            // The animatable properties of the effect are entries of the
415            // property set of the brush, which animates them.
416            CompositionBrushKind::Effect(data) if data.graph.is_animatable(property_name) => {
417                self.object.properties().try_start_animation(property_name, animation, final_value)
418            }
419            CompositionBrushKind::Effect(data) => data.brush.start_animation(self, property_name, animation, final_value),
420            CompositionBrushKind::Backdrop(props) | CompositionBrushKind::HostBackdrop(props) => props.start_animation(self, property_name, animation, final_value),
421        }
422    }
423
424    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
425        match &self.kind {
426            CompositionBrushKind::SolidColor(_) => {
427                ServerCompositionSolidColorBrushProps::get_composition_property(property_name)
428            }
429            CompositionBrushKind::LinearGradient(..) => {
430                ServerCompositionLinearGradientBrushProps::get_composition_property(property_name)
431            }
432            CompositionBrushKind::RadialGradient(..) => {
433                ServerCompositionRadialGradientBrushProps::get_composition_property(property_name)
434            }
435            CompositionBrushKind::ConicGradient(..) => {
436                ServerCompositionConicGradientBrushProps::get_composition_property(property_name)
437            }
438            CompositionBrushKind::Surface(_) => {
439                ServerCompositionSurfaceBrushProps::get_composition_property(property_name)
440            }
441            CompositionBrushKind::NineGrid(_) => {
442                ServerCompositionNineGridBrushProps::get_composition_property(property_name)
443            }
444            CompositionBrushKind::Mask(_) => {
445                ServerCompositionMaskBrushProps::get_composition_property(property_name)
446            }
447            // The animatable properties of an effect are properties of its
448            // property set, not of the brush.
449            CompositionBrushKind::Effect(_) | CompositionBrushKind::Backdrop(_) | CompositionBrushKind::HostBackdrop(_) => None,
450        }
451        .or_else(|| ServerCompositionBrushProps::get_composition_property(property_name))
452    }
453
454    fn composition_object(&self) -> &CompositionObject {
455        &self.object
456    }
457
458    /// `StopAnimation`: an animatable property of an effect brush is stopped
459    /// on the property set of the brush, which runs it; any other property
460    /// as for every composition object.
461    fn stop_animation(&self, property_name: &str) {
462        if let CompositionBrushKind::Effect(data) = &self.kind {
463            if data.graph.is_animatable(property_name) {
464                self.object.note_animation_stopped(property_name);
465                self.object.properties().stop_animation(property_name);
466                return;
467            }
468        }
469        // The default of `ICompositionObjectAnimations::stop_animation`.
470        self.object.note_animation_stopped(property_name);
471        if let Some((property, components)) = parse_subchannel(property_name) {
472            let Some(property) = self.get_composition_property(property) else {
473                panic!("Unknown property {property_name}");
474            };
475            self.object.stop_subchannel_animation(property, components);
476            return;
477        }
478        let Some(property) = self.get_composition_property(property_name) else {
479            panic!("Unknown property {property_name}");
480        };
481        self.object.stop_animation(property);
482    }
483}
484
485impl AsCompositionObject for CompositionBrush {
486    fn as_composition_object(&self) -> &CompositionObject {
487        &self.object
488    }
489
490    fn composition_type_name(&self) -> &'static str {
491        self.type_name()
492    }
493}
494
495impl ICompositionObject for CompositionBrush {
496    fn server(&self) -> ServerObjectId {
497        self.object.required_server()
498    }
499
500    fn as_any(&self) -> &dyn Any {
501        self
502    }
503
504    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
505        self
506    }
507}
508
509impl ICompositorSerializable for CompositionBrush {
510    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
511        self.object.try_get_server(c)
512    }
513
514    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
515        self.object.begin_serialize_changes(c);
516        self.serialize_changes_core(writer);
517    }
518}
519
520// --- IBrush --------------------------------------------------------------------
521//
522// The brush is drawn as itself where there is no compositor, and through its
523// server object on its own compositor. The members of the interfaces of the
524// concrete classes are answered only by the matching kind (the `as_*`
525// members of `IBrush` select it).
526
527impl IBrush for CompositionBrush {
528    fn opacity(&self) -> f64 {
529        CompositionBrush::opacity(self)
530    }
531
532    fn transform(&self) -> Option<Rc<dyn ITransform>> {
533        CompositionBrush::transform(self)
534    }
535
536    fn transform_origin(&self) -> RelativePoint {
537        CompositionBrush::transform_origin(self)
538    }
539
540    fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
541        CompositionBrush::relative_transform(self)
542    }
543
544    fn as_any(&self) -> &dyn Any {
545        self
546    }
547
548    fn as_solid_color_brush(&self) -> Option<&dyn ISolidColorBrush> {
549        matches!(self.kind, CompositionBrushKind::SolidColor(_)).then_some(self as &dyn ISolidColorBrush)
550    }
551
552    fn as_gradient_brush(&self) -> Option<&dyn IGradientBrush> {
553        self.gradient_data().map(|_| self as &dyn IGradientBrush)
554    }
555
556    fn as_linear_gradient_brush(&self) -> Option<&dyn ILinearGradientBrush> {
557        matches!(self.kind, CompositionBrushKind::LinearGradient(..)).then_some(self as &dyn ILinearGradientBrush)
558    }
559
560    fn as_radial_gradient_brush(&self) -> Option<&dyn IRadialGradientBrush> {
561        matches!(self.kind, CompositionBrushKind::RadialGradient(..)).then_some(self as &dyn IRadialGradientBrush)
562    }
563
564    fn as_conic_gradient_brush(&self) -> Option<&dyn IConicGradientBrush> {
565        matches!(self.kind, CompositionBrushKind::ConicGradient(..)).then_some(self as &dyn IConicGradientBrush)
566    }
567
568    fn as_composition_brush(&self) -> Option<&CompositionBrush> {
569        Some(self)
570    }
571}
572
573fn not_of_kind(member: &str) -> ! {
574    panic!("{member} is not a member of this composition brush");
575}
576
577impl ISolidColorBrush for CompositionBrush {
578    fn color(&self) -> Color {
579        match &self.kind {
580            CompositionBrushKind::SolidColor(props) => props.color(),
581            _ => not_of_kind("Color"),
582        }
583    }
584}
585
586impl IGradientBrush for CompositionBrush {
587    fn gradient_stops(&self) -> Vec<Rc<dyn IGradientStop>> {
588        match self.gradient_data() {
589            Some(data) => data.gradient_stops.borrow().to_vec(),
590            None => not_of_kind("GradientStops"),
591        }
592    }
593
594    fn spread_method(&self) -> GradientSpreadMethod {
595        match self.gradient_data() {
596            Some(data) => data.spread_method.get(),
597            None => not_of_kind("SpreadMethod"),
598        }
599    }
600}
601
602impl ILinearGradientBrush for CompositionBrush {
603    fn start_point(&self) -> RelativePoint {
604        match &self.kind {
605            CompositionBrushKind::LinearGradient(_, props) => props.start_point(),
606            _ => not_of_kind("StartPoint"),
607        }
608    }
609
610    fn end_point(&self) -> RelativePoint {
611        match &self.kind {
612            CompositionBrushKind::LinearGradient(_, props) => props.end_point(),
613            _ => not_of_kind("EndPoint"),
614        }
615    }
616}
617
618impl IRadialGradientBrush for CompositionBrush {
619    fn center(&self) -> RelativePoint {
620        match &self.kind {
621            CompositionBrushKind::RadialGradient(_, props) => props.center(),
622            _ => not_of_kind("Center"),
623        }
624    }
625
626    fn gradient_origin(&self) -> RelativePoint {
627        match &self.kind {
628            CompositionBrushKind::RadialGradient(_, props) => props.gradient_origin(),
629            _ => not_of_kind("GradientOrigin"),
630        }
631    }
632
633    fn radius_x(&self) -> RelativeScalar {
634        match &self.kind {
635            CompositionBrushKind::RadialGradient(_, props) => props.radius_x(),
636            _ => not_of_kind("RadiusX"),
637        }
638    }
639
640    fn radius_y(&self) -> RelativeScalar {
641        match &self.kind {
642            CompositionBrushKind::RadialGradient(_, props) => props.radius_y(),
643            _ => not_of_kind("RadiusY"),
644        }
645    }
646}
647
648impl IConicGradientBrush for CompositionBrush {
649    fn center(&self) -> RelativePoint {
650        match &self.kind {
651            CompositionBrushKind::ConicGradient(_, props) => props.center(),
652            _ => not_of_kind("Center"),
653        }
654    }
655
656    fn angle(&self) -> f64 {
657        match &self.kind {
658            CompositionBrushKind::ConicGradient(_, props) => props.angle(),
659            _ => not_of_kind("Angle"),
660        }
661    }
662}
663
664// --- the concrete classes ------------------------------------------------------
665
666/// A composition brush that paints with a color.
667#[derive(Clone)]
668pub struct CompositionSolidColorBrush(Rc<CompositionBrush>);
669
670impl Deref for CompositionSolidColorBrush {
671    type Target = Rc<CompositionBrush>;
672
673    fn deref(&self) -> &Rc<CompositionBrush> {
674        &self.0
675    }
676}
677
678impl CompositionSolidColorBrush {
679    fn create(compositor: &Rc<Compositor>) -> Rc<CompositionBrush> {
680        CompositionBrush::create(
681            compositor,
682            CompositionBrushKind::SolidColor(CompositionSolidColorBrushProps::new()),
683            |c| ServerCompositionSolidColorBrush::new(c),
684        )
685    }
686
687    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionSolidColorBrush {
688        let brush = Self::create(compositor);
689        brush.initialize_defaults();
690        CompositionSolidColorBrush(brush)
691    }
692
693    pub(crate) fn with_color(compositor: &Rc<Compositor>, color: Color) -> CompositionSolidColorBrush {
694        let brush = Self::create(compositor);
695        let this = CompositionSolidColorBrush(brush);
696        // The constructor of the base class, then the color, then the
697        // defaults of the class.
698        this.props().base().initialize_defaults(&*this.0);
699        this.set_color(color);
700        this.props().initialize_own_defaults(&*this.0);
701        this
702    }
703
704    /// The handle of `brush` as a solid color brush, if it is one.
705    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionSolidColorBrush> {
706        matches!(brush.kind, CompositionBrushKind::SolidColor(_)).then(|| CompositionSolidColorBrush(brush.clone()))
707    }
708
709    fn props(&self) -> &CompositionSolidColorBrushProps {
710        match &self.0.kind {
711            CompositionBrushKind::SolidColor(props) => props,
712            _ => unreachable!("a solid color brush handle wraps a solid color brush"),
713        }
714    }
715
716    pub fn color(&self) -> Color {
717        self.props().color()
718    }
719
720    pub fn set_color(&self, value: Color) {
721        self.props().set_color(&*self.0, value)
722    }
723}
724
725/// The base of the composition gradient brushes: the gradient stops and the
726/// spread method.
727#[derive(Clone)]
728pub struct CompositionGradientBrush(Rc<CompositionBrush>);
729
730impl Deref for CompositionGradientBrush {
731    type Target = Rc<CompositionBrush>;
732
733    fn deref(&self) -> &Rc<CompositionBrush> {
734        &self.0
735    }
736}
737
738impl CompositionGradientBrush {
739    /// The handle of `brush` as a gradient brush, if it is one.
740    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionGradientBrush> {
741        brush.gradient_data().map(|_| CompositionGradientBrush(brush.clone()))
742    }
743
744    fn data(&self) -> &CompositionGradientBrushData {
745        match self.0.gradient_data() {
746            Some(data) => data,
747            None => unreachable!("a gradient brush handle wraps a gradient brush"),
748        }
749    }
750
751    /// The gradient stops. Mutations of the list itself are not tracked - assign
752    /// the property to ship in-place edits made after a commit. Stops created via
753    /// [`Compositor::create_gradient_stop_with`] stay live
754    /// on the server and animate individually; other stops are snapshotted at
755    /// serialization time.
756    pub fn gradient_stops(&self) -> FerroList<Rc<dyn IGradientStop>> {
757        self.data().gradient_stops.borrow().clone()
758    }
759
760    pub fn set_gradient_stops(&self, value: FerroList<Rc<dyn IGradientStop>>) {
761        if self.data().gradient_stops.borrow().ptr_eq(&value) {
762            return;
763        }
764        *self.data().gradient_stops.borrow_mut() = value;
765        self.0.register_for_serialization();
766    }
767
768    /// How the gradient repeats outside the stop range.
769    pub fn spread_method(&self) -> GradientSpreadMethod {
770        self.data().spread_method.get()
771    }
772
773    pub fn set_spread_method(&self, value: GradientSpreadMethod) {
774        if self.data().spread_method.get() == value {
775            return;
776        }
777        self.data().spread_method.set(value);
778        self.0.register_for_serialization();
779    }
780}
781
782macro_rules! gradient_brush_class {
783    ($(#[$meta:meta])* $name:ident, $variant:ident, $props:ident, $server:ident) => {
784        $(#[$meta])*
785        #[derive(Clone)]
786        pub struct $name(CompositionGradientBrush);
787
788        impl Deref for $name {
789            type Target = CompositionGradientBrush;
790
791            fn deref(&self) -> &CompositionGradientBrush {
792                &self.0
793            }
794        }
795
796        impl $name {
797            pub(crate) fn new(compositor: &Rc<Compositor>) -> $name {
798                let brush = CompositionBrush::create(
799                    compositor,
800                    CompositionBrushKind::$variant(CompositionGradientBrushData::new(), $props::new()),
801                    |c| $server::new(c),
802                );
803                brush.initialize_defaults();
804                $name(CompositionGradientBrush(brush))
805            }
806
807            /// The handle of `brush` as this class, if it is one.
808            pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<$name> {
809                matches!(brush.kind, CompositionBrushKind::$variant(..))
810                    .then(|| $name(CompositionGradientBrush(brush.clone())))
811            }
812
813            fn props(&self) -> &$props {
814                match &self.0 .0.kind {
815                    CompositionBrushKind::$variant(_, props) => props,
816                    _ => unreachable!("the handle wraps a brush of its class"),
817                }
818            }
819
820            fn host(&self) -> &CompositionBrush {
821                &self.0 .0
822            }
823        }
824    };
825}
826
827gradient_brush_class!(
828    /// A composition brush that paints with a linear gradient.
829    CompositionLinearGradientBrush,
830    LinearGradient,
831    CompositionLinearGradientBrushProps,
832    ServerCompositionLinearGradientBrush
833);
834
835impl CompositionLinearGradientBrush {
836    pub fn start_point(&self) -> RelativePoint {
837        self.props().start_point()
838    }
839
840    pub fn set_start_point(&self, value: RelativePoint) {
841        self.props().set_start_point(self.host(), value)
842    }
843
844    pub fn end_point(&self) -> RelativePoint {
845        self.props().end_point()
846    }
847
848    pub fn set_end_point(&self, value: RelativePoint) {
849        self.props().set_end_point(self.host(), value)
850    }
851}
852
853gradient_brush_class!(
854    /// A composition brush that paints with a radial gradient.
855    CompositionRadialGradientBrush,
856    RadialGradient,
857    CompositionRadialGradientBrushProps,
858    ServerCompositionRadialGradientBrush
859);
860
861impl CompositionRadialGradientBrush {
862    pub fn center(&self) -> RelativePoint {
863        self.props().center()
864    }
865
866    pub fn set_center(&self, value: RelativePoint) {
867        self.props().set_center(self.host(), value)
868    }
869
870    pub fn gradient_origin(&self) -> RelativePoint {
871        self.props().gradient_origin()
872    }
873
874    pub fn set_gradient_origin(&self, value: RelativePoint) {
875        self.props().set_gradient_origin(self.host(), value)
876    }
877
878    pub fn radius_x(&self) -> RelativeScalar {
879        self.props().radius_x()
880    }
881
882    pub fn set_radius_x(&self, value: RelativeScalar) {
883        self.props().set_radius_x(self.host(), value)
884    }
885
886    pub fn radius_y(&self) -> RelativeScalar {
887        self.props().radius_y()
888    }
889
890    pub fn set_radius_y(&self, value: RelativeScalar) {
891        self.props().set_radius_y(self.host(), value)
892    }
893
894    pub fn radius(&self) -> f64 {
895        self.radius_x().scalar
896    }
897}
898
899gradient_brush_class!(
900    /// A composition brush that paints with a conic gradient.
901    CompositionConicGradientBrush,
902    ConicGradient,
903    CompositionConicGradientBrushProps,
904    ServerCompositionConicGradientBrush
905);
906
907impl CompositionConicGradientBrush {
908    pub fn angle(&self) -> f64 {
909        self.props().angle()
910    }
911
912    pub fn set_angle(&self, value: f64) {
913        self.props().set_angle(self.host(), value)
914    }
915
916    pub fn center(&self) -> RelativePoint {
917        self.props().center()
918    }
919
920    pub fn set_center(&self, value: RelativePoint) {
921        self.props().set_center(self.host(), value)
922    }
923}
924
925/// A composition brush that paints a surface (WinUI
926/// `Microsoft.UI.Composition.CompositionSurfaceBrush`): a
927/// [`LoadedImageSurface`](crate::rendering::composition::LoadedImageSurface),
928/// a [`CompositionVisualSurface`](crate::rendering::composition::CompositionVisualSurface)
929/// or a [`CompositionSurface`](crate::rendering::composition::CompositionSurface).
930///
931/// The surface is laid out at its natural size in the area of the brush by
932/// `stretch` and the alignment ratios, transformed by the transform of the
933/// brush (`AnchorPoint`, `CenterPoint`, `Offset`, `RotationAngle`, `Scale`,
934/// `TransformMatrix`, relative to the area) and clipped to the area. The
935/// alignment ratios and the transform members are animatable.
936///
937/// It is painted by the visuals that show it (a sprite visual); it is not a
938/// brush of the drawing context, so it cannot fill the shapes of a drawing
939/// context. The `Opacity` of the composition brushes applies; their
940/// `Transform` and `RelativeTransform` do not.
941#[derive(Clone)]
942pub struct CompositionSurfaceBrush(Rc<CompositionBrush>);
943
944impl Deref for CompositionSurfaceBrush {
945    type Target = Rc<CompositionBrush>;
946
947    fn deref(&self) -> &Rc<CompositionBrush> {
948        &self.0
949    }
950}
951
952impl CompositionSurfaceBrush {
953    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionSurfaceBrush {
954        let brush = CompositionBrush::create(
955            compositor,
956            CompositionBrushKind::Surface(CompositionSurfaceBrushData {
957                props: CompositionSurfaceBrushProps::new(),
958                surface: RefCell::new(None),
959            }),
960            |c| ServerCompositionSurfaceBrush::new(c),
961        );
962        brush.initialize_defaults();
963        CompositionSurfaceBrush(brush)
964    }
965
966    pub(crate) fn with_surface(compositor: &Rc<Compositor>, surface: Rc<dyn ICompositionSurface>) -> CompositionSurfaceBrush {
967        let brush = Self::new(compositor);
968        brush.set_surface(Some(surface));
969        brush
970    }
971
972    /// The handle of `brush` as a surface brush, if it is one.
973    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionSurfaceBrush> {
974        matches!(brush.kind, CompositionBrushKind::Surface(_)).then(|| CompositionSurfaceBrush(brush.clone()))
975    }
976
977    fn data(&self) -> &CompositionSurfaceBrushData {
978        match &self.0.kind {
979            CompositionBrushKind::Surface(data) => data,
980            _ => unreachable!("a surface brush handle wraps a surface brush"),
981        }
982    }
983
984    fn host(&self) -> &CompositionBrush {
985        &self.0
986    }
987
988    /// The generated properties of the brush.
989    pub fn props(&self) -> &CompositionSurfaceBrushProps {
990        &self.data().props
991    }
992
993    /// The surface the brush paints.
994    pub fn surface(&self) -> Option<Rc<dyn ICompositionSurface>> {
995        self.data().surface.borrow().clone()
996    }
997
998    pub fn set_surface(&self, value: Option<Rc<dyn ICompositionSurface>>) {
999        *self.data().surface.borrow_mut() = value.clone();
1000        self.props().set_surface(self.host(), value.map(|surface| surface as Rc<dyn ICompositionObject>))
1001    }
1002
1003    /// How the surface is sized in the area of the brush (default `Fill`).
1004    pub fn stretch(&self) -> CompositionStretch {
1005        self.props().stretch()
1006    }
1007
1008    pub fn set_stretch(&self, value: CompositionStretch) {
1009        self.props().set_stretch(self.host(), value)
1010    }
1011
1012    /// Where the surface is placed horizontally: 0 left, 0.5 centred
1013    /// (default), 1 right.
1014    pub fn horizontal_alignment_ratio(&self) -> f32 {
1015        self.props().horizontal_alignment_ratio()
1016    }
1017
1018    pub fn set_horizontal_alignment_ratio(&self, value: f32) {
1019        self.props().set_horizontal_alignment_ratio(self.host(), value)
1020    }
1021
1022    /// Where the surface is placed vertically: 0 top, 0.5 centred (default),
1023    /// 1 bottom.
1024    pub fn vertical_alignment_ratio(&self) -> f32 {
1025        self.props().vertical_alignment_ratio()
1026    }
1027
1028    pub fn set_vertical_alignment_ratio(&self, value: f32) {
1029        self.props().set_vertical_alignment_ratio(self.host(), value)
1030    }
1031
1032    /// How a bitmap surface is resampled (default `Linear`).
1033    pub fn bitmap_interpolation_mode(&self) -> CompositionBitmapInterpolationMode {
1034        self.props().bitmap_interpolation_mode()
1035    }
1036
1037    pub fn set_bitmap_interpolation_mode(&self, value: CompositionBitmapInterpolationMode) {
1038        self.props().set_bitmap_interpolation_mode(self.host(), value)
1039    }
1040
1041    /// The point of the content, relative to the area of the brush, placed
1042    /// at `offset`.
1043    pub fn anchor_point(&self) -> Vector {
1044        self.props().anchor_point()
1045    }
1046
1047    pub fn set_anchor_point(&self, value: Vector) {
1048        self.props().set_anchor_point(self.host(), value)
1049    }
1050
1051    /// The point the content is scaled and rotated about.
1052    pub fn center_point(&self) -> Vector {
1053        self.props().center_point()
1054    }
1055
1056    pub fn set_center_point(&self, value: Vector) {
1057        self.props().set_center_point(self.host(), value)
1058    }
1059
1060    /// The offset of the content.
1061    pub fn offset(&self) -> Vector {
1062        self.props().offset()
1063    }
1064
1065    pub fn set_offset(&self, value: Vector) {
1066        self.props().set_offset(self.host(), value)
1067    }
1068
1069    /// The rotation of the content about its center point, in radians.
1070    pub fn rotation_angle(&self) -> f32 {
1071        self.props().rotation_angle()
1072    }
1073
1074    pub fn set_rotation_angle(&self, value: f32) {
1075        self.props().set_rotation_angle(self.host(), value)
1076    }
1077
1078    /// The rotation in degrees: a view of
1079    /// [`rotation_angle`](Self::rotation_angle) (not an animation target).
1080    pub fn rotation_angle_in_degrees(&self) -> f32 {
1081        self.rotation_angle().to_degrees()
1082    }
1083
1084    pub fn set_rotation_angle_in_degrees(&self, value: f32) {
1085        self.set_rotation_angle(value.to_radians())
1086    }
1087
1088    /// The scale of the content about its center point (default `(1, 1)`).
1089    pub fn scale(&self) -> Vector {
1090        self.props().scale()
1091    }
1092
1093    pub fn set_scale(&self, value: Vector) {
1094        self.props().set_scale(self.host(), value)
1095    }
1096
1097    /// The transform matrix of the content (WinUI `Matrix3x2`).
1098    pub fn transform_matrix(&self) -> Matrix {
1099        self.props().transform_matrix()
1100    }
1101
1102    pub fn set_transform_matrix(&self, value: Matrix) {
1103        self.props().set_transform_matrix(self.host(), value)
1104    }
1105
1106    /// Whether the content is aligned to device pixels.
1107    pub fn snap_to_pixels(&self) -> bool {
1108        self.props().snap_to_pixels()
1109    }
1110
1111    pub fn set_snap_to_pixels(&self, value: bool) {
1112        self.props().set_snap_to_pixels(self.host(), value)
1113    }
1114}