Skip to main content

ferroui_base/media/
xaml_composition_brush_base.rs

1//! `XamlCompositionBrushBase` of WinUI (`Microsoft.UI.Xaml.Media`): the base
2//! of the brushes of the object model that paint with a composition brush,
3//! such as [`AcrylicBrush`](super::AcrylicBrush) (winui-materials.md).
4
5use crate::media::ref_adapter::RefAdapter;
6use crate::media::{Brush, BrushImpl, BrushImplExt, Color, IBrush, ServerBrushFactory};
7use crate::rendering::composition::brushes::ServerXamlCompositionBrush;
8use crate::rendering::composition::transport::BatchStreamWriter;
9use crate::rendering::composition::{CompositionBrush, Compositor};
10use crate::{ferro_class, ferro_property, FerroObjectImpl, FerroProperty, Ref, StyledProperty};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14/// What a XAML composition brush sends to its server side with each batch:
15/// its fallback colour, the composition brush it paints with and the ids of
16/// the XAML lights that target it.
17pub(crate) struct XamlBrushChanges {
18    pub(crate) fallback_color: Color,
19    pub(crate) composition_brush: Option<crate::rendering::composition::server::ServerObjectId>,
20    pub(crate) light_target_ids: Vec<String>,
21    pub(crate) needs_transparent_window: bool,
22}
23
24#[derive(Default)]
25struct XamlBrushState {
26    /// `CompositionBrush`: what the brush paints with.
27    composition_brush: Option<Rc<CompositionBrush>>,
28    /// The compositor the brush is connected to.
29    compositor: Option<Rc<Compositor>>,
30    /// The ids of the XAML lights that target the brush
31    /// (`XamlLight.AddTargetBrush`).
32    light_target_ids: Vec<String>,
33    /// Whether the composition brush shows what is behind the window (a
34    /// host backdrop): where the window is opaque the fallback colour is
35    /// painted instead.
36    needs_transparent_window: bool,
37}
38
39/// A brush that paints an area with a composition brush (WinUI
40/// `Microsoft.UI.Xaml.Media.XamlCompositionBrushBase`).
41///
42/// A subclass creates its [`composition_brush`](Self::composition_brush)
43/// when the brush is first used in a window that has a compositor
44/// (`OnConnected`) and releases it when no element of such a window uses it
45/// any more (`OnDisconnected`). The compositor paints the composition brush
46/// inside the shape the brush fills, as the brushes of WinUI's composition
47/// are painted; where no composition brush is set, and where the brush is
48/// drawn without a compositor (an image of a visual drawn by the immediate
49/// renderer), the area is filled with [`fallback_color`](Self::fallback_color).
50#[repr(C)]
51pub struct XamlCompositionBrushBase {
52    base: Brush,
53    state: RefCell<XamlBrushState>,
54}
55
56ferro_class! {
57    XamlCompositionBrushBase: Brush, virtuals XamlCompositionBrushBaseImpl: BrushImpl {
58        /// `OnConnected`: the brush is used in a window with a compositor for
59        /// the first time; the subclass creates its composition brush.
60        fn on_connected(this);
61
62        /// `OnDisconnected`: no element of a window with a compositor uses
63        /// the brush any more; the subclass releases its composition brush.
64        fn on_disconnected(this);
65    }
66}
67
68crate::ferro_impl_classes!(XamlCompositionBrushBase: FerroObjectImpl);
69
70impl BrushImpl for XamlCompositionBrushBase {
71    fn factory(_this: &Self) -> Option<ServerBrushFactory> {
72        Some(|c| ServerXamlCompositionBrush::new(c))
73    }
74
75    fn on_referenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
76        Self::parent_on_referenced_from_compositor(this, c);
77        let connect = {
78            let mut state = this.state.borrow_mut();
79            if state.compositor.is_none() {
80                state.compositor = Some(c.clone());
81                true
82            } else {
83                false
84            }
85        };
86        if connect {
87            this.on_connected();
88        }
89    }
90
91    fn on_unreferenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
92        Self::parent_on_unreferenced_from_compositor(this, c);
93        let disconnect = this.state.borrow().compositor.as_ref().is_some_and(|compositor| Rc::ptr_eq(compositor, c));
94        if disconnect {
95            this.on_disconnected();
96            this.state.borrow_mut().compositor = None;
97        }
98    }
99
100    fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
101        Self::parent_serialize_changes(this, c, writer);
102        let state = this.state.borrow();
103        let composition_brush = state
104            .composition_brush
105            .as_ref()
106            .filter(|brush| std::ptr::eq(&**brush.compositor(), c) && !brush.is_disposed())
107            .map(|brush| brush.server());
108        writer.write_value(Some(XamlBrushChanges {
109            fallback_color: this.fallback_color(),
110            composition_brush,
111            light_target_ids: state.light_target_ids.clone(),
112            needs_transparent_window: state.needs_transparent_window,
113        }));
114    }
115}
116
117impl XamlCompositionBrushBaseImpl for XamlCompositionBrushBase {
118    fn on_connected(_this: &Self) {}
119
120    fn on_disconnected(_this: &Self) {}
121}
122
123crate::ferro_properties! { impl XamlCompositionBrushBase {
124    ferro_property!(
125        /// `FallbackColor`: the colour the brush fills with when it has no
126        /// composition brush, or cannot use it.
127        pub fn fallback_color_property() -> StyledProperty<Color> {
128            FerroProperty::register::<XamlCompositionBrushBase, _>("FallbackColor", Color::default())
129        }
130    );
131} }
132
133impl XamlCompositionBrushBase {
134    /// Creates the class data; see [`FerroObject::construct`](crate::FerroObject).
135    pub fn construct() -> Self {
136        Self { base: Brush::construct(), state: RefCell::new(XamlBrushState::default()) }
137    }
138
139    /// `FallbackColor`.
140    pub fn fallback_color(&self) -> Color {
141        self.get_value(Self::fallback_color_property())
142    }
143
144    pub fn set_fallback_color(&self, value: Color) {
145        self.set_value(Self::fallback_color_property(), value)
146    }
147
148    /// `CompositionBrush`: the composition brush the brush paints with, set
149    /// by the subclass.
150    pub fn composition_brush(&self) -> Option<Rc<CompositionBrush>> {
151        self.state.borrow().composition_brush.clone()
152    }
153
154    /// Sets the composition brush the brush paints with (`None` paints the
155    /// fallback colour). It must belong to the compositor the brush is
156    /// connected to.
157    pub fn set_composition_brush(&self, brush: Option<Rc<CompositionBrush>>) {
158        {
159            let mut state = self.state.borrow_mut();
160            let same = match (&state.composition_brush, &brush) {
161                (Some(a), Some(b)) => Rc::ptr_eq(a, b),
162                (None, None) => true,
163                _ => false,
164            };
165            if same {
166                return;
167            }
168            state.composition_brush = brush;
169        }
170        // The server brush paints the new composition brush: the render
171        // data of the elements that use the brush is unchanged (and a brush
172        // that connects while the renderer draws must not invalidate them).
173        self.register_for_serialization();
174    }
175
176    /// Whether the composition brush shows what is behind the window (a host
177    /// backdrop): where the window of the element is opaque, the platform
178    /// shows nothing behind it, and the fallback colour is painted instead.
179    pub fn needs_transparent_window(&self) -> bool {
180        self.state.borrow().needs_transparent_window
181    }
182
183    pub fn set_needs_transparent_window(&self, value: bool) {
184        if self.state.borrow().needs_transparent_window == value {
185            return;
186        }
187        self.state.borrow_mut().needs_transparent_window = value;
188        self.register_for_serialization();
189    }
190
191    /// The compositor the brush is connected to (`None` while it is not).
192    pub fn compositor(&self) -> Option<Rc<Compositor>> {
193        self.state.borrow().compositor.clone()
194    }
195
196    /// Whether the brush is connected (`OnConnected` was called and
197    /// `OnDisconnected` was not since).
198    pub fn is_connected(&self) -> bool {
199        self.state.borrow().compositor.is_some()
200    }
201
202    /// The ids of the XAML lights that target the brush.
203    pub fn light_target_ids(&self) -> Vec<String> {
204        self.state.borrow().light_target_ids.clone()
205    }
206
207    /// `XamlLight.AddTargetBrush(id, this)`: the XAML lights of id `id`
208    /// light the brush.
209    pub fn add_light_target_id(&self, id: &str) {
210        {
211            let mut state = self.state.borrow_mut();
212            if state.light_target_ids.iter().any(|existing| existing == id) {
213                return;
214            }
215            state.light_target_ids.push(id.to_owned());
216        }
217        self.register_for_serialization();
218    }
219
220    /// `XamlLight.RemoveTargetBrush(id, this)`.
221    pub fn remove_light_target_id(&self, id: &str) {
222        {
223            let mut state = self.state.borrow_mut();
224            let count = state.light_target_ids.len();
225            state.light_target_ids.retain(|existing| existing != id);
226            if state.light_target_ids.len() == count {
227                return;
228            }
229        }
230        self.register_for_serialization();
231    }
232
233    /// The XAML composition brush `brush` is, if it is one.
234    pub fn of(brush: &dyn IBrush) -> Option<Ref<XamlCompositionBrushBase>> {
235        brush.as_object().and_then(|object| object.downcast_ref::<XamlCompositionBrushBase>()).map(|brush| brush.to_ref())
236    }
237
238    /// The brush as `Rc<dyn IBrush>`.
239    pub fn as_brush(&self) -> Rc<dyn IBrush> {
240        Rc::new(RefAdapter(self.to_ref()))
241    }
242}
243
244crate::ferro_class_info!(XamlCompositionBrushBase {
245    markup: {
246        namespace: "FerroUI.Media",
247    },
248});