Skip to main content

ferroui_base/rendering/composition/server/
server_composition_geometry.rs

1//! The server-side counterparts of the WinUI geometries
2//! (`CompositionRectangleGeometry`, `CompositionRoundedRectangleGeometry`,
3//! `CompositionEllipseGeometry`, `CompositionLineGeometry`,
4//! `CompositionPathGeometry`): each builds its platform geometry with the
5//! render interface of the compositor, trims it, and caches the result until
6//! one of its members changes.
7
8use super::{
9    impl_server_render_resource, IAnimatedServerObject, IServerObject, IServerRenderResource, ServerCompositor,
10    ServerRenderResource,
11};
12use crate::media::{FillRule, GeometryBuilder};
13use crate::platform::{IGeometryImpl, IPlatformRenderInterface};
14use crate::rendering::composition::generated::{
15    ServerCompositionEllipseGeometryHooks, ServerCompositionEllipseGeometryProps, ServerCompositionGeometryHooks,
16    ServerCompositionGeometryProps, ServerCompositionLineGeometryHooks, ServerCompositionLineGeometryProps,
17    ServerCompositionPathGeometryHooks, ServerCompositionPathGeometryProps, ServerCompositionRectangleGeometryHooks,
18    ServerCompositionRectangleGeometryProps, ServerCompositionRoundedRectangleGeometryHooks,
19    ServerCompositionRoundedRectangleGeometryProps,
20};
21use crate::rendering::composition::transport::BatchStreamReader;
22use crate::{Point, Rect};
23use std::any::{Any, TypeId};
24use std::cell::RefCell;
25use std::rc::{Rc, Weak};
26use std::sync::Arc;
27use std::time::Duration;
28
29/// What the shapes and clips need from a server-side geometry.
30pub trait IServerCompositionGeometry {
31    /// The platform geometry with the trimming applied, cached until a
32    /// member of the geometry changes. `None` when there is nothing to draw
33    /// (no path, or a trim that leaves nothing) or when the compositor has
34    /// no render interface.
35    fn platform_geometry(&self) -> Option<Arc<dyn IGeometryImpl>>;
36
37    /// The properties of `CompositionGeometry` (the trimming).
38    fn geometry_props(&self) -> &ServerCompositionGeometryProps;
39}
40
41/// The server-side geometry behind `object`, if it is one.
42pub fn server_geometry_of(object: &dyn IServerObject) -> Option<&dyn IServerCompositionGeometry> {
43    let any = object.as_any();
44    if let Some(geometry) = any.downcast_ref::<ServerCompositionRectangleGeometry>() {
45        return Some(geometry);
46    }
47    if let Some(geometry) = any.downcast_ref::<ServerCompositionRoundedRectangleGeometry>() {
48        return Some(geometry);
49    }
50    if let Some(geometry) = any.downcast_ref::<ServerCompositionEllipseGeometry>() {
51        return Some(geometry);
52    }
53    if let Some(geometry) = any.downcast_ref::<ServerCompositionLineGeometry>() {
54        return Some(geometry);
55    }
56    if let Some(geometry) = any.downcast_ref::<ServerCompositionPathGeometry>() {
57        return Some(geometry);
58    }
59    None
60}
61
62/// The trimmed platform geometry of `object` (a server-side geometry), if
63/// it is one and there is something to draw.
64pub fn platform_geometry_of(object: &Rc<dyn IServerObject>) -> Option<Arc<dyn IGeometryImpl>> {
65    server_geometry_of(&**object)?.platform_geometry()
66}
67
68/// The part of the outline of `geometry` that `TrimStart`, `TrimEnd` and
69/// `TrimOffset` leave: the fractions `start..end` of the length of the
70/// outline, both moved by `offset` and wrapping around the start of the
71/// outline. `None` when nothing is left.
72///
73/// Deviation (winui-composition.md): the trim points are clamped to
74/// `0..=1` and swapped when the start is after the end (the port's reading);
75/// a part that wraps around is two figures; when the platform cannot cut
76/// the outline (`try_get_segment` fails) the whole geometry is drawn.
77/// A fraction of the single-precision members as the decimal it was written as (`0.2`, not
78/// `0.20000000298`): the fractions select lengths of the outline, which would otherwise be off by
79/// the error of the single precision.
80fn widen(value: f32) -> f64 {
81    value.to_string().parse().unwrap_or(value as f64)
82}
83
84pub fn trim_geometry(
85    geometry: Arc<dyn IGeometryImpl>,
86    trim_start: f32,
87    trim_end: f32,
88    trim_offset: f32,
89    render_interface: &dyn IPlatformRenderInterface,
90) -> Option<Arc<dyn IGeometryImpl>> {
91    let mut start = widen(trim_start).clamp(0.0, 1.0);
92    let mut end = widen(trim_end).clamp(0.0, 1.0);
93    if start > end {
94        std::mem::swap(&mut start, &mut end);
95    }
96    if start <= 0.0 && end >= 1.0 {
97        return Some(geometry);
98    }
99    if end - start <= 0.0 {
100        return None;
101    }
102    let length = geometry.contour_length();
103    if length.is_nan() || length <= 0.0 {
104        return Some(geometry);
105    }
106    let from = (start + widen(trim_offset)).rem_euclid(1.0);
107    let to = from + (end - start);
108    let segment = |a: f64, b: f64| geometry.try_get_segment(a * length, b * length, true);
109    let trimmed = if to <= 1.0 {
110        segment(from, to)
111    } else {
112        match (segment(from, 1.0), segment(0.0, to - 1.0)) {
113            (Some(first), Some(second)) => {
114                Some(render_interface.create_geometry_group(FillRule::NonZero, &[first, second]))
115            }
116            _ => None,
117        }
118    };
119    Some(trimmed.unwrap_or(geometry))
120}
121
122/// The cached platform geometry of a server-side geometry.
123#[derive(Default)]
124struct GeometryCache {
125    /// `None` until built; then the trimmed geometry, `None` for nothing.
126    value: RefCell<Option<Option<Arc<dyn IGeometryImpl>>>>,
127}
128
129impl GeometryCache {
130    fn clear(&self) {
131        *self.value.borrow_mut() = None;
132    }
133
134    fn get_or_build(
135        &self,
136        compositor: Option<Rc<ServerCompositor>>,
137        props: &ServerCompositionGeometryProps,
138        build: impl FnOnce(&dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>>,
139    ) -> Option<Arc<dyn IGeometryImpl>> {
140        if let Some(value) = &*self.value.borrow() {
141            return value.clone();
142        }
143        let compositor = compositor?;
144        // Without a render interface nothing is cached: the geometry is
145        // built once one is known.
146        let built = compositor.render_interface().with_platform_render_interface(|render_interface| {
147            build(render_interface).and_then(|geometry| {
148                trim_geometry(geometry, props.trim_start(), props.trim_end(), props.trim_offset(), render_interface)
149            })
150        })?;
151        *self.value.borrow_mut() = Some(built.clone());
152        built
153    }
154}
155
156/// Declares a server-side geometry class: the struct, its constructor, the
157/// render resource contracts, `IServerObject` and
158/// [`IServerCompositionGeometry`] over its `build` method.
159macro_rules! server_geometry {
160    ($(#[$meta:meta])* $name:ident, $props:ident, $hooks:ident) => {
161        $(#[$meta])*
162        pub struct $name {
163            base: ServerRenderResource,
164            props: $props,
165            cache: GeometryCache,
166        }
167
168        impl $name {
169            /// Creates the geometry, active: the UI-thread geometry activates
170            /// its server object when it is created.
171            pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<$name> {
172                let geometry = Rc::new_cyclic(|this: &Weak<$name>| $name {
173                    base: ServerRenderResource::new(
174                        compositor,
175                        this.clone() as Weak<dyn IAnimatedServerObject>,
176                        this.clone() as Weak<dyn IServerRenderResource>,
177                    ),
178                    props: $props::new(),
179                    cache: GeometryCache::default(),
180                });
181                geometry.base.object().activate();
182                geometry
183            }
184
185            /// The properties of the geometry.
186            pub fn props(&self) -> &$props {
187                &self.props
188            }
189
190            pub fn is_disposed(&self) -> bool {
191                self.base.is_disposed()
192            }
193        }
194
195        impl_server_render_resource!($name, base, composition_property: $props::get_composition_property);
196
197        impl ServerCompositionGeometryHooks for $name {}
198
199        impl $hooks for $name {}
200
201        impl IServerCompositionGeometry for $name {
202            fn platform_geometry(&self) -> Option<Arc<dyn IGeometryImpl>> {
203                self.cache.get_or_build(self.base.compositor(), self.props.base(), |render_interface| {
204                    self.build(render_interface)
205                })
206            }
207
208            fn geometry_props(&self) -> &ServerCompositionGeometryProps {
209                self.props.base()
210            }
211        }
212
213        impl IServerObject for $name {
214            fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
215                self.props.deserialize_changes_core(self, reader, committed_at);
216            }
217
218            /// A member changed (directly or by an animation): the geometry
219            /// is built again when it is next drawn.
220            fn values_invalidated(&self) {
221                self.cache.clear();
222                self.base.core().invalidated(self);
223            }
224
225            fn dispose(&self) {
226                self.cache.clear();
227                self.base.core().dispose();
228            }
229
230            fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
231                Some(self)
232            }
233
234            fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
235                Some(self)
236            }
237
238            fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
239                self.props.find_props(type_id)
240            }
241
242            fn as_any(&self) -> &dyn Any {
243                self
244            }
245
246            fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
247                self
248            }
249        }
250    };
251}
252
253server_geometry!(
254    /// The server-side counterpart of a
255    /// [`CompositionRectangleGeometry`](crate::rendering::composition::CompositionRectangleGeometry).
256    ServerCompositionRectangleGeometry,
257    ServerCompositionRectangleGeometryProps,
258    ServerCompositionRectangleGeometryHooks
259);
260
261impl ServerCompositionRectangleGeometry {
262    /// The rectangle `(offset, size)`.
263    pub fn rect(&self) -> Rect {
264        let (offset, size) = (self.props.offset(), self.props.size());
265        Rect::new(offset.x, offset.y, size.x.max(0.0), size.y.max(0.0))
266    }
267
268    fn build(&self, render_interface: &dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>> {
269        Some(render_interface.create_rectangle_geometry(self.rect()))
270    }
271}
272
273server_geometry!(
274    /// The server-side counterpart of a
275    /// [`CompositionRoundedRectangleGeometry`](crate::rendering::composition::CompositionRoundedRectangleGeometry).
276    ServerCompositionRoundedRectangleGeometry,
277    ServerCompositionRoundedRectangleGeometryProps,
278    ServerCompositionRoundedRectangleGeometryHooks
279);
280
281impl ServerCompositionRoundedRectangleGeometry {
282    /// The rectangle `(offset, size)`.
283    pub fn rect(&self) -> Rect {
284        let (offset, size) = (self.props.offset(), self.props.size());
285        Rect::new(offset.x, offset.y, size.x.max(0.0), size.y.max(0.0))
286    }
287
288    /// The radii of the corners, limited to half the size.
289    pub fn radii(&self) -> (f64, f64) {
290        let rect = self.rect();
291        let radius = self.props.corner_radius();
292        (radius.x.clamp(0.0, rect.width / 2.0), radius.y.clamp(0.0, rect.height / 2.0))
293    }
294
295    fn build(&self, render_interface: &dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>> {
296        let rect = self.rect();
297        let (radius_x, radius_y) = self.radii();
298        if radius_x <= 0.0 || radius_y <= 0.0 {
299            return Some(render_interface.create_rectangle_geometry(rect));
300        }
301        let mut builder = render_interface.create_stream_geometry_builder(None);
302        GeometryBuilder::draw_rounded_corners_rectangle(&mut *builder, rect, radius_x, radius_y);
303        let geometry = builder.to_geometry();
304        builder.dispose();
305        Some(geometry)
306    }
307}
308
309server_geometry!(
310    /// The server-side counterpart of a
311    /// [`CompositionEllipseGeometry`](crate::rendering::composition::CompositionEllipseGeometry).
312    ServerCompositionEllipseGeometry,
313    ServerCompositionEllipseGeometryProps,
314    ServerCompositionEllipseGeometryHooks
315);
316
317impl ServerCompositionEllipseGeometry {
318    /// The bounds of the ellipse.
319    pub fn rect(&self) -> Rect {
320        let (center, radius) = (self.props.center(), self.props.radius());
321        let (radius_x, radius_y) = (radius.x.abs(), radius.y.abs());
322        Rect::new(center.x - radius_x, center.y - radius_y, radius_x * 2.0, radius_y * 2.0)
323    }
324
325    fn build(&self, render_interface: &dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>> {
326        Some(render_interface.create_ellipse_geometry(self.rect()))
327    }
328}
329
330server_geometry!(
331    /// The server-side counterpart of a
332    /// [`CompositionLineGeometry`](crate::rendering::composition::CompositionLineGeometry).
333    ServerCompositionLineGeometry,
334    ServerCompositionLineGeometryProps,
335    ServerCompositionLineGeometryHooks
336);
337
338impl ServerCompositionLineGeometry {
339    fn build(&self, render_interface: &dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>> {
340        let (start, end) = (self.props.start(), self.props.end());
341        Some(render_interface.create_line_geometry(Point::new(start.x, start.y), Point::new(end.x, end.y)))
342    }
343}
344
345server_geometry!(
346    /// The server-side counterpart of a
347    /// [`CompositionPathGeometry`](crate::rendering::composition::CompositionPathGeometry).
348    ServerCompositionPathGeometry,
349    ServerCompositionPathGeometryProps,
350    ServerCompositionPathGeometryHooks
351);
352
353impl ServerCompositionPathGeometry {
354    fn build(&self, _render_interface: &dyn IPlatformRenderInterface) -> Option<Arc<dyn IGeometryImpl>> {
355        self.props.path().map(|path| path.geometry().clone())
356    }
357}