Skip to main content

ferroui_base/rendering/composition/
composition_geometry.rs

1//! The WinUI geometries of the shapes: `CompositionGeometry` and its
2//! subclasses `CompositionRectangleGeometry`,
3//! `CompositionRoundedRectangleGeometry`, `CompositionEllipseGeometry`,
4//! `CompositionLineGeometry` and `CompositionPathGeometry`, and the
5//! `CompositionPath` a path geometry shows.
6//!
7//! As for the clips, every geometry is one [`CompositionGeometry`] behind an
8//! `Rc`, which holds what the concrete class adds in a kind; the concrete
9//! classes are typed handles around the same `Rc`. The server builds the
10//! platform geometry from the members and caches it until a member changes.
11
12use super::composition_object_impls::composition_object_impls;
13use super::generated::{
14    CompositionEllipseGeometryHooks, CompositionEllipseGeometryProps, CompositionGeometryHooks,
15    CompositionGeometryProps, CompositionLineGeometryHooks, CompositionLineGeometryProps,
16    CompositionPathGeometryHooks, CompositionPathGeometryProps, CompositionRectangleGeometryHooks,
17    CompositionRectangleGeometryProps, CompositionRoundedRectangleGeometryHooks,
18    CompositionRoundedRectangleGeometryProps, ServerCompositionEllipseGeometryProps,
19    ServerCompositionLineGeometryProps, ServerCompositionPathGeometryProps, ServerCompositionRectangleGeometryProps,
20    ServerCompositionRoundedRectangleGeometryProps,
21};
22use super::server::{
23    IServerObject, ServerCompositionEllipseGeometry, ServerCompositionLineGeometry, ServerCompositionPathGeometry,
24    ServerCompositionRectangleGeometry, ServerCompositionRoundedRectangleGeometry, ServerCompositor, ServerObjectId,
25};
26use super::{CompositionObject, Compositor};
27use crate::media::Geometry;
28use crate::platform::IGeometryImpl;
29use crate::utilities::FormatError;
30use crate::{Rect, Vector};
31use std::ops::Deref;
32use std::rc::{Rc, Weak};
33use std::sync::Arc;
34
35/// The path of a [`CompositionPathGeometry`] (WinUI
36/// `Microsoft.UI.Composition.CompositionPath`): an immutable platform
37/// geometry, made from a FerroUI [`Geometry`] (a `StreamGeometry`, a
38/// `PathGeometry`, ...) or from a string of path data.
39///
40/// Deviation (winui-composition.md): WinUI makes a path from an
41/// `IGeometrySource2D` (a Win2D geometry); here the source is a FerroUI
42/// geometry, and the path keeps the platform geometry it had when the path
43/// was made.
44#[derive(Clone)]
45pub struct CompositionPath {
46    geometry: Arc<dyn IGeometryImpl>,
47}
48
49impl CompositionPath {
50    /// A path over a platform geometry.
51    pub fn new(geometry: Arc<dyn IGeometryImpl>) -> CompositionPath {
52        CompositionPath { geometry }
53    }
54
55    /// The path of `geometry`, as it is now; `None` when the geometry has no
56    /// platform geometry.
57    pub fn from_geometry(geometry: &Geometry) -> Option<CompositionPath> {
58        geometry.platform_impl().map(CompositionPath::new)
59    }
60
61    /// The path described by a string of path data (the mini-language of
62    /// `Geometry::parse`).
63    pub fn parse(data: &str) -> Result<CompositionPath, FormatError> {
64        let geometry = Geometry::parse(data)?;
65        let platform = geometry.platform_impl().expect("a parsed stream geometry has a platform geometry");
66        Ok(CompositionPath::new(platform))
67    }
68
69    /// The platform geometry of the path.
70    pub fn geometry(&self) -> &Arc<dyn IGeometryImpl> {
71        &self.geometry
72    }
73
74    /// The bounds of the path.
75    pub fn bounds(&self) -> Rect {
76        self.geometry.bounds()
77    }
78}
79
80impl std::fmt::Debug for CompositionPath {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("CompositionPath").field("bounds", &self.geometry.bounds()).finish()
83    }
84}
85
86/// What the concrete class of a geometry adds to [`CompositionGeometry`].
87pub(crate) enum CompositionGeometryKind {
88    Rectangle(CompositionRectangleGeometryProps),
89    RoundedRectangle(CompositionRoundedRectangleGeometryProps),
90    Ellipse(CompositionEllipseGeometryProps),
91    Line(CompositionLineGeometryProps),
92    Path(CompositionPathGeometryProps),
93}
94
95/// The geometry of a sprite shape or a geometric clip (WinUI
96/// `Microsoft.UI.Composition.CompositionGeometry`), with the animatable
97/// trimming of its outline: `TrimStart` and `TrimEnd` (fractions of the
98/// length of the outline, default 0 and 1) and `TrimOffset` (added to both,
99/// wrapping around the outline).
100pub struct CompositionGeometry {
101    this: Weak<CompositionGeometry>,
102    object: CompositionObject,
103    kind: CompositionGeometryKind,
104}
105
106impl CompositionGeometry {
107    fn create(
108        compositor: &Rc<Compositor>,
109        kind: CompositionGeometryKind,
110        server: impl FnOnce(&Rc<ServerCompositor>) -> Rc<dyn IServerObject> + Send + 'static,
111    ) -> Rc<CompositionGeometry> {
112        let server = compositor.create_server_object(move |compositor, _| server(compositor));
113        let geometry = Rc::new_cyclic(|this: &Weak<CompositionGeometry>| CompositionGeometry {
114            this: this.clone(),
115            object: CompositionObject::new(compositor, Some(server)),
116            kind,
117        });
118        match &geometry.kind {
119            CompositionGeometryKind::Rectangle(props) => props.initialize_defaults(&*geometry),
120            CompositionGeometryKind::RoundedRectangle(props) => props.initialize_defaults(&*geometry),
121            CompositionGeometryKind::Ellipse(props) => props.initialize_defaults(&*geometry),
122            CompositionGeometryKind::Line(props) => props.initialize_defaults(&*geometry),
123            CompositionGeometryKind::Path(props) => props.initialize_defaults(&*geometry),
124        }
125        geometry
126    }
127
128    /// The generated properties of `CompositionGeometry`.
129    pub fn geometry_props(&self) -> &CompositionGeometryProps {
130        match &self.kind {
131            CompositionGeometryKind::Rectangle(props) => props.base(),
132            CompositionGeometryKind::RoundedRectangle(props) => props.base(),
133            CompositionGeometryKind::Ellipse(props) => props.base(),
134            CompositionGeometryKind::Line(props) => props.base(),
135            CompositionGeometryKind::Path(props) => props.base(),
136        }
137    }
138
139    pub fn compositor(&self) -> &Rc<Compositor> {
140        self.object.compositor()
141    }
142
143    /// The embedded `CompositionObject`.
144    pub fn object(&self) -> &CompositionObject {
145        &self.object
146    }
147
148    /// The id of the server-side geometry.
149    pub fn server(&self) -> ServerObjectId {
150        self.object.required_server()
151    }
152
153    pub fn is_disposed(&self) -> bool {
154        self.object.is_disposed()
155    }
156
157    pub fn dispose(&self) {
158        self.object.dispose();
159    }
160
161    /// The name of the concrete class, for messages.
162    pub fn type_name(&self) -> &'static str {
163        match &self.kind {
164            CompositionGeometryKind::Rectangle(_) => "CompositionRectangleGeometry",
165            CompositionGeometryKind::RoundedRectangle(_) => "CompositionRoundedRectangleGeometry",
166            CompositionGeometryKind::Ellipse(_) => "CompositionEllipseGeometry",
167            CompositionGeometryKind::Line(_) => "CompositionLineGeometry",
168            CompositionGeometryKind::Path(_) => "CompositionPathGeometry",
169        }
170    }
171
172    /// Where the drawn part of the outline ends, as a fraction of its length
173    /// (default 1).
174    pub fn trim_end(&self) -> f32 {
175        self.geometry_props().trim_end()
176    }
177
178    pub fn set_trim_end(&self, value: f32) {
179        self.geometry_props().set_trim_end(self, value)
180    }
181
182    /// How far along the outline both trim points are moved, as a fraction
183    /// of its length; the drawn part wraps around the start of the outline.
184    pub fn trim_offset(&self) -> f32 {
185        self.geometry_props().trim_offset()
186    }
187
188    pub fn set_trim_offset(&self, value: f32) {
189        self.geometry_props().set_trim_offset(self, value)
190    }
191
192    /// Where the drawn part of the outline starts, as a fraction of its
193    /// length (default 0).
194    pub fn trim_start(&self) -> f32 {
195        self.geometry_props().trim_start()
196    }
197
198    pub fn set_trim_start(&self, value: f32) {
199        self.geometry_props().set_trim_start(self, value)
200    }
201}
202
203impl CompositionGeometryHooks for CompositionGeometry {}
204
205impl CompositionRectangleGeometryHooks for CompositionGeometry {}
206
207impl CompositionRoundedRectangleGeometryHooks for CompositionGeometry {}
208
209impl CompositionEllipseGeometryHooks for CompositionGeometry {}
210
211impl CompositionLineGeometryHooks for CompositionGeometry {}
212
213impl CompositionPathGeometryHooks for CompositionGeometry {}
214
215composition_object_impls!(
216    CompositionGeometry, "CompositionGeometry",
217    serialize: |this, writer| match &this.kind {
218        CompositionGeometryKind::Rectangle(props) => props.serialize_changes_core(this, writer),
219        CompositionGeometryKind::RoundedRectangle(props) => props.serialize_changes_core(this, writer),
220        CompositionGeometryKind::Ellipse(props) => props.serialize_changes_core(this, writer),
221        CompositionGeometryKind::Line(props) => props.serialize_changes_core(this, writer),
222        CompositionGeometryKind::Path(props) => props.serialize_changes_core(this, writer),
223    },
224    start_animation: |this, name, animation, final_value| match &this.kind {
225        CompositionGeometryKind::Rectangle(props) => props.start_animation(this, name, animation, final_value),
226        CompositionGeometryKind::RoundedRectangle(props) => props.start_animation(this, name, animation, final_value),
227        CompositionGeometryKind::Ellipse(props) => props.start_animation(this, name, animation, final_value),
228        CompositionGeometryKind::Line(props) => props.start_animation(this, name, animation, final_value),
229        CompositionGeometryKind::Path(props) => props.start_animation(this, name, animation, final_value),
230    },
231    composition_property: |this, name| match &this.kind {
232        CompositionGeometryKind::Rectangle(_) => ServerCompositionRectangleGeometryProps::get_composition_property(name),
233        CompositionGeometryKind::RoundedRectangle(_) => {
234            ServerCompositionRoundedRectangleGeometryProps::get_composition_property(name)
235        }
236        CompositionGeometryKind::Ellipse(_) => ServerCompositionEllipseGeometryProps::get_composition_property(name),
237        CompositionGeometryKind::Line(_) => ServerCompositionLineGeometryProps::get_composition_property(name),
238        CompositionGeometryKind::Path(_) => ServerCompositionPathGeometryProps::get_composition_property(name),
239    },
240);
241
242/// Declares the typed handle of a geometry class: the struct, `Deref` to
243/// the shared geometry, `from_geometry` and the accessor of its properties.
244macro_rules! geometry_handle {
245    ($(#[$meta:meta])* $name:ident, $variant:ident, $props:ident, $what:literal) => {
246        $(#[$meta])*
247        #[derive(Clone)]
248        pub struct $name(Rc<CompositionGeometry>);
249
250        impl Deref for $name {
251            type Target = Rc<CompositionGeometry>;
252
253            fn deref(&self) -> &Rc<CompositionGeometry> {
254                &self.0
255            }
256        }
257
258        impl $name {
259            #[doc = concat!("The handle of `geometry` as ", $what, ", if it is one.")]
260            pub fn from_geometry(geometry: &Rc<CompositionGeometry>) -> Option<$name> {
261                matches!(geometry.kind, CompositionGeometryKind::$variant(_)).then(|| $name(geometry.clone()))
262            }
263
264            /// The geometry as a [`CompositionGeometry`].
265            pub fn geometry(&self) -> &Rc<CompositionGeometry> {
266                &self.0
267            }
268
269            fn props(&self) -> &$props {
270                match &self.0.kind {
271                    CompositionGeometryKind::$variant(props) => props,
272                    _ => unreachable!(concat!("the handle wraps ", $what)),
273                }
274            }
275        }
276    };
277}
278
279geometry_handle!(
280    /// A rectangle (WinUI `Microsoft.UI.Composition.CompositionRectangleGeometry`):
281    /// `(offset, size)`, both animatable (`Offset`, `Size`).
282    CompositionRectangleGeometry,
283    Rectangle,
284    CompositionRectangleGeometryProps,
285    "a rectangle geometry"
286);
287
288impl CompositionRectangleGeometry {
289    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionRectangleGeometry {
290        CompositionRectangleGeometry(CompositionGeometry::create(
291            compositor,
292            CompositionGeometryKind::Rectangle(CompositionRectangleGeometryProps::new()),
293            |c| ServerCompositionRectangleGeometry::new(c),
294        ))
295    }
296
297    /// The top left corner of the rectangle.
298    pub fn offset(&self) -> Vector {
299        self.props().offset()
300    }
301
302    pub fn set_offset(&self, value: Vector) {
303        self.props().set_offset(&*self.0, value)
304    }
305
306    /// The size of the rectangle.
307    pub fn size(&self) -> Vector {
308        self.props().size()
309    }
310
311    pub fn set_size(&self, value: Vector) {
312        self.props().set_size(&*self.0, value)
313    }
314}
315
316geometry_handle!(
317    /// A rectangle with rounded corners (WinUI
318    /// `Microsoft.UI.Composition.CompositionRoundedRectangleGeometry`):
319    /// `(offset, size)` with the elliptical radii `corner_radius`, all
320    /// animatable (`CornerRadius`, `Offset`, `Size`). The radii are limited to
321    /// half the size.
322    CompositionRoundedRectangleGeometry,
323    RoundedRectangle,
324    CompositionRoundedRectangleGeometryProps,
325    "a rounded rectangle geometry"
326);
327
328impl CompositionRoundedRectangleGeometry {
329    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionRoundedRectangleGeometry {
330        CompositionRoundedRectangleGeometry(CompositionGeometry::create(
331            compositor,
332            CompositionGeometryKind::RoundedRectangle(CompositionRoundedRectangleGeometryProps::new()),
333            |c| ServerCompositionRoundedRectangleGeometry::new(c),
334        ))
335    }
336
337    /// The radii of the corners.
338    pub fn corner_radius(&self) -> Vector {
339        self.props().corner_radius()
340    }
341
342    pub fn set_corner_radius(&self, value: Vector) {
343        self.props().set_corner_radius(&*self.0, value)
344    }
345
346    /// The top left corner of the rectangle.
347    pub fn offset(&self) -> Vector {
348        self.props().offset()
349    }
350
351    pub fn set_offset(&self, value: Vector) {
352        self.props().set_offset(&*self.0, value)
353    }
354
355    /// The size of the rectangle.
356    pub fn size(&self) -> Vector {
357        self.props().size()
358    }
359
360    pub fn set_size(&self, value: Vector) {
361        self.props().set_size(&*self.0, value)
362    }
363}
364
365geometry_handle!(
366    /// An ellipse (WinUI `Microsoft.UI.Composition.CompositionEllipseGeometry`):
367    /// its `center` and its radii `radius`, both animatable (`Center`,
368    /// `Radius`).
369    CompositionEllipseGeometry,
370    Ellipse,
371    CompositionEllipseGeometryProps,
372    "an ellipse geometry"
373);
374
375impl CompositionEllipseGeometry {
376    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionEllipseGeometry {
377        CompositionEllipseGeometry(CompositionGeometry::create(
378            compositor,
379            CompositionGeometryKind::Ellipse(CompositionEllipseGeometryProps::new()),
380            |c| ServerCompositionEllipseGeometry::new(c),
381        ))
382    }
383
384    /// The centre of the ellipse.
385    pub fn center(&self) -> Vector {
386        self.props().center()
387    }
388
389    pub fn set_center(&self, value: Vector) {
390        self.props().set_center(&*self.0, value)
391    }
392
393    /// The radii of the ellipse.
394    pub fn radius(&self) -> Vector {
395        self.props().radius()
396    }
397
398    pub fn set_radius(&self, value: Vector) {
399        self.props().set_radius(&*self.0, value)
400    }
401}
402
403geometry_handle!(
404    /// A line (WinUI `Microsoft.UI.Composition.CompositionLineGeometry`) from
405    /// `start` to `end`, both animatable (`Start`, `End`).
406    CompositionLineGeometry,
407    Line,
408    CompositionLineGeometryProps,
409    "a line geometry"
410);
411
412impl CompositionLineGeometry {
413    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionLineGeometry {
414        CompositionLineGeometry(CompositionGeometry::create(
415            compositor,
416            CompositionGeometryKind::Line(CompositionLineGeometryProps::new()),
417            |c| ServerCompositionLineGeometry::new(c),
418        ))
419    }
420
421    /// The start point of the line.
422    pub fn start(&self) -> Vector {
423        self.props().start()
424    }
425
426    pub fn set_start(&self, value: Vector) {
427        self.props().set_start(&*self.0, value)
428    }
429
430    /// The end point of the line.
431    pub fn end(&self) -> Vector {
432        self.props().end()
433    }
434
435    pub fn set_end(&self, value: Vector) {
436        self.props().set_end(&*self.0, value)
437    }
438}
439
440geometry_handle!(
441    /// A geometry that shows a [`CompositionPath`] (WinUI
442    /// `Microsoft.UI.Composition.CompositionPathGeometry`).
443    CompositionPathGeometry,
444    Path,
445    CompositionPathGeometryProps,
446    "a path geometry"
447);
448
449impl CompositionPathGeometry {
450    pub(crate) fn new(compositor: &Rc<Compositor>) -> CompositionPathGeometry {
451        CompositionPathGeometry(CompositionGeometry::create(
452            compositor,
453            CompositionGeometryKind::Path(CompositionPathGeometryProps::new()),
454            |c| ServerCompositionPathGeometry::new(c),
455        ))
456    }
457
458    pub(crate) fn with_path(compositor: &Rc<Compositor>, path: Option<CompositionPath>) -> CompositionPathGeometry {
459        let geometry = Self::new(compositor);
460        geometry.set_path(path);
461        geometry
462    }
463
464    /// The path the geometry shows.
465    pub fn path(&self) -> Option<CompositionPath> {
466        self.props().path().map(|path| (*path).clone())
467    }
468
469    pub fn set_path(&self, value: Option<CompositionPath>) {
470        self.props().set_path(&*self.0, value.map(Arc::new))
471    }
472}