Skip to main content

ferroui_base/rendering/composition/server/
server_sprite_visual.rs

1use super::{
2    paint_composition_brush, CompositionProperty, IServerObject, IServerRenderResourceObserver, IServerVisualContent,
3    ServerCompositionVisual, ServerDropShadow, ServerSizeDependantVisual,
4    ServerVisualRenderContext,
5};
6use crate::platform::LtrbRect;
7use crate::rendering::composition::generated::{
8    ServerCompositionContainerVisualProps, ServerSpriteVisualHooks, ServerSpriteVisualProps,
9};
10use crate::rendering::composition::transport::BatchStreamReader;
11use crate::Rect;
12use std::any::{Any, TypeId};
13use std::rc::Rc;
14use std::time::Duration;
15
16/// Server-side counterpart of a
17/// [`SpriteVisual`](crate::rendering::composition::SpriteVisual): the shadow
18/// of its rectangle, of its mask or of the alpha of its brush (see
19/// [`ServerDropShadow::render_under_rect`] and
20/// [`ServerDropShadow::render_shadow_of`]), then its brush over
21/// `(0, 0, size)`, then its children.
22///
23/// The visual observes its brush and its shadow, which are render
24/// resources: a change of either (an animation included) redraws it.
25#[derive(Default)]
26pub struct ServerSpriteVisual {
27    props: ServerSpriteVisualProps,
28}
29
30impl ServerSpriteVisual {
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// The properties of the visual.
36    pub fn props(&self) -> &ServerSpriteVisualProps {
37        &self.props
38    }
39
40    /// The brush: a server-side composition brush.
41    pub fn brush(&self) -> Option<Rc<dyn IServerObject>> {
42        self.props.brush()
43    }
44
45    /// The shadow.
46    pub fn shadow(&self) -> Option<Rc<ServerDropShadow>> {
47        self.props.shadow().and_then(|shadow| shadow.into_any_rc().downcast::<ServerDropShadow>().ok())
48    }
49
50    /// Starts or stops observing a render resource the visual shows.
51    fn observe(visual: &ServerCompositionVisual, resource: Option<Rc<dyn IServerObject>>, observe: bool) {
52        let Some(resource) = resource.and_then(|resource| resource.as_render_resource()) else { return };
53        let Some(this) = visual.weak().upgrade() else { return };
54        let observer: Rc<dyn IServerRenderResourceObserver> = this;
55        if observe {
56            resource.add_observer(&observer);
57        } else {
58            resource.remove_observer(&observer);
59        }
60    }
61
62    fn rect(visual: &ServerCompositionVisual) -> Rect {
63        let size = visual.size();
64        Rect::new(0.0, 0.0, size.x, size.y)
65    }
66}
67
68impl ServerSpriteVisualHooks for ServerCompositionVisual {
69    fn base_deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
70        ServerCompositionVisual::base_deserialize_changes_core(self, reader, committed_at);
71    }
72
73    fn on_brush_changing(&self) {
74        if let Some(content) = self.content_as::<ServerSpriteVisual>() {
75            ServerSpriteVisual::observe(self, content.brush(), false);
76        }
77    }
78
79    fn on_brush_changed(&self) {
80        if let Some(content) = self.content_as::<ServerSpriteVisual>() {
81            ServerSpriteVisual::observe(self, content.brush(), true);
82        }
83        self.invalidate_content();
84    }
85
86    fn on_shadow_changing(&self) {
87        if let Some(content) = self.content_as::<ServerSpriteVisual>() {
88            ServerSpriteVisual::observe(self, content.props.shadow(), false);
89        }
90    }
91
92    fn on_shadow_changed(&self) {
93        if let Some(content) = self.content_as::<ServerSpriteVisual>() {
94            ServerSpriteVisual::observe(self, content.props.shadow(), true);
95        }
96        self.invalidate_content();
97    }
98}
99
100impl IServerVisualContent for ServerSpriteVisual {
101    fn deserialize_changes_core(
102        &self,
103        visual: &ServerCompositionVisual,
104        reader: &mut BatchStreamReader<'_>,
105        committed_at: Duration,
106    ) {
107        self.props.deserialize_changes_core(visual, reader, committed_at);
108    }
109
110    /// The rectangle of the visual, with the area of its shadow.
111    fn compute_own_content_bounds(&self, visual: &ServerCompositionVisual) -> Option<LtrbRect> {
112        let bounds = ServerSizeDependantVisual::compute_own_content_bounds(visual)?;
113        match self.shadow() {
114            Some(shadow) => Some(LtrbRect::from_rect(shadow.bounds_in(Some(visual), bounds.to_rect()))),
115            None => Some(bounds),
116        }
117    }
118
119    fn size_changed(&self, visual: &ServerCompositionVisual) {
120        ServerSizeDependantVisual::size_changed(visual);
121    }
122
123    fn render_core(
124        &self,
125        visual: &ServerCompositionVisual,
126        context: &mut ServerVisualRenderContext<'_>,
127        _current_transformed_clip: LtrbRect,
128    ) {
129        let rect = Self::rect(visual);
130        if rect.width <= 0.0 || rect.height <= 0.0 {
131            return;
132        }
133        let brush = self.brush();
134        if let Some(shadow) = self.shadow().filter(|shadow| shadow.is_visible()) {
135            if shadow.inherits_from_visual_content() {
136                // `InheritFromVisualContent`: the shadow of the alpha of the
137                // brush; a sprite without a brush has no content to cast one.
138                if let Some(brush) = &brush {
139                    let drawn = shadow.render_shadow_of(context.canvas(), Some(visual), rect, |canvas| {
140                        paint_composition_brush(canvas, brush, rect)
141                    });
142                    if !drawn {
143                        shadow.render_under_rect_in(context.canvas(), Some(visual), rect);
144                    }
145                }
146            } else {
147                // The shadow of the mask, or of the rectangle.
148                shadow.render_under_rect_in(context.canvas(), Some(visual), rect);
149            }
150        }
151        if let Some(brush) = brush {
152            paint_composition_brush(context.canvas(), &brush, rect);
153        }
154    }
155
156    /// The brush or the shadow changed.
157    fn dependency_queued_invalidate(&self, visual: &ServerCompositionVisual) {
158        visual.invalidate_content();
159    }
160
161    fn dispose(&self, visual: &ServerCompositionVisual) {
162        ServerSpriteVisual::observe(visual, self.brush(), false);
163        ServerSpriteVisual::observe(visual, self.props.shadow(), false);
164    }
165
166    fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
167        self.props.find_props(type_id)
168    }
169
170    fn get_composition_property(&self, name: &str) -> Option<&'static CompositionProperty> {
171        ServerCompositionContainerVisualProps::get_composition_property(name)
172    }
173
174    fn as_any(&self) -> &dyn Any {
175        self
176    }
177}