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