Skip to main content

ferroui_base/rendering/composition/brushes/
composition_effect_brush.rs

1//! The WinUI `CompositionEffectBrush` and `CompositionBackdropBrush` (phase
2//! 6, not from upstream): composition brushes painted by the visuals that
3//! show them, as the surface brush.
4
5use super::composition_brush::{CompositionBrush, CompositionBrushKind};
6use super::server_composition_effect_brush::EffectBrushChanges;
7use super::{ServerCompositionBackdropBrush, ServerCompositionEffectBrush, ServerCompositionHostBackdropBrush};
8use crate::rendering::composition::effects::{EffectGraph, EffectPropertyValue};
9use crate::rendering::composition::generated::CompositionBrushProps;
10use crate::rendering::composition::transport::{BatchStreamWriter, IRegisterForSerialization};
11use crate::rendering::composition::{AsCompositionObject, CompositionPropertySet, Compositor};
12use std::cell::{Cell, RefCell};
13use std::ops::Deref;
14use std::rc::Rc;
15use std::sync::Arc;
16
17/// What a `CompositionEffectBrush` adds to the generated
18/// `CompositionBrush`: the graph of its factory and the brushes bound to its
19/// source parameters.
20pub(crate) struct CompositionEffectBrushData {
21    pub(super) brush: CompositionBrushProps,
22    pub(super) graph: Arc<EffectGraph>,
23    /// The brushes bound to the source parameters, by name.
24    sources: RefCell<Vec<(String, Option<Rc<CompositionBrush>>)>>,
25    sources_changed: Cell<bool>,
26    /// Whether the server side knows the property set of the brush.
27    properties_sent: Cell<bool>,
28}
29
30impl CompositionEffectBrushData {
31    fn new(graph: Arc<EffectGraph>) -> Self {
32        let sources = graph.parameters.iter().map(|name| (name.clone(), None)).collect();
33        Self {
34            brush: CompositionBrushProps::new(),
35            graph,
36            sources: RefCell::new(sources),
37            sources_changed: Cell::new(false),
38            properties_sent: Cell::new(false),
39        }
40    }
41
42    /// `SerializeChangesCore`: the generated base class, then the property
43    /// set of the brush (once) and the bound brushes (when they changed).
44    pub(super) fn serialize_changes_core(&self, host: &CompositionBrush, writer: &mut BatchStreamWriter<'_>) {
45        self.brush.serialize_changes_core(host, writer);
46        let properties = if self.properties_sent.replace(true) {
47            None
48        } else {
49            host.object().properties().as_composition_object().server()
50        };
51        let sources = self.sources_changed.replace(false).then(|| {
52            self.sources
53                .borrow()
54                .iter()
55                .map(|(name, brush)| (name.clone(), brush.as_ref().map(|brush| brush.server())))
56                .collect::<Vec<_>>()
57        });
58        let changes = (properties.is_some() || sources.is_some()).then_some(EffectBrushChanges { properties, sources });
59        writer.write_value(changes);
60    }
61}
62
63/// A brush that draws an effect graph over its sources (WinUI
64/// `Microsoft.UI.Composition.CompositionEffectBrush`), made by
65/// [`CompositionEffectFactory::create_brush`](crate::rendering::composition::effects::CompositionEffectFactory::create_brush).
66///
67/// The source parameters of the graph are bound to composition brushes
68/// with [`set_source_parameter`](Self::set_source_parameter); the brush
69/// draws the graph over the area of the visual that shows it: a Gaussian
70/// blur is the blur effect of the drawing context around its source
71/// (nothing is blurred on a drawing context without effects), an opacity
72/// pushes an opacity, a cross-fade paints `source1` with `1 - weight` and
73/// `source2` with `weight` over it, a composite paints its sources in
74/// order, a colour source fills the area.
75///
76/// The properties named as animatable when the factory was made
77/// (`Blur.BlurAmount`, `Crossfade.Weight`) are entries of
78/// [`properties`](Self::properties), set to the values of the description:
79/// a change of an entry, or an animation of it (`start_animation` on the
80/// brush or on its properties), redraws the brush.
81///
82/// As the effect of a [`LayerVisual`](crate::rendering::composition::LayerVisual),
83/// the first source parameter of the graph is the composed children of the
84/// layer.
85///
86/// Deviation (winui-composition.md): the cross-fade draws its second source
87/// over the first, each with its weight, rather than adding them; animations
88/// of the effect properties under an animation controller are started on
89/// the properties, not on the brush.
90#[derive(Clone)]
91pub struct CompositionEffectBrush(Rc<CompositionBrush>);
92
93impl Deref for CompositionEffectBrush {
94    type Target = Rc<CompositionBrush>;
95
96    fn deref(&self) -> &Rc<CompositionBrush> {
97        &self.0
98    }
99}
100
101impl CompositionEffectBrush {
102    pub(crate) fn new(compositor: &Rc<Compositor>, graph: Arc<EffectGraph>) -> CompositionEffectBrush {
103        let server_graph = graph.clone();
104        let brush = CompositionBrush::create(
105            compositor,
106            CompositionBrushKind::Effect(CompositionEffectBrushData::new(graph.clone())),
107            move |c| ServerCompositionEffectBrush::new(c, server_graph),
108        );
109        brush.initialize_defaults();
110        // The animatable properties are entries of `Properties`, with the
111        // values of the description.
112        let properties = brush.object().properties();
113        for (key, value) in &graph.animatable {
114            match *value {
115                EffectPropertyValue::Scalar(value) => properties.insert_scalar(key, value),
116                EffectPropertyValue::Color(value) => properties.insert_color(key, value),
117            }
118        }
119        brush.register_for_serialization();
120        CompositionEffectBrush(brush)
121    }
122
123    /// The handle of `brush` as an effect brush, if it is one.
124    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionEffectBrush> {
125        matches!(brush.kind, CompositionBrushKind::Effect(_)).then(|| CompositionEffectBrush(brush.clone()))
126    }
127
128    fn data(&self) -> &CompositionEffectBrushData {
129        match &self.0.kind {
130            CompositionBrushKind::Effect(data) => data,
131            _ => unreachable!("an effect brush handle wraps an effect brush"),
132        }
133    }
134
135    /// The brush as a composition brush.
136    pub fn brush(&self) -> &Rc<CompositionBrush> {
137        &self.0
138    }
139
140    /// `Properties`: the property set whose entries are the animatable
141    /// properties of the effect (`Blur.BlurAmount`). Inserting a value of
142    /// such an entry or animating it changes what the brush draws; other
143    /// entries are the application's.
144    pub fn properties(&self) -> Rc<CompositionPropertySet> {
145        self.0.object().properties()
146    }
147
148    /// The names of the source parameters of the graph, in graph order
149    /// (empty when the factory did not load its effect).
150    pub fn source_parameter_names(&self) -> Vec<String> {
151        self.data().graph.parameters.clone()
152    }
153
154    /// `GetSourceParameter(name)`: the brush bound to the source parameter
155    /// `name`.
156    pub fn get_source_parameter(&self, name: &str) -> Option<Rc<CompositionBrush>> {
157        self.data().sources.borrow().iter().find(|(existing, _)| existing == name).and_then(|(_, brush)| brush.clone())
158    }
159
160    /// `SetSourceParameter(name, source)`: binds the source parameter `name`
161    /// to `source` (`None` unbinds it; an unbound parameter draws nothing).
162    ///
163    /// Panics when the loaded graph has no source parameter of that name
164    /// (WinUI throws). A brush whose factory did not load its effect accepts
165    /// any name.
166    pub fn set_source_parameter(&self, name: &str, source: Option<Rc<CompositionBrush>>) {
167        let data = self.data();
168        if data.graph.is_loaded() && !data.graph.has_parameter(name) {
169            panic!("the effect has no source parameter named {name}");
170        }
171        {
172            let mut sources = data.sources.borrow_mut();
173            match sources.iter_mut().find(|(existing, _)| existing == name) {
174                Some(entry) => entry.1 = source,
175                None => sources.push((name.to_owned(), source)),
176            }
177        }
178        data.sources_changed.set(true);
179        self.0.register_for_serialization();
180    }
181}
182
183/// A brush that shows what is behind the visual that paints it (WinUI
184/// `Microsoft.UI.Composition.CompositionBackdropBrush`), made by
185/// [`Compositor::create_backdrop_brush`]; usually the source of an effect
186/// brush (a blurred backdrop).
187///
188/// The compositor draws again what was drawn under the visual: the tree of
189/// the window from its root up to the visual, cut to the area of the brush
190/// (winui-materials.md, the backdrop).
191#[derive(Clone)]
192pub struct CompositionBackdropBrush(Rc<CompositionBrush>);
193
194impl Deref for CompositionBackdropBrush {
195    type Target = Rc<CompositionBrush>;
196
197    fn deref(&self) -> &Rc<CompositionBrush> {
198        &self.0
199    }
200}
201
202impl CompositionBackdropBrush {
203    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionBackdropBrush {
204        let brush = CompositionBrush::create(
205            compositor,
206            CompositionBrushKind::Backdrop(CompositionBrushProps::new()),
207            |c| ServerCompositionBackdropBrush::new(c),
208        );
209        brush.initialize_defaults();
210        CompositionBackdropBrush(brush)
211    }
212
213    /// The handle of `brush` as a backdrop brush, if it is one.
214    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionBackdropBrush> {
215        matches!(brush.kind, CompositionBrushKind::Backdrop(_)).then(|| CompositionBackdropBrush(brush.clone()))
216    }
217}
218
219/// A brush that shows what is behind the window (WinUI
220/// `Microsoft.UI.Composition.CompositionHostBackdropBrush`), made by
221/// [`Compositor::create_host_backdrop_brush`].
222///
223/// In a window the platform composes over the desktop (a transparency level
224/// other than none) the area of the brush is made transparent, so what is
225/// behind the window shows; the platform blurs it when the window has the
226/// blur or acrylic level. In an opaque window the brush paints nothing
227/// (winui-materials.md).
228#[derive(Clone)]
229pub struct CompositionHostBackdropBrush(Rc<CompositionBrush>);
230
231impl Deref for CompositionHostBackdropBrush {
232    type Target = Rc<CompositionBrush>;
233
234    fn deref(&self) -> &Rc<CompositionBrush> {
235        &self.0
236    }
237}
238
239impl CompositionHostBackdropBrush {
240    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionHostBackdropBrush {
241        let brush = CompositionBrush::create(
242            compositor,
243            CompositionBrushKind::HostBackdrop(CompositionBrushProps::new()),
244            |c| ServerCompositionHostBackdropBrush::new(c),
245        );
246        brush.initialize_defaults();
247        CompositionHostBackdropBrush(brush)
248    }
249
250    /// The handle of `brush` as a host backdrop brush, if it is one.
251    pub fn from_brush(brush: &Rc<CompositionBrush>) -> Option<CompositionHostBackdropBrush> {
252        matches!(brush.kind, CompositionBrushKind::HostBackdrop(_)).then(|| CompositionHostBackdropBrush(brush.clone()))
253    }
254}