Skip to main content

ferroui_base/rendering/composition/server/
server_shape_visual.rs

1use super::{
2    render_shapes, CompositionProperty, IServerObject, IServerRenderResourceObserver, IServerVisualContent,
3    ServerCompositionShapeCollection, ServerCompositionViewBox, ServerCompositionVisual, ServerSizeDependantVisual,
4    ServerVisualRenderContext, ViewBoxMapping,
5};
6use crate::platform::LtrbRect;
7use crate::rendering::composition::generated::{
8    ServerCompositionContainerVisualProps, ServerShapeVisualHooks, ServerShapeVisualProps,
9};
10use crate::rendering::composition::transport::BatchStreamReader;
11use crate::{Matrix, Rect};
12use std::any::{Any, TypeId};
13use std::rc::Rc;
14use std::time::Duration;
15
16/// Server-side counterpart of a
17/// [`ShapeVisual`](crate::rendering::composition::ShapeVisual): its shapes,
18/// through its view box, clipped to its size, then its children.
19///
20/// The visual observes its shape collection and its view box, which are
21/// render resources: a change of either, or of anything a shape shows (an
22/// animation included), redraws it.
23#[derive(Default)]
24pub struct ServerShapeVisual {
25    props: ServerShapeVisualProps,
26}
27
28impl ServerShapeVisual {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// The properties of the visual.
34    pub fn props(&self) -> &ServerShapeVisualProps {
35        &self.props
36    }
37
38    /// The shapes, in drawing order.
39    pub fn shapes(&self) -> Vec<Rc<dyn IServerObject>> {
40        self.props
41            .shapes()
42            .as_ref()
43            .and_then(ServerCompositionShapeCollection::of)
44            .map(ServerCompositionShapeCollection::shapes)
45            .unwrap_or_default()
46    }
47
48    /// How the view box maps the shapes onto a visual of `size`, when the
49    /// visual has a view box that maps anything.
50    pub fn view_box_mapping(&self, size: crate::Vector) -> Option<ViewBoxMapping> {
51        ServerCompositionViewBox::mapping_of(&self.props.view_box()?, size)
52    }
53
54    /// Starts or stops observing a render resource the visual shows.
55    fn observe(visual: &ServerCompositionVisual, resource: Option<Rc<dyn IServerObject>>, observe: bool) {
56        let Some(resource) = resource.and_then(|resource| resource.as_render_resource()) else { return };
57        let Some(this) = visual.weak().upgrade() else { return };
58        let observer: Rc<dyn IServerRenderResourceObserver> = this;
59        if observe {
60            resource.add_observer(&observer);
61        } else {
62            resource.remove_observer(&observer);
63        }
64    }
65}
66
67impl ServerShapeVisualHooks for ServerCompositionVisual {
68    fn base_deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
69        ServerCompositionVisual::base_deserialize_changes_core(self, reader, committed_at);
70    }
71
72    fn on_shapes_changing(&self) {
73        if let Some(content) = self.content_as::<ServerShapeVisual>() {
74            ServerShapeVisual::observe(self, content.props.shapes(), false);
75        }
76    }
77
78    fn on_shapes_changed(&self) {
79        if let Some(content) = self.content_as::<ServerShapeVisual>() {
80            ServerShapeVisual::observe(self, content.props.shapes(), true);
81        }
82        self.invalidate_content();
83    }
84
85    fn on_view_box_changing(&self) {
86        if let Some(content) = self.content_as::<ServerShapeVisual>() {
87            ServerShapeVisual::observe(self, content.props.view_box(), false);
88        }
89    }
90
91    fn on_view_box_changed(&self) {
92        if let Some(content) = self.content_as::<ServerShapeVisual>() {
93            ServerShapeVisual::observe(self, content.props.view_box(), true);
94        }
95        self.invalidate_content();
96    }
97}
98
99impl IServerVisualContent for ServerShapeVisual {
100    fn deserialize_changes_core(
101        &self,
102        visual: &ServerCompositionVisual,
103        reader: &mut BatchStreamReader<'_>,
104        committed_at: Duration,
105    ) {
106        self.props.deserialize_changes_core(visual, reader, committed_at);
107    }
108
109    /// The size of the visual: the shapes are clipped to it.
110    fn compute_own_content_bounds(&self, visual: &ServerCompositionVisual) -> Option<LtrbRect> {
111        ServerSizeDependantVisual::compute_own_content_bounds(visual)
112    }
113
114    fn size_changed(&self, visual: &ServerCompositionVisual) {
115        ServerSizeDependantVisual::size_changed(visual);
116    }
117
118    /// Draws the shapes in order, clipped to the size of the visual and to
119    /// the mapped view box.
120    ///
121    /// Deviation (winui-composition.md): the shapes are clipped to the size
122    /// of the visual (a shape visual without a size shows nothing, as in
123    /// WinUI) and to the view box (the port's reading of WinUI).
124    fn render_core(
125        &self,
126        visual: &ServerCompositionVisual,
127        context: &mut ServerVisualRenderContext<'_>,
128        _current_transformed_clip: LtrbRect,
129    ) {
130        let size = visual.size();
131        if size.x <= 0.0 || size.y <= 0.0 {
132            return;
133        }
134        let shapes = self.shapes();
135        if shapes.is_empty() {
136            return;
137        }
138        let mapping = self.view_box_mapping(size);
139        let canvas = context.canvas();
140        let base = canvas.transform();
141        canvas.push_clip(Rect::new(0.0, 0.0, size.x, size.y));
142        if let Some(mapping) = &mapping {
143            canvas.push_clip(mapping.clip);
144        }
145        let parent = mapping.map_or(Matrix::IDENTITY, |mapping| mapping.transform);
146        render_shapes(canvas, &shapes, parent, base);
147        canvas.set_transform(base);
148        if mapping.is_some() {
149            canvas.pop_clip();
150        }
151        canvas.pop_clip();
152    }
153
154    /// The shape collection or the view box changed.
155    fn dependency_queued_invalidate(&self, visual: &ServerCompositionVisual) {
156        visual.invalidate_content();
157    }
158
159    fn dispose(&self, visual: &ServerCompositionVisual) {
160        ServerShapeVisual::observe(visual, self.props.shapes(), false);
161        ServerShapeVisual::observe(visual, self.props.view_box(), false);
162    }
163
164    fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
165        self.props.find_props(type_id)
166    }
167
168    fn get_composition_property(&self, name: &str) -> Option<&'static CompositionProperty> {
169        ServerCompositionContainerVisualProps::get_composition_property(name)
170    }
171
172    fn as_any(&self) -> &dyn Any {
173        self
174    }
175}