Skip to main content

ferroui_base/rendering/composition/brushes/
server_xaml_composition_brush.rs

1//! The server-side counterpart of a XAML composition brush
2//! ([`XamlCompositionBrushBase`](crate::media::XamlCompositionBrushBase),
3//! winui-materials.md): it paints the composition brush the XAML brush set,
4//! or its fallback colour.
5
6use super::paint_composition_brush;
7use crate::media::immutable::ImmutableSolidColorBrush;
8use crate::media::{BoxShadows, Color, XamlBrushChanges};
9use crate::platform::IDrawingContextImpl;
10use crate::rendering::composition::generated::{ServerCompositionSimpleBrushHooks, ServerCompositionSimpleBrushProps};
11use crate::rendering::composition::server::{
12    impl_simple_server_render_resource, with_brush_light_ids, IServerObject, IServerRenderResource,
13    IServerRenderResourceObserver, ServerCompositor, SimpleServerRenderResource,
14};
15use crate::rendering::composition::transport::BatchStreamReader;
16use crate::{Rect, RoundedRect};
17use std::any::{Any, TypeId};
18use std::cell::{Cell, RefCell};
19use std::rc::{Rc, Weak};
20use std::sync::Arc;
21use std::time::Duration;
22
23/// The server-side counterpart of a XAML composition brush.
24pub struct ServerXamlCompositionBrush {
25    base: SimpleServerRenderResource,
26    this: Weak<ServerXamlCompositionBrush>,
27    props: ServerCompositionSimpleBrushProps,
28    fallback_color: Cell<Color>,
29    composition_brush: RefCell<Option<Rc<dyn IServerObject>>>,
30    light_target_ids: RefCell<Arc<[String]>>,
31    needs_transparent_window: Cell<bool>,
32    /// Set while the brush paints: a composition brush that paints this one
33    /// is not painted inside itself.
34    painting: Cell<bool>,
35}
36
37impl ServerXamlCompositionBrush {
38    pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerXamlCompositionBrush> {
39        Rc::new_cyclic(|this: &Weak<ServerXamlCompositionBrush>| ServerXamlCompositionBrush {
40            base: SimpleServerRenderResource::new(compositor, this.clone() as Weak<dyn IServerRenderResource>),
41            this: this.clone(),
42            props: ServerCompositionSimpleBrushProps::new(),
43            fallback_color: Cell::new(Color::default()),
44            composition_brush: RefCell::new(None),
45            light_target_ids: RefCell::new(Arc::from(Vec::<String>::new())),
46            needs_transparent_window: Cell::new(false),
47            painting: Cell::new(false),
48        })
49    }
50
51    pub fn is_disposed(&self) -> bool {
52        self.base.is_disposed()
53    }
54
55    /// The brush properties (opacity, transforms).
56    pub fn brush_props(&self) -> &ServerCompositionSimpleBrushProps {
57        &self.props
58    }
59
60    /// The fallback colour of the XAML brush.
61    pub fn fallback_color(&self) -> Color {
62        self.fallback_color.get()
63    }
64
65    /// The composition brush the XAML brush paints with.
66    pub fn composition_brush(&self) -> Option<Rc<dyn IServerObject>> {
67        self.composition_brush.borrow().clone()
68    }
69
70    /// The ids of the XAML lights that target the brush.
71    pub fn light_target_ids(&self) -> Arc<[String]> {
72        self.light_target_ids.borrow().clone()
73    }
74
75    fn observer(&self) -> Option<Rc<dyn IServerRenderResourceObserver>> {
76        self.this.upgrade().map(|this| this as Rc<dyn IServerRenderResourceObserver>)
77    }
78
79    /// Paints over `rect`: the composition brush, with the light ids of the
80    /// brush offered to the scene lighting it draws, or the fallback colour.
81    pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
82        let opacity = self.props.opacity();
83        if opacity <= 0.0 || rect.width <= 0.0 || rect.height <= 0.0 || self.painting.replace(true) {
84            return;
85        }
86        if opacity < 1.0 {
87            canvas.push_opacity(opacity, Some(rect));
88        }
89        // A brush that shows what is behind the window, in a window the
90        // platform shows nothing behind: the fallback colour.
91        let window_is_opaque = self.needs_transparent_window.get()
92            && crate::rendering::composition::server::painting_visual()
93                .and_then(|visual| visual.root())
94                .is_none_or(|target| {
95                    target.transparency_level() == crate::rendering::composition::CompositionTransparencyLevel::None
96                });
97        match self.composition_brush().filter(|_| !window_is_opaque) {
98            Some(brush) => {
99                let ids = self.light_target_ids();
100                with_brush_light_ids(ids, || paint_composition_brush(canvas, &brush, rect));
101            }
102            None => {
103                let color = self.fallback_color();
104                if color.a > 0 {
105                    let brush = ImmutableSolidColorBrush::new(color);
106                    canvas.draw_rectangle(Some(&brush), None, RoundedRect::from_rect(rect), &BoxShadows::default());
107                }
108            }
109        }
110        if opacity < 1.0 {
111            canvas.pop_opacity();
112        }
113        self.painting.set(false);
114    }
115
116    fn set_composition_brush(&self, brush: Option<Rc<dyn IServerObject>>) {
117        let old = std::mem::replace(&mut *self.composition_brush.borrow_mut(), brush);
118        let Some(observer) = self.observer() else { return };
119        if let Some(old) = old.and_then(|old| old.as_render_resource()) {
120            old.remove_observer(&observer);
121        }
122        if let Some(new) = self.composition_brush().and_then(|new| new.as_render_resource()) {
123            new.add_observer(&observer);
124        }
125    }
126}
127
128impl_simple_server_render_resource!(ServerXamlCompositionBrush, base);
129
130impl ServerCompositionSimpleBrushHooks for ServerXamlCompositionBrush {}
131
132impl IServerObject for ServerXamlCompositionBrush {
133    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
134        self.props.deserialize_changes_core(self, reader, committed_at);
135        let Some(changes) = reader.read_value::<XamlBrushChanges>() else { return };
136        self.fallback_color.set(changes.fallback_color);
137        self.needs_transparent_window.set(changes.needs_transparent_window);
138        *self.light_target_ids.borrow_mut() = Arc::from(changes.light_target_ids);
139        let compositor = self.base.compositor();
140        let brush = changes.composition_brush.and_then(|id| compositor.as_ref().and_then(|compositor| compositor.get_object(id)));
141        self.set_composition_brush(brush);
142    }
143
144    fn values_invalidated(&self) {
145        self.base.core().invalidated(self);
146    }
147
148    fn dispose(&self) {
149        self.set_composition_brush(None);
150        self.base.core().dispose();
151    }
152
153    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
154        Some(self)
155    }
156
157    /// The brush as the compositor paints it (a XAML brush is painted by the compositor, not
158    /// handed to the drawing context), so it can also be the brush of a pen: the border brush
159    /// of a Reveal border.
160    fn as_brush(self: Rc<Self>) -> Option<Rc<dyn crate::media::IBrush>> {
161        let object: Rc<dyn IServerObject> = self;
162        crate::rendering::composition::drawing::painted_brush_of(&object)
163    }
164
165    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
166        self.props.find_props(type_id)
167    }
168
169    fn as_any(&self) -> &dyn Any {
170        self
171    }
172
173    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
174        self
175    }
176}