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}