Skip to main content

ferroui_base/rendering/composition/effects/
composition_effect_factory.rs

1//! `CompositionEffectFactory` and the factories of the effect and backdrop
2//! brushes (`Compositor.CreateEffectFactory`, `CreateBackdropBrush` of
3//! `Microsoft.UI.Composition`, phase 6).
4
5use super::effect_graph::EffectGraph;
6use super::{CanvasComposite, GraphicsEffect};
7use crate::rendering::composition::{
8    CompositionBackdropBrush, CompositionEffectBrush, CompositionHostBackdropBrush, Compositor,
9};
10use std::rc::Rc;
11use std::sync::Arc;
12
13/// Whether an effect factory loaded its effect (WinUI
14/// `Microsoft.UI.Composition.CompositionEffectFactoryLoadStatus`).
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16#[repr(i32)]
17pub enum CompositionEffectFactoryLoadStatus {
18    /// The effect was loaded; its brushes draw it.
19    #[default]
20    Success = 0,
21    /// The graph has too many effects or is nested too deeply.
22    EffectTooComplex = 1,
23    /// The effect is still loading (never reported: loading is immediate).
24    Pending = 2,
25    /// The effect cannot be loaded: an unsupported effect type or composite
26    /// mode, a missing source, an animatable property that does not exist.
27    /// [`CompositionEffectFactory::extended_error`] says which.
28    Other = -1,
29}
30
31/// Why an effect factory did not load its effect: the `ExtendedError` of
32/// WinUI's factory, as a value.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum CompositionEffectFactoryError {
35    /// An effect of the graph is of a type the drawing contexts cannot
36    /// draw (the name of its Win2D class).
37    EffectTypeNotSupported(&'static str),
38    /// A composite effect has a mode the drawing contexts do not have
39    /// (`MaskInvert`).
40    CompositeModeNotSupported(CanvasComposite),
41    /// An effect of the graph has an input that is not set.
42    MissingSource {
43        /// The Win2D class of the effect.
44        effect: &'static str,
45        /// The name of the effect.
46        name: String,
47    },
48    /// An animatable property name does not name an animatable property of
49    /// a named effect of the graph (`EffectName.PropertyName`).
50    InvalidAnimatableProperty(String),
51    /// The graph has too many effects or is nested too deeply.
52    EffectTooComplex,
53}
54
55impl std::fmt::Display for CompositionEffectFactoryError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            CompositionEffectFactoryError::EffectTypeNotSupported(effect) => {
59                write!(f, "the effect type {effect} is not supported")
60            }
61            CompositionEffectFactoryError::CompositeModeNotSupported(mode) => {
62                write!(f, "the composite mode {mode:?} is not supported")
63            }
64            CompositionEffectFactoryError::MissingSource { effect, name } => {
65                write!(f, "the {effect} '{name}' has an input that is not set")
66            }
67            CompositionEffectFactoryError::InvalidAnimatableProperty(key) => {
68                write!(f, "'{key}' is not an animatable property of the effect")
69            }
70            CompositionEffectFactoryError::EffectTooComplex => f.write_str("the effect graph is too complex"),
71        }
72    }
73}
74
75impl std::error::Error for CompositionEffectFactoryError {}
76
77/// Creates the brushes of one effect graph (WinUI
78/// `Microsoft.UI.Composition.CompositionEffectFactory`), made by
79/// [`Compositor::create_effect_factory`].
80///
81/// The graph is checked when the factory is made: [`load_status`](Self::load_status)
82/// is `Success` when the brushes can draw it. A factory whose effect cannot
83/// be loaded still makes brushes, which draw nothing.
84///
85/// Deviation (winui-composition.md): WinUI throws from `CreateEffectFactory`
86/// for an effect it cannot load; here the factory is made and reports the
87/// failure through its load status and extended error. The factory is not a
88/// composition object (no `Properties`, no animations).
89pub struct CompositionEffectFactory {
90    compositor: Rc<Compositor>,
91    graph: Arc<EffectGraph>,
92    load_status: CompositionEffectFactoryLoadStatus,
93    extended_error: Option<CompositionEffectFactoryError>,
94}
95
96impl CompositionEffectFactory {
97    fn new(compositor: Rc<Compositor>, effect: &GraphicsEffect, animatable_properties: &[&str]) -> Rc<Self> {
98        let (graph, load_status, extended_error) = match EffectGraph::load(effect, animatable_properties) {
99            Ok(graph) => (graph, CompositionEffectFactoryLoadStatus::Success, None),
100            Err((status, error)) => (EffectGraph::failed(), status, Some(error)),
101        };
102        Rc::new(Self { compositor, graph: Arc::new(graph), load_status, extended_error })
103    }
104
105    /// The compositor of the factory.
106    pub fn compositor(&self) -> &Rc<Compositor> {
107        &self.compositor
108    }
109
110    /// `CreateBrush`: a brush that draws the effect. Its source parameters
111    /// are bound with
112    /// [`set_source_parameter`](CompositionEffectBrush::set_source_parameter);
113    /// its animatable properties are entries of its `Properties`, set to the
114    /// values of the description.
115    pub fn create_brush(&self) -> CompositionEffectBrush {
116        CompositionEffectBrush::new(&self.compositor, self.graph.clone())
117    }
118
119    /// `LoadStatus`: whether the effect was loaded.
120    pub fn load_status(&self) -> CompositionEffectFactoryLoadStatus {
121        self.load_status
122    }
123
124    /// `ExtendedError`: why the effect was not loaded; `None` when it was.
125    pub fn extended_error(&self) -> Option<&CompositionEffectFactoryError> {
126        self.extended_error.as_ref()
127    }
128}
129
130impl Compositor {
131    /// `CreateEffectFactory(graphicsEffect)`: a factory of brushes that draw
132    /// `effect`, without animatable properties.
133    pub fn create_effect_factory(&self, effect: impl Into<GraphicsEffect>) -> Rc<CompositionEffectFactory> {
134        self.create_effect_factory_with_properties(effect, &[])
135    }
136
137    /// `CreateEffectFactory(graphicsEffect, animatableProperties)`: a factory
138    /// of brushes that draw `effect`, whose properties named in
139    /// `animatable_properties` (`EffectName.PropertyName`, as
140    /// `Blur.BlurAmount`) can be changed and animated on the brushes.
141    pub fn create_effect_factory_with_properties(
142        &self,
143        effect: impl Into<GraphicsEffect>,
144        animatable_properties: &[&str],
145    ) -> Rc<CompositionEffectFactory> {
146        CompositionEffectFactory::new(self.this_handle(), &effect.into(), animatable_properties)
147    }
148
149    /// `CreateBackdropBrush`: a brush that shows what is behind the visual
150    /// that paints it: the compositor draws again what was drawn under the
151    /// visual (winui-materials.md, the backdrop).
152    pub fn create_backdrop_brush(&self) -> CompositionBackdropBrush {
153        CompositionBackdropBrush::new(&self.this_handle())
154    }
155
156    /// `CreateHostBackdropBrush`: a brush that shows what is behind the
157    /// window (see [`CompositionHostBackdropBrush`]).
158    pub fn create_host_backdrop_brush(&self) -> CompositionHostBackdropBrush {
159        CompositionHostBackdropBrush::new(&self.this_handle())
160    }
161}