Skip to main content

ferroui_base/rendering/composition/
visual.rs

1//! `CompositionVisual`.
2//!
3//! Upstream this is the root of a class hierarchy
4//! (`CompositionContainerVisual`, `CompositionDrawListVisual`,
5//! `CompositionSolidColorVisual`, ...). Here every visual is one
6//! [`CompositionVisual`] behind an `Rc`: the members of the abstract class
7//! and of the container class (which every concrete class derives from),
8//! plus a [`CompositionVisualKind`] that holds what the concrete class
9//! adds. The concrete classes are typed handles around the same `Rc`
10//! (`CompositionDrawListVisual`, `CompositionSolidColorVisual`, ...).
11
12use super::animations::{ICompositionAnimation, ICompositionAnimationBase, ImplicitAnimationCollection};
13use super::composition_custom_visual::CustomVisualData;
14use super::composition_draw_list_visual::DrawListData;
15use super::composition_object::ICompositionObjectAnimations;
16use super::drawing::brush_get_server_resource;
17use super::expressions::ExpressionVariant;
18use super::generated::{
19    CompositionContainerVisualHooks, CompositionContainerVisualProps, CompositionSolidColorVisualProps,
20    CompositionSurfaceVisualProps, CompositionVisualHooks, CompositionVisualProps,
21    ServerCompositionContainerVisualProps, ShapeVisualProps, SpriteVisualProps,
22};
23use super::generated::{LayerVisualProps, RedirectVisualProps};
24use super::hit_testing::CompositionHitTestAabbTree;
25use super::server::{
26    CompositionProperty, IServerVisualContent, ReadbackData, ServerCompositionVisual,
27    ServerCompositionVisualCollection, ServerObjectId, VisualReadback,
28};
29use super::transport::{BatchStreamWriter, IRegisterForSerialization};
30use super::{
31    AsCompositionObject, CompositionBitmapCache, CompositionClip, CompositionObject, CompositionTarget,
32    CompositionVisualCollection, Compositor, ICompositionObject, ICompositionObjectHost, ICompositorSerializable,
33    PendingAnimations,
34};
35use crate::media::{BrushExtensions, Geometry, IBrush, IImmutableEffect, IntersectionResult, RenderOptions, TextOptions};
36use crate::numerics::Quaternion;
37use crate::platform::IGeometryImpl;
38use crate::{Matrix, Point, Ref, Vector, Vector3D};
39use std::any::Any;
40use std::cell::{Cell, RefCell};
41use std::rc::{Rc, Weak};
42use std::sync::Arc;
43
44/// What the concrete class of a visual adds to [`CompositionVisual`].
45pub(crate) enum CompositionVisualKind {
46    Container(CompositionContainerVisualProps),
47    DrawList(DrawListData),
48    SolidColor(CompositionSolidColorVisualProps),
49    Surface(CompositionSurfaceVisualProps),
50    Custom(CustomVisualData),
51    /// The WinUI `SpriteVisual`.
52    Sprite(SpriteVisualProps),
53    /// The WinUI `ShapeVisual`.
54    Shape(ShapeVisualProps),
55    /// The WinUI `LayerVisual`.
56    Layer(LayerVisualProps),
57    /// The WinUI `RedirectVisual`.
58    Redirect(RedirectVisualProps),
59}
60
61/// A node of the composition visual tree.
62pub struct CompositionVisual {
63    pub(super) this: Weak<CompositionVisual>,
64    object: CompositionObject,
65    pub(super) kind: CompositionVisualKind,
66    children: Rc<CompositionVisualCollection>,
67    pub(super) hit_test_children: RefCell<Option<CompositionHitTestAabbTree>>,
68    opacity_mask: RefCell<Option<Rc<dyn IBrush>>>,
69    pub(super) custom_hit_test_count_in_sub_tree: Cell<i32>,
70    tag: RefCell<Option<Rc<dyn Any>>>,
71    readback: Arc<VisualReadback>,
72}
73
74macro_rules! visual_value_properties {
75    ($($(#[$meta:meta])* $get:ident / $set:ident : $ty:ty;)*) => {
76        impl CompositionVisual {$(
77            $(#[$meta])*
78            pub fn $get(&self) -> $ty {
79                self.visual_props().$get()
80            }
81
82            pub fn $set(&self, value: $ty) {
83                self.visual_props().$set(self, value)
84            }
85        )*}
86    };
87}
88
89visual_value_properties! {
90    visible / set_visible: bool;
91    opacity / set_opacity: f32;
92    /// The geometry the visual is clipped with.
93    clip / set_clip: Option<Arc<dyn IGeometryImpl>>;
94    clip_to_bounds / set_clip_to_bounds: bool;
95    offset / set_offset: Vector3D;
96    translation / set_translation: Vector3D;
97    size / set_size: Vector;
98    anchor_point / set_anchor_point: Vector;
99    center_point / set_center_point: Vector3D;
100    rotation_angle / set_rotation_angle: f32;
101    orientation / set_orientation: Quaternion;
102    scale / set_scale: Vector3D;
103    transform_matrix / set_transform_matrix: Matrix;
104    /// Whether the adorner is clipped by the clips of the adorned visual
105    /// and its ancestors.
106    adorner_is_clipped / set_adorner_is_clipped: bool;
107    effect / set_effect: Option<std::sync::Arc<dyn IImmutableEffect>>;
108    render_options / set_render_options: RenderOptions;
109    text_options / set_text_options: TextOptions;
110}
111
112impl CompositionVisual {
113    /// Creates a visual of the given kind. `content` creates the concrete
114    /// class of the server-side visual, on the render thread.
115    pub(crate) fn create(
116        compositor: &Rc<Compositor>,
117        kind: CompositionVisualKind,
118        content: impl FnOnce() -> Box<dyn IServerVisualContent> + Send + 'static,
119    ) -> Rc<CompositionVisual> {
120        Self::create_with(compositor, kind, content, |_| {})
121    }
122
123    /// [`create`](Self::create), with `created` called on the render
124    /// thread right after the server-side visual has been created.
125    pub(crate) fn create_with(
126        compositor: &Rc<Compositor>,
127        kind: CompositionVisualKind,
128        content: impl FnOnce() -> Box<dyn IServerVisualContent> + Send + 'static,
129        created: impl FnOnce(&Rc<ServerCompositionVisual>) + Send + 'static,
130    ) -> Rc<CompositionVisual> {
131        let children_server =
132            compositor.create_server_object(|compositor, _| ServerCompositionVisualCollection::new(compositor));
133        let readback = Arc::new(VisualReadback::new());
134        let server_readback = readback.clone();
135        let server = compositor.create_server_object(move |compositor, _| {
136            let visual = ServerCompositionVisual::new(
137                compositor,
138                compositor.get::<ServerCompositionVisualCollection>(children_server),
139                server_readback,
140                content(),
141            );
142            created(&visual);
143            visual
144        });
145        let visual = Rc::new_cyclic(|this: &Weak<CompositionVisual>| CompositionVisual {
146            this: this.clone(),
147            object: CompositionObject::new(compositor, Some(server)),
148            kind,
149            children: CompositionVisualCollection::new(compositor, this.clone(), children_server),
150            hit_test_children: RefCell::new(None),
151            opacity_mask: RefCell::new(None),
152            custom_hit_test_count_in_sub_tree: Cell::new(0),
153            tag: RefCell::new(None),
154            readback,
155        });
156        visual.initialize_defaults();
157        visual
158    }
159
160    fn initialize_defaults(&self) {
161        match &self.kind {
162            CompositionVisualKind::Container(props) => props.initialize_defaults(self),
163            CompositionVisualKind::DrawList(data) => data.initialize_defaults(self),
164            CompositionVisualKind::SolidColor(props) => props.initialize_defaults(self),
165            CompositionVisualKind::Surface(props) => props.initialize_defaults(self),
166            CompositionVisualKind::Custom(data) => data.props().initialize_defaults(self),
167            CompositionVisualKind::Sprite(props) => props.initialize_defaults(self),
168            CompositionVisualKind::Shape(props) => props.initialize_defaults(self),
169            CompositionVisualKind::Layer(props) => props.initialize_defaults(self),
170            CompositionVisualKind::Redirect(props) => props.initialize_defaults(self),
171        }
172    }
173
174    pub(super) fn container_props(&self) -> &CompositionContainerVisualProps {
175        match &self.kind {
176            CompositionVisualKind::Container(props) => props,
177            CompositionVisualKind::DrawList(data) => data.props(),
178            CompositionVisualKind::SolidColor(props) => props.base(),
179            CompositionVisualKind::Surface(props) => props.base(),
180            CompositionVisualKind::Custom(data) => data.props(),
181            CompositionVisualKind::Sprite(props) => props.base(),
182            CompositionVisualKind::Shape(props) => props.base(),
183            CompositionVisualKind::Layer(props) => props.base(),
184            CompositionVisualKind::Redirect(props) => props.base(),
185        }
186    }
187
188    /// The generated properties of the visual class.
189    pub fn visual_props(&self) -> &CompositionVisualProps {
190        self.container_props().base()
191    }
192
193    pub fn compositor(&self) -> &Rc<Compositor> {
194        self.object.compositor()
195    }
196
197    /// The embedded `CompositionObject`.
198    pub fn object(&self) -> &CompositionObject {
199        &self.object
200    }
201
202    /// The collection of implicit animations attached to this object.
203    pub fn implicit_animations(&self) -> Option<Rc<ImplicitAnimationCollection>> {
204        self.object.implicit_animations()
205    }
206
207    pub fn set_implicit_animations(&self, value: Option<Rc<ImplicitAnimationCollection>>) {
208        self.object.set_implicit_animations(value)
209    }
210
211    /// The id of the server-side visual.
212    pub fn server(&self) -> ServerObjectId {
213        self.object.required_server()
214    }
215
216    pub fn is_disposed(&self) -> bool {
217        self.object.is_disposed()
218    }
219
220    pub fn dispose(&self) {
221        self.object.dispose();
222    }
223
224    /// The children of the visual.
225    pub fn children(&self) -> &Rc<CompositionVisualCollection> {
226        &self.children
227    }
228
229    // --- generated reference properties, typed --------------------------------
230
231    /// The composition target the visual is attached to.
232    pub fn root(&self) -> Option<Rc<CompositionTarget>> {
233        self.visual_props().root().and_then(|o| o.into_any_rc().downcast::<CompositionTarget>().ok())
234    }
235
236    pub(crate) fn set_root(&self, value: Option<Rc<CompositionTarget>>) {
237        self.visual_props().set_root(self, value.map(|v| v as Rc<dyn ICompositionObject>));
238    }
239
240    pub fn parent(&self) -> Option<Rc<CompositionVisual>> {
241        self.visual_props().parent().and_then(|o| o.into_any_rc().downcast::<CompositionVisual>().ok())
242    }
243
244    pub(crate) fn set_parent(&self, value: Option<Rc<CompositionVisual>>) {
245        self.visual_props().set_parent(self, value.map(|v| v as Rc<dyn ICompositionObject>));
246    }
247
248    /// The visual this visual is an adorner of: its transform follows that
249    /// visual.
250    pub fn adorned_visual(&self) -> Option<Rc<CompositionVisual>> {
251        self.visual_props().adorned_visual().and_then(|o| o.into_any_rc().downcast::<CompositionVisual>().ok())
252    }
253
254    pub fn set_adorned_visual(&self, value: Option<Rc<CompositionVisual>>) {
255        self.visual_props().set_adorned_visual(self, value.map(|v| v as Rc<dyn ICompositionObject>));
256    }
257
258    pub fn cache_mode(&self) -> Option<Rc<CompositionBitmapCache>> {
259        self.visual_props().cache_mode().and_then(|o| o.into_any_rc().downcast::<CompositionBitmapCache>().ok())
260    }
261
262    pub fn set_cache_mode(&self, value: Option<Rc<CompositionBitmapCache>>) {
263        self.visual_props().set_cache_mode(self, value.map(|v| v as Rc<dyn ICompositionObject>));
264    }
265
266    /// The WinUI clip of the visual (`Visual.Clip`): an
267    /// [`InsetClip`](super::InsetClip) or a
268    /// [`RectangleClip`](super::RectangleClip), applied in the coordinates
269    /// of the visual, after its transform, to its content and children.
270    ///
271    /// Deviation (winui-composition.md): WinUI names it `Clip`; here `clip`
272    /// is the geometry clip of the upstream compositor, so the WinUI member
273    /// is `composition_clip`. Both may be set; the visible area is their
274    /// intersection.
275    pub fn composition_clip(&self) -> Option<Rc<CompositionClip>> {
276        self.visual_props().composition_clip().and_then(|o| o.into_any_rc().downcast::<CompositionClip>().ok())
277    }
278
279    pub fn set_composition_clip(&self, value: Option<Rc<CompositionClip>>) {
280        self.visual_props().set_composition_clip(self, value.map(|v| v as Rc<dyn ICompositionObject>));
281    }
282
283    // --- Visual.cs ---------------------------------------------------------------
284
285    pub fn disable_sub_tree_bounds_hit_test_optimization(&self) -> bool {
286        self.custom_hit_test_count_in_sub_tree.get() != 0
287    }
288
289    pub fn opacity_mask(&self) -> Option<Rc<dyn IBrush>> {
290        self.opacity_mask.borrow().clone()
291    }
292
293    pub fn set_opacity_mask(&self, value: Option<Rc<dyn IBrush>>) {
294        let current = self.opacity_mask.borrow().clone();
295        let same = match (&current, &value) {
296            (Some(a), Some(b)) => std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b)),
297            (None, None) => true,
298            _ => false,
299        };
300        if same {
301            return;
302        }
303
304        let compositor = self.compositor().clone();
305        // Release the previous compositor-resource based brush
306        if let Some(old) = &current {
307            if let Some(resource) = old.as_composition_render_resource() {
308                resource.release_on_compositor(&compositor);
309                *self.opacity_mask.borrow_mut() = None;
310                self.visual_props().set_opacity_mask_brush_transport_field(self, None);
311            }
312        }
313
314        match &value {
315            Some(brush) if brush.as_composition_render_resource().is_some() => {
316                if let Some(resource) = brush.as_composition_render_resource() {
317                    resource.add_ref_on_compositor(&compositor);
318                }
319                let transport = brush_get_server_resource(Some(brush), Some(&compositor));
320                self.visual_props().set_opacity_mask_brush_transport_field(self, transport);
321                *self.opacity_mask.borrow_mut() = value;
322            }
323            _ => {
324                let immutable = value.as_ref().map(|brush| {
325                    let immutable: Rc<dyn IBrush> = BrushExtensions::to_immutable(brush);
326                    super::transport::BatchResource::Value(immutable)
327                });
328                *self.opacity_mask.borrow_mut() = value;
329                self.visual_props().set_opacity_mask_brush_transport_field(self, immutable);
330            }
331        }
332    }
333
334    /// The data the server wrote back for this visual, if it is valid for
335    /// the current read revision.
336    pub fn try_get_valid_readback(&self) -> Option<ReadbackData> {
337        let root = self.root()?;
338
339        let indices = self.compositor().readback().clone();
340        let readback = self.readback.get_readback(indices.read_revision())?;
341
342        // CompositionVisual wasn't visible or wasn't even attached to the composition target during the lat frame
343        if !readback.visible || readback.target_id != root.id() {
344            return None;
345        }
346
347        Some(readback)
348    }
349
350    pub fn tag(&self) -> Option<Rc<dyn Any>> {
351        self.tag.borrow().clone()
352    }
353
354    pub fn set_tag(&self, value: Option<Rc<dyn Any>>) {
355        *self.tag.borrow_mut() = value;
356    }
357
358    /// Whether the content of the visual is hit at a point in its own
359    /// coordinates.
360    pub fn hit_test(&self, point: Point) -> bool {
361        match &self.kind {
362            CompositionVisualKind::DrawList(data) => data.hit_test(point),
363            _ => true,
364        }
365    }
366
367    /// How the content of the visual intersects a geometry in its own
368    /// coordinates.
369    pub fn hit_test_geometry(&self, geometry: &Ref<Geometry>) -> IntersectionResult {
370        match &self.kind {
371            CompositionVisualKind::DrawList(data) => data.hit_test_geometry(geometry),
372            _ => IntersectionResult::Intersects,
373        }
374    }
375
376    fn serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
377        match &self.kind {
378            CompositionVisualKind::Container(props) => props.serialize_changes_core(self, writer),
379            CompositionVisualKind::DrawList(data) => data.serialize_changes_core(self, writer),
380            CompositionVisualKind::SolidColor(props) => props.serialize_changes_core(self, writer),
381            CompositionVisualKind::Surface(props) => props.serialize_changes_core(self, writer),
382            CompositionVisualKind::Custom(data) => data.props().serialize_changes_core(self, writer),
383            CompositionVisualKind::Sprite(props) => props.serialize_changes_core(self, writer),
384            CompositionVisualKind::Shape(props) => props.serialize_changes_core(self, writer),
385            CompositionVisualKind::Layer(props) => props.serialize_changes_core(self, writer),
386            CompositionVisualKind::Redirect(props) => props.serialize_changes_core(self, writer),
387        }
388    }
389}
390
391impl CompositionVisualHooks for CompositionVisual {
392    fn on_root_changed(&self) {
393        // OnRootChangedCore of the container visual.
394        let root = self.root();
395        // By index, as the original enumerates the list: no copy of the
396        // children for every visual of a subtree that is attached or detached.
397        let mut index = 0;
398        while index < self.children.count() {
399            self.children.get(index).set_root(root.clone());
400            index += 1;
401        }
402    }
403
404    fn on_parent_changing(&self) {
405        // Propagate the blight
406        let count = self.custom_hit_test_count_in_sub_tree.get();
407        if count != 0 {
408            let mut parent = self.parent();
409            while let Some(p) = parent {
410                p.custom_hit_test_count_in_sub_tree.set(p.custom_hit_test_count_in_sub_tree.get() - count);
411                parent = p.parent();
412            }
413        }
414    }
415
416    fn on_parent_changed(&self) {
417        self.set_root(self.parent().and_then(|parent| parent.root()));
418
419        // Propagate the blight
420        let count = self.custom_hit_test_count_in_sub_tree.get();
421        if count != 0 {
422            let mut parent = self.parent();
423            while let Some(p) = parent {
424                p.custom_hit_test_count_in_sub_tree.set(p.custom_hit_test_count_in_sub_tree.get() + count);
425                parent = p.parent();
426            }
427        }
428    }
429}
430
431impl CompositionContainerVisualHooks for CompositionVisual {}
432
433impl IRegisterForSerialization for CompositionVisual {
434    fn register_for_serialization(&self) {
435        self.object
436            .register_for_serialization(|| self.this.upgrade().map(|this| this as Rc<dyn ICompositorSerializable>));
437    }
438}
439
440impl ICompositionObjectHost for CompositionVisual {
441    fn server(&self) -> ServerObjectId {
442        self.object.required_server()
443    }
444
445    fn pending_animations(&self) -> &PendingAnimations {
446        self.object.pending_animations()
447    }
448
449    fn implicit_animation(&self, property_name: &str) -> Option<Rc<dyn ICompositionAnimationBase>> {
450        self.object.implicit_animation(property_name)
451    }
452
453    fn start_animation_group(
454        &self,
455        grp: &Rc<dyn ICompositionAnimationBase>,
456        target: &str,
457        final_value: ExpressionVariant,
458    ) -> bool {
459        self.start_animation_group_for(&**grp, target, final_value)
460    }
461}
462
463impl ICompositionObjectAnimations for CompositionVisual {
464    fn try_start_animation(
465        &self,
466        property_name: &str,
467        animation: &dyn ICompositionAnimation,
468        final_value: Option<ExpressionVariant>,
469    ) -> bool {
470        match &self.kind {
471            CompositionVisualKind::Container(props) => props.start_animation(self, property_name, animation, final_value),
472            CompositionVisualKind::DrawList(data) => {
473                data.props().start_animation(self, property_name, animation, final_value)
474            }
475            CompositionVisualKind::SolidColor(props) => props.start_animation(self, property_name, animation, final_value),
476            CompositionVisualKind::Surface(props) => props.start_animation(self, property_name, animation, final_value),
477            CompositionVisualKind::Custom(data) => {
478                data.props().start_animation(self, property_name, animation, final_value)
479            }
480            CompositionVisualKind::Sprite(props) => props.start_animation(self, property_name, animation, final_value),
481            CompositionVisualKind::Shape(props) => props.start_animation(self, property_name, animation, final_value),
482            CompositionVisualKind::Layer(props) => props.start_animation(self, property_name, animation, final_value),
483            CompositionVisualKind::Redirect(props) => props.start_animation(self, property_name, animation, final_value),
484        }
485    }
486
487    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
488        match &self.kind {
489            CompositionVisualKind::SolidColor(_) => {
490                super::generated::ServerCompositionSolidColorVisualProps::get_composition_property(property_name)
491                    .or_else(|| ServerCompositionContainerVisualProps::get_composition_property(property_name))
492            }
493            _ => ServerCompositionContainerVisualProps::get_composition_property(property_name),
494        }
495    }
496
497    fn composition_object(&self) -> &CompositionObject {
498        &self.object
499    }
500}
501
502impl AsCompositionObject for CompositionVisual {
503    fn as_composition_object(&self) -> &CompositionObject {
504        &self.object
505    }
506
507    fn composition_type_name(&self) -> &'static str {
508        match &self.kind {
509            CompositionVisualKind::Container(_) => "CompositionContainerVisual",
510            CompositionVisualKind::DrawList(_) => "CompositionDrawListVisual",
511            CompositionVisualKind::SolidColor(_) => "CompositionSolidColorVisual",
512            CompositionVisualKind::Surface(_) => "CompositionSurfaceVisual",
513            CompositionVisualKind::Custom(_) => "CompositionCustomVisual",
514            CompositionVisualKind::Sprite(_) => "SpriteVisual",
515            CompositionVisualKind::Shape(_) => "ShapeVisual",
516            CompositionVisualKind::Layer(_) => "LayerVisual",
517            CompositionVisualKind::Redirect(_) => "RedirectVisual",
518        }
519    }
520}
521
522impl ICompositionObject for CompositionVisual {
523    fn server(&self) -> ServerObjectId {
524        self.object.required_server()
525    }
526
527    fn as_any(&self) -> &dyn Any {
528        self
529    }
530
531    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
532        self
533    }
534}
535
536impl ICompositorSerializable for CompositionVisual {
537    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
538        self.object.try_get_server(c)
539    }
540
541    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
542        self.object.begin_serialize_changes(c);
543        self.serialize_changes_core(writer);
544    }
545}