Skip to main content

ferroui_base/
visual.rs

1use crate::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
2use crate::media::{
3    can_value_affect_render, subscribe_invalidated, CacheMode, DrawingContext, FlowDirection, Geometry, IBrush, IEffect,
4    ITransform, RenderOptions, TextOptions, Transform,
5};
6use crate::rendering::composition::{CompositionDrawListVisual, CompositionVisual, Compositor};
7use crate::reactive::{Disposable, IDisposable};
8use crate::rendering::IPresentationSource;
9use crate::utilities::HandlerList;
10use crate::{
11    ferro_class, ferro_property, instantiate, AttachedProperty, DirectProperty, FerroObjectImpl,
12    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, ObjectType, Rect, Ref,
13    RelativePoint, StyledElement,
14    StyledElementImpl, StyledElementImplExt, StyledProperty, StyledPropertyOptions, Upcast,
15    VisualTreeAttachmentEventArgs, WeakRef,
16};
17use std::cell::{Cell, OnceCell, RefCell};
18use std::rc::Rc;
19
20/// Base class for controls that provides rendering and related visual
21/// properties.
22///
23/// The visual class represents elements that have a visual on-screen
24/// representation and stores all the information needed for a renderer to
25/// render the control. To traverse the visual tree, use the methods in the
26/// `visual_tree` module.
27#[repr(C)]
28pub struct Visual {
29    base: StyledElement,
30    bounds: Cell<Rect>,
31    presentation_source: RefCell<Option<Rc<dyn IPresentationSource>>>,
32    visual_parent: RefCell<Option<WeakRef<Visual>>>,
33    has_mirror_transform: Cell<bool>,
34    // Deviation (DEVIATIONS.md, Visual tree): whether `RenderTransform` has a value, kept by its
35    // change handler, so that the transforms of the visual tree skip the property read.
36    has_render_transform: Cell<bool>,
37    // Not upstream: whether the visual has a manipulation transform (the zoom a scroll presenter
38    // applies to its content), kept so that the transforms of the visual tree skip the lookup.
39    has_manipulation_transform: Cell<bool>,
40    is_effectively_visible: Cell<bool>,
41    has_non_uniform_z_index_children: Cell<bool>,
42    visual_children: OnceCell<FerroList<Ref<Visual>>>,
43    attached_to_visual_tree: HandlerList<dyn Fn(&VisualTreeAttachmentEventArgs)>,
44    detached_from_visual_tree: HandlerList<dyn Fn(&VisualTreeAttachmentEventArgs)>,
45    is_effectively_visible_changed: HandlerList<dyn Fn()>,
46    render_options: Cell<RenderOptions>,
47    text_options: Cell<TextOptions>,
48    composition_visual: RefCell<Option<CompositionDrawListVisual>>,
49    child_composition_visual: RefCell<Option<Rc<CompositionVisual>>>,
50    /// What the composition API of WinUI adds to the visual (the `Translation` facade, the
51    /// requests to hide the composition visual, the layer composition visual): boxed and
52    /// created on first use, so that a visual that uses none of it pays one pointer.
53    composition_extras: RefCell<Option<Box<CompositionExtras>>>,
54    /// The subscription to the `changed` notification of the render
55    /// transform, held while the visual is attached to a visual tree.
56    render_transform_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
57    affects_render_subscriptions: RefCell<Vec<(&'static FerroProperty, Rc<dyn IDisposable>)>>,
58}
59
60/// The state of the WinUI composition API on a visual (see
61/// `docs/porting/winui-composition.md`).
62#[derive(Default)]
63struct CompositionExtras {
64    /// `UIElement.Translation`: `None` while the facade was never set, so that the translation
65    /// of the composition visual is left to the application.
66    translation: Option<crate::Vector3D>,
67    /// The number of outstanding requests to hide the composition visual (a connected animation
68    /// shows the element through a visual surface while it flies).
69    hide_requests: u32,
70    /// Source-free presentation suppression honored by both the compositor and immediate
71    /// rendering. Unlike composition hiding, bitmap/visual-brush captures also omit it.
72    render_suppression_requests: u32,
73    /// The number of outstanding requests to keep the composition visual visible although the
74    /// visual is not (an implicit hide animation runs).
75    show_requests: u32,
76    /// A composition visual shown above the children and the child composition visual: the
77    /// layer of the connected animations on a top level.
78    layer_visual: Option<Rc<CompositionVisual>>,
79    /// Composition visuals shown after the children: the images of removed children that play
80    /// their implicit hide animations (WinUI's unloading storage).
81    unloading_visuals: Vec<Rc<CompositionVisual>>,
82    /// The other transform facades of `UIElement` (`Rotation`, `RotationAxis`, `Scale`,
83    /// `CenterPoint`, `TransformMatrix`) and the transitions (`OpacityTransition`,
84    /// `RotationTransition`, `ScaleTransition`, `TranslationTransition`).
85    facades: composition_facades::CompositionFacades,
86    /// `UIElement.Lights`: the XAML lights the element holds (winui-materials.md), and the
87    /// subscriptions that connect them while the element is in a visual tree.
88    lights: Option<FerroList<Ref<crate::media::XamlLight>>>,
89    lights_subscriptions: Vec<Rc<dyn IDisposable>>,
90    /// The ids of the XAML lights that target the element (`XamlLight.AddTargetElement`), and
91    /// the subscription that offers the element to those lights when it enters a tree.
92    light_target_ids: Vec<String>,
93    light_target_subscription: Option<Rc<dyn IDisposable>>,
94    /// The manipulation transform of the visual (see [`Visual::manipulation_transform`]).
95    manipulation_transform: Option<crate::Matrix>,
96    /// The theme shadow of the visual (`UIElement.Shadow`): `None` while the visual never had a
97    /// `Shadow` and is not the child of a popup with one.
98    theme_shadow: Option<ThemeShadowState>,
99}
100
101/// What a visual knows of the theme shadow it casts (see `visual/theme_shadow.rs`).
102#[derive(Default)]
103struct ThemeShadowState {
104    /// The popup whose `Shadow` the visual casts as its child.
105    popup: Option<WeakRef<Visual>>,
106    /// Whether the visual is a popup, whose `Shadow` its child casts.
107    delegates_to_child: bool,
108    /// The visual that draws the shadow, shown by the parent before the composition visual of
109    /// this visual.
110    visual: Option<Rc<crate::rendering::composition::ThemeShadowVisual>>,
111}
112
113mod composition_facades;
114mod theme_shadow;
115#[cfg(test)]
116mod composition_facades_tests;
117
118pub use composition_facades::{ScalarTransition, Vector3Transition, Vector3TransitionComponents};
119
120ferro_class! {
121    Visual: StyledElement, virtuals VisualImpl: StyledElementImpl {
122        /// Renders the visual to a drawing context.
123        fn render(this, context: &mut DrawingContext);
124        /// Calls the `on_attached_to_visual_tree` method for this control and
125        /// all of its visual descendants.
126        fn on_attached_to_visual_tree_core(this, e: &VisualTreeAttachmentEventArgs);
127        /// Calls the `on_detached_from_visual_tree` method for this control
128        /// and all of its visual descendants.
129        fn on_detached_from_visual_tree_core(this, e: &VisualTreeAttachmentEventArgs);
130        /// Called when the control is added to a rooted visual tree.
131        fn on_attached_to_visual_tree(this, e: &VisualTreeAttachmentEventArgs);
132        /// Called when the control is removed from a rooted visual tree.
133        fn on_detached_from_visual_tree(this, e: &VisualTreeAttachmentEventArgs);
134        /// Called when the control's visual parent changes.
135        fn on_visual_parent_changed(this, old_parent: Option<&Ref<Visual>>, new_parent: Option<&Ref<Visual>>);
136        /// Whether the control ignores the flow direction policies, i.e. is
137        /// never mirrored.
138        fn bypass_flow_direction_policies(this) -> bool;
139        /// Computes the `has_mirror_transform` value according to the flow
140        /// direction of this visual and its parent.
141        fn invalidate_mirror_transform(this);
142        /// The corner radius the visual clips its content with when
143        /// `clip_to_bounds` is set; `None` for a plain rectangular clip.
144        /// (Upstream: the `IVisualWithRoundRectClip` interface.)
145        fn clip_to_bounds_radius(this) -> Option<crate::CornerRadius>;
146        /// The custom hit test of the visual at a point in its own
147        /// coordinates; `None` when the visual has none and is hit by what
148        /// it occupies or draws. (Upstream: the `ICustomHitTest` interface;
149        /// a class implementing it overrides this and the geometry form.)
150        fn custom_hit_test(this, point: crate::Point) -> Option<bool>;
151        /// The custom hit test of the visual against a geometry in its own
152        /// coordinates; `None` when the visual has none.
153        fn custom_hit_test_geometry(this, geometry: &Ref<crate::media::Geometry>) -> Option<crate::media::IntersectionResult>;
154        /// Ensures that the visual is ready to use as the visual in a
155        /// visual brush. (Upstream: the `IVisualBrushInitialize` interface;
156        /// a class implementing it overrides this.)
157        fn ensure_initialized_for_visual_brush(this);
158        /// Creates the composition visual of the visual on `compositor`.
159        fn create_composition_visual(this, compositor: &Rc<Compositor>) -> CompositionDrawListVisual;
160        /// Releases the composition visual of the visual.
161        fn detach_from_compositor(this);
162        /// Makes the children of the composition visual match the visual
163        /// children (in Z order) and the child composition visual.
164        fn synchronize_composition_child_visuals(this);
165        /// Copies the properties of the visual to its composition visual.
166        fn synchronize_composition_properties(this);
167    }
168}
169
170crate::ferro_thread_local! {
171    static ROOTED_VISUAL_CHILDREN_COUNT: Cell<i32> = const { Cell::new(0) };
172}
173crate::ferro_class_info!(Visual { new: Visual::new });
174
175crate::ferro_thread_local! {
176    // Not upstream: a version of what the effective viewports of the visuals are computed from (the
177    // bounds, the visual parents, `ClipToBounds`, `RenderTransform` and `RenderTransformOrigin` of
178    // the visuals of the thread), which the layout manager reads to reuse the viewports of shared
179    // ancestors within one pass (DEVIATIONS.md, Layout).
180    static GEOMETRY_VERSION: Cell<u64> = const { Cell::new(0) };
181}
182
183/// The version of the geometry of the visuals of the thread (see `GEOMETRY_VERSION`).
184#[inline]
185pub(crate) fn geometry_version() -> u64 {
186    GEOMETRY_VERSION.with(Cell::get)
187}
188
189#[inline]
190fn bump_geometry_version() {
191    GEOMETRY_VERSION.with(|version| version.set(version.get().wrapping_add(1)));
192}
193
194crate::ferro_overrides! { impl FerroObjectImpl for Visual {
195    fn constructed(this: &Self) {
196        Self::parent_constructed(this);
197
198        // Disable transitions until we're added to the visual tree.
199        this.disable_transitions();
200    }
201
202    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
203        Self::parent_on_property_changed(this, change);
204
205        if change.property() == Self::is_visible_property().as_property() {
206            let parent_state = this.visual_parent().is_none_or(|p| p.is_effectively_visible());
207            this.update_is_effectively_visible(parent_state);
208        } else if change.property() == Self::shadow_property().as_property() {
209            let old = change.get_old_value::<Option<Ref<crate::media::Shadow>>>().flatten();
210            let new = change.get_new_value::<Option<Ref<crate::media::Shadow>>>();
211            this.on_shadow_changed(old.as_ref(), new.as_ref());
212        } else if change.property() == crate::styling::ThemeVariant::actual_theme_variant_property().as_property() {
213            // The shadow of the theme is drawn in the theme of the element.
214            if this.may_cast_theme_shadow() {
215                this.invalidate_visual();
216            }
217        } else if change.property() == Self::flow_direction_property().as_property() {
218            this.invalidate_mirror_transform();
219            if let Some(children) = this.visual_children.get() {
220                for child in children.snapshot().iter() {
221                    child.invalidate_mirror_transform();
222                }
223            }
224        }
225    }
226} }
227
228impl StyledElementImpl for Visual {
229    fn logical_children_collection_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<StyledElement>>) {
230        Self::parent_logical_children_collection_changed(this, e);
231        if let Some(source) = this.presentation_source() {
232            source.renderer().recalculate_children(this);
233        }
234    }
235
236    fn on_templated_parent_control_theme_changed(this: &Self) {
237        Self::parent_on_templated_parent_control_theme_changed(this);
238        let templated_parent = this.templated_parent();
239        if let Some(children) = this.visual_children.get() {
240            for child in children.snapshot().iter() {
241                if child.templated_parent() == templated_parent {
242                    child.on_templated_parent_control_theme_changed();
243                }
244            }
245        }
246    }
247}
248
249impl VisualImpl for Visual {
250    fn render(_this: &Self, _context: &mut DrawingContext) {}
251
252    fn on_attached_to_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
253        *this.presentation_source.borrow_mut() = Some(e.presentation_source().clone());
254        ROOTED_VISUAL_CHILDREN_COUNT.with(|count| count.set(count.get() + 1));
255
256        if let Some(render_transform) = this.render_transform() {
257            if let Some(mutable_transform) = render_transform.as_mutable_transform() {
258                let weak = this.to_ref().downgrade();
259                let subscription = mutable_transform.changed(Rc::new(move || {
260                    if let Some(visual) = weak.upgrade() {
261                        visual.render_transform_value_changed();
262                    }
263                }));
264                this.set_render_transform_subscription(Some(subscription));
265            }
266        }
267
268        this.enable_transitions();
269        let renderer = e.presentation_source().renderer();
270        if let Some(compositor) = renderer.compositor() {
271            this.attach_to_compositor(&compositor);
272        }
273
274        this.invalidate_mirror_transform();
275        let parent = this.visual_parent();
276        this.update_is_effectively_visible(parent.as_ref().is_none_or(|p| p.is_effectively_visible()));
277        this.on_attached_to_visual_tree(e);
278        if !this.attached_to_visual_tree.is_empty() {
279            for (_, handler) in this.attached_to_visual_tree.snapshot().iter() {
280                handler(e);
281            }
282        }
283        this.invalidate_visual();
284
285        if let Some(parent) = &parent {
286            renderer.recalculate_children(parent);
287            if this.z_index() != 0 {
288                parent.has_non_uniform_z_index_children.set(true);
289            }
290        }
291
292        if let Some(children) = this.visual_children.get() {
293            let this_ref = this.to_ref();
294            for child in children.snapshot().iter() {
295                // An event handler may have modified the children: skip a
296                // child which has already been attached, or which has been
297                // removed from this visual (this is a snapshot).
298                let attached_here = child
299                    .presentation_source()
300                    .is_some_and(|s| Rc::ptr_eq(&s, e.presentation_source()));
301                if !attached_here && child.visual_parent().is_some_and(|p| p == this_ref) {
302                    child.on_attached_to_visual_tree_core(e);
303                }
304            }
305        }
306    }
307
308    fn on_detached_from_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
309        ROOTED_VISUAL_CHILDREN_COUNT.with(|count| count.set(count.get() - 1));
310
311        this.set_render_transform_subscription(None);
312
313        this.disable_transitions();
314        this.update_is_effectively_visible(true);
315        this.on_detached_from_visual_tree(e);
316        this.detach_from_compositor();
317        if !this.detached_from_visual_tree.is_empty() {
318            for (_, handler) in this.detached_from_visual_tree.snapshot().iter() {
319                handler(e);
320            }
321        }
322        if let Some(source) = this.presentation_source() {
323            source.renderer().add_dirty(this);
324        }
325        *this.presentation_source.borrow_mut() = None;
326
327        if let Some(children) = this.visual_children.get() {
328            for child in children.snapshot().iter() {
329                // A child removed within an event handler has already been
330                // detached by the removal itself.
331                if child.presentation_source().is_some() {
332                    child.on_detached_from_visual_tree_core(e);
333                }
334            }
335        }
336    }
337
338    fn on_attached_to_visual_tree(_this: &Self, _e: &VisualTreeAttachmentEventArgs) {}
339
340    fn on_detached_from_visual_tree(_this: &Self, _e: &VisualTreeAttachmentEventArgs) {}
341
342    fn on_visual_parent_changed(this: &Self, old_parent: Option<&Ref<Visual>>, new_parent: Option<&Ref<Visual>>) {
343        this.raise_direct_property_changed(
344            Self::visual_parent_property(),
345            &old_parent.cloned(),
346            &new_parent.cloned(),
347        );
348    }
349
350    fn bypass_flow_direction_policies(_this: &Self) -> bool {
351        false
352    }
353
354    fn clip_to_bounds_radius(_this: &Self) -> Option<crate::CornerRadius> {
355        None
356    }
357
358    fn custom_hit_test(_this: &Self, _point: crate::Point) -> Option<bool> {
359        None
360    }
361
362    fn custom_hit_test_geometry(
363        _this: &Self,
364        _geometry: &Ref<crate::media::Geometry>,
365    ) -> Option<crate::media::IntersectionResult> {
366        None
367    }
368
369    fn ensure_initialized_for_visual_brush(_this: &Self) {}
370
371    fn create_composition_visual(this: &Self, compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
372        CompositionDrawListVisual::new(compositor, this)
373    }
374
375    fn detach_from_compositor(this: &Self) {
376        // The shadow visual belongs to the composition visual it follows.
377        this.release_theme_shadow_visual();
378        let composition_visual = this.composition_visual.borrow_mut().take();
379        if let Some(composition_visual) = composition_visual {
380            if let Some(child) = this.child_composition_visual() {
381                composition_visual.children().remove(&child);
382            }
383            if let Some(layer) = this.composition_layer_visual() {
384                composition_visual.children().remove(&layer);
385            }
386            composition_visual.set_draw_list(None);
387            composition_visual.set_opacity_mask(None);
388            // The composition visual and its children refer to each other;
389            // nothing collects such a cycle here, so the visual lets go of
390            // its children. They are re-added when their visuals are
391            // synchronized again.
392            composition_visual.children().clear();
393        }
394    }
395
396    fn synchronize_composition_child_visuals(this: &Self) {
397        let Some(composition_visual) = this.composition_visual() else { return };
398        let composition_children = composition_visual.children();
399        let visual_children: Rc<Vec<Ref<Visual>>> =
400            this.visual_children_snapshot().unwrap_or_else(|| Rc::new(Vec::new()));
401
402        let mut sorted_children: Option<Vec<(Ref<Visual>, usize)>> = None;
403        if this.has_non_uniform_z_index_children() && visual_children.len() > 1 {
404            let mut sorted: Vec<(Ref<Visual>, usize)> =
405                visual_children.iter().cloned().enumerate().map(|(index, visual)| (visual, index)).collect();
406            // The indices keep the order of elements with the same Z index.
407            sorted.sort_by(|lhs, rhs| lhs.0.z_index().cmp(&rhs.0.z_index()).then(lhs.1.cmp(&rhs.1)));
408            sorted_children = Some(sorted);
409        }
410
411        let mut child_visual = this.child_composition_visual();
412
413        // Check if the current visual somehow got migrated to another compositor
414        if child_visual.as_ref().is_some_and(|child| !Rc::ptr_eq(child.compositor(), composition_visual.compositor())) {
415            child_visual = None;
416        }
417
418        let mut layer_visual = this.composition_layer_visual();
419        if layer_visual.as_ref().is_some_and(|layer| !Rc::ptr_eq(layer.compositor(), composition_visual.compositor())) {
420            layer_visual = None;
421        }
422
423        let unloading_visuals: Vec<Rc<CompositionVisual>> = this
424            .read_composition_extras(|extras| extras.unloading_visuals.clone())
425            .unwrap_or_default()
426            .into_iter()
427            .filter(|unloading| Rc::ptr_eq(unloading.compositor(), composition_visual.compositor()))
428            .collect();
429
430        let child_count = visual_children.len();
431        let child_at = |c: usize| -> &Ref<Visual> {
432            match &sorted_children {
433                Some(sorted) => &sorted[c].0,
434                None => &visual_children[c],
435            }
436        };
437
438        // The visuals that draw the theme shadows of the children, each shown just before the
439        // visual of its child (WinUI parents it under the prepend visual of the caster), so that
440        // it is under the child and clipped by the clips of this visual, not by the child's.
441        // Looked at only when a child may cast one.
442        let shadows: Option<Vec<Option<Rc<CompositionVisual>>>> =
443            if visual_children.iter().any(|child| child.may_cast_theme_shadow()) {
444                Some((0..child_count).map(|c| child_at(c).ensure_theme_shadow_composition_visual()).collect())
445            } else {
446                None
447            };
448        let shadow_at = |c: usize| -> Option<&Rc<CompositionVisual>> {
449            shadows.as_ref().and_then(|shadows| shadows[c].as_ref())
450        };
451        let shadow_count = shadows.as_ref().map_or(0, |shadows| shadows.iter().flatten().count());
452
453        let mut expected_count = child_count + shadow_count + unloading_visuals.len();
454        if child_visual.is_some() {
455            expected_count += 1;
456        }
457        if layer_visual.is_some() {
458            expected_count += 1;
459        }
460
461        let same = |a: &Rc<CompositionVisual>, b: Option<CompositionDrawListVisual>| {
462            b.is_some_and(|b| Rc::ptr_eq(a, &b))
463        };
464
465        if composition_children.count() == expected_count {
466            let mut mismatch = false;
467            let mut index = 0;
468            for c in 0..child_count {
469                if let Some(shadow) = shadow_at(c) {
470                    if !Rc::ptr_eq(&composition_children.get(index), shadow) {
471                        mismatch = true;
472                        break;
473                    }
474                    index += 1;
475                }
476                if !same(&composition_children.get(index), child_at(c).composition_visual()) {
477                    mismatch = true;
478                    break;
479                }
480                index += 1;
481            }
482
483            if !mismatch {
484                for unloading in &unloading_visuals {
485                    if !Rc::ptr_eq(&composition_children.get(index), unloading) {
486                        mismatch = true;
487                    }
488                    index += 1;
489                }
490                if let Some(child_visual) = &child_visual {
491                    if !Rc::ptr_eq(&composition_children.get(index), child_visual) {
492                        mismatch = true;
493                    }
494                    index += 1;
495                }
496                if let Some(layer_visual) = &layer_visual {
497                    if !Rc::ptr_eq(&composition_children.get(index), layer_visual) {
498                        mismatch = true;
499                    }
500                }
501            }
502
503            if !mismatch {
504                return;
505            }
506        }
507
508        composition_children.clear();
509        for c in 0..child_count {
510            if let Some(shadow) = shadow_at(c) {
511                composition_children.add(shadow.clone());
512            }
513            if let Some(composition_child) = child_at(c).composition_visual() {
514                composition_children.add((*composition_child).clone());
515            }
516        }
517
518        for unloading in unloading_visuals {
519            composition_children.add(unloading);
520        }
521        if let Some(child_visual) = child_visual {
522            composition_children.add(child_visual);
523        }
524        if let Some(layer_visual) = layer_visual {
525            composition_children.add(layer_visual);
526        }
527    }
528
529    fn synchronize_composition_properties(this: &Self) {
530        let Some(comp) = this.composition_visual() else { return };
531        let bounds = this.bounds();
532
533        // The implicit animations of the transitions, installed before the properties are
534        // written (so that a change animates), except on the first synchronization of a new
535        // composition visual (so that the initial values do not).
536        this.apply_composition_transitions(&comp, false);
537
538        // TODO: Introduce a dirty mask like WPF has, so we don't overwrite properties every time
539        comp.set_offset(crate::Vector3D::new(bounds.x, bounds.y, 0.0));
540        comp.set_size(crate::Vector::new(bounds.width, bounds.height));
541        comp.set_visible(
542            (this.is_visible() || this.is_shown_by_composition())
543                && !this.is_hidden_by_composition()
544                && !this.is_render_suppressed(),
545        );
546        comp.set_opacity(this.opacity() as f32);
547        comp.set_clip_to_bounds(this.clip_to_bounds());
548        comp.set_clip(this.clip().and_then(|clip| clip.platform_impl()));
549
550        let opacity_mask = this.opacity_mask();
551        let same_mask = match (comp.opacity_mask(), &opacity_mask) {
552            (Some(a), Some(b)) => *a == **b,
553            (None, None) => true,
554            _ => false,
555        };
556        if !same_mask {
557            comp.set_opacity_mask(opacity_mask);
558        }
559
560        let cache_mode = this.cache_mode().map(|cache_mode| cache_mode.get_for_compositor(comp.compositor()));
561        let same_cache_mode = match (comp.cache_mode(), &cache_mode) {
562            (Some(a), Some(b)) => Rc::ptr_eq(&a, b),
563            (None, None) => true,
564            _ => false,
565        };
566        if !same_cache_mode {
567            comp.set_cache_mode(cache_mode);
568        }
569
570        let effect = this.effect();
571        if !crate::media::EffectExtensions::effect_equals(comp.effect().as_deref(), effect.as_deref()) {
572            comp.set_effect(effect.as_ref().map(crate::media::EffectExtensions::to_immutable));
573        }
574
575        comp.set_render_options(this.render_options());
576        comp.set_text_options(this.text_options());
577
578        let mut render_transform = crate::Matrix::IDENTITY;
579
580        if this.has_mirror_transform() {
581            render_transform = crate::Matrix::new(-1.0, 0.0, 0.0, 1.0, bounds.width, 0.0);
582        }
583
584        if let Some(transform) = this.render_transform() {
585            let origin = this.render_transform_origin().to_pixels(crate::Size::new(bounds.width, bounds.height));
586            let offset = crate::Matrix::create_translation(origin.x, origin.y);
587            render_transform = render_transform * ((-offset) * transform.value() * offset);
588        }
589
590        // The `TransformMatrix` facade follows the render transform (see
591        // `composition_facade_transform_matrix`).
592        if let Some(facade) = this.composition_facade_transform_matrix() {
593            render_transform = render_transform * facade;
594        }
595
596        // The zoom of a scroll presenter, after the transforms of the element itself.
597        if let Some(manipulation) = this.manipulation_transform() {
598            render_transform = render_transform * manipulation;
599        }
600
601        comp.set_transform_matrix(render_transform);
602
603        if let Some(translation) = this.composition_translation() {
604            comp.set_translation(translation);
605        }
606
607        this.synchronize_composition_facades(&comp);
608        this.apply_composition_transitions(&comp, true);
609
610        // The shadow of the theme follows the size, the visibility, the elevation and the theme.
611        this.update_theme_shadow_visual();
612    }
613
614    fn invalidate_mirror_transform(this: &Self) {
615        let flow_direction = this.flow_direction();
616        let mut parent_flow_direction = FlowDirection::LeftToRight;
617        let bypass = this.bypass_flow_direction_policies();
618        let mut parent_bypass = false;
619
620        if let Some(parent) = this.visual_parent() {
621            parent_flow_direction = parent.flow_direction();
622            parent_bypass = parent.bypass_flow_direction_policies();
623        }
624
625        let this_should_be_mirrored = flow_direction == FlowDirection::RightToLeft && !bypass;
626        let parent_should_be_mirrored = parent_flow_direction == FlowDirection::RightToLeft && !parent_bypass;
627
628        this.set_has_mirror_transform(this_should_be_mirrored != parent_should_be_mirrored);
629    }
630}
631
632// Deviation (DEVIATIONS.md, Visual tree): the subscriptions to the values of
633// the render-affecting properties are weak events in the managed original (`InvalidatedWeakEvent`): the value does not
634// keep the visual, and the entries of visuals that were collected are removed
635// from its list when the list is compacted. Here the handler of a visual holds
636// it weakly too, and the visual takes its handlers out of the lists when it is
637// dropped: a value many visuals share (the geometry of an icon that is a
638// resource) would otherwise keep one entry, and the memory of the visual its
639// weak reference holds, for every visual that ever drew it.
640impl Drop for Visual {
641    fn drop(&mut self) {
642        for (_, subscription) in self.affects_render_subscriptions.get_mut().drain(..) {
643            subscription.dispose();
644        }
645    }
646}
647
648crate::ferro_properties! { impl Visual {
649    ferro_property!(
650        /// Defines the `Bounds` property.
651        pub fn bounds_property() -> DirectProperty<Visual, Rect> {
652            FerroProperty::register_direct::<Visual, _>("Bounds", |o| o.bounds(), None, Rect::default())
653        }
654    );
655
656    ferro_property!(
657        /// Defines the `ClipToBounds` property.
658        pub fn clip_to_bounds_property() -> StyledProperty<bool> {
659            FerroProperty::register::<Visual, _>("ClipToBounds", false)
660        }
661    );
662
663    ferro_property!(
664        /// Defines the `Clip` property.
665        pub fn clip_property() -> StyledProperty<Option<Ref<Geometry>>> {
666            FerroProperty::register::<Visual, _>("Clip", None)
667        }
668    );
669
670    ferro_property!(
671        /// Defines the `Shadow` property (WinUI `UIElement.Shadow`): the shadow the element casts,
672        /// a [`ThemeShadow`](crate::media::ThemeShadow).
673        pub fn shadow_property() -> StyledProperty<Option<Ref<crate::media::Shadow>>> {
674            FerroProperty::register::<Visual, _>("Shadow", None)
675        }
676    );
677
678    ferro_property!(
679        /// Defines the `Translation` property (WinUI `UIElement.Translation`): the facade
680        /// [`translation`](Visual::translation) as a property markup can set
681        /// (`Translation="0,0,32"`).
682        pub fn translation_property() -> DirectProperty<Visual, crate::Vector3D> {
683            FerroProperty::register_direct::<Visual, _>(
684                "Translation",
685                |o| o.translation(),
686                Some(|o, value| o.set_translation(value)),
687                crate::Vector3D::default(),
688            )
689        }
690    );
691
692    ferro_property!(
693        /// Defines the `CacheMode` property.
694        pub fn cache_mode_property() -> StyledProperty<Option<Ref<CacheMode>>> {
695            FerroProperty::register::<Visual, _>("CacheMode", None)
696        }
697    );
698
699    ferro_property!(
700        /// Defines the `Effect` property.
701        pub fn effect_property() -> StyledProperty<Option<Rc<dyn IEffect>>> {
702            FerroProperty::register::<Visual, _>("Effect", None)
703        }
704    );
705
706    ferro_property!(
707        /// Defines the `OpacityMask` property.
708        pub fn opacity_mask_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
709            FerroProperty::register::<Visual, _>("OpacityMask", None)
710        }
711    );
712
713    ferro_property!(
714        /// Defines the `RenderTransform` property.
715        pub fn render_transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
716            FerroProperty::register::<Visual, _>("RenderTransform", None)
717        }
718    );
719
720    ferro_property!(
721        /// Defines the `RenderTransformOrigin` property.
722        pub fn render_transform_origin_property() -> StyledProperty<RelativePoint> {
723            FerroProperty::register::<Visual, _>("RenderTransformOrigin", RelativePoint::CENTER)
724        }
725    );
726
727    ferro_property!(
728        /// Defines the `IsVisible` property.
729        pub fn is_visible_property() -> StyledProperty<bool> {
730            FerroProperty::register::<Visual, _>("IsVisible", true)
731        }
732    );
733
734    ferro_property!(
735        /// Defines the `Opacity` property.
736        pub fn opacity_property() -> StyledProperty<f64> {
737            FerroProperty::register::<Visual, _>("Opacity", 1.0)
738        }
739    );
740
741    ferro_property!(
742        /// Defines the `HasMirrorTransform` property.
743        pub fn has_mirror_transform_property() -> DirectProperty<Visual, bool> {
744            FerroProperty::register_direct::<Visual, _>("HasMirrorTransform", |o| o.has_mirror_transform(), None, false)
745        }
746    );
747
748    ferro_property!(
749        /// Defines the `FlowDirection` attached property.
750        pub fn flow_direction_property() -> AttachedProperty<FlowDirection> {
751            FerroProperty::register_attached_with::<Visual, Visual, _>(
752                "FlowDirection",
753                StyledPropertyOptions::new(FlowDirection::LeftToRight).inherits(true),
754            )
755        }
756    );
757
758    ferro_property!(
759        /// Defines the `VisualParent` property.
760        pub fn visual_parent_property() -> DirectProperty<Visual, Option<Ref<Visual>>> {
761            FerroProperty::register_direct::<Visual, _>("VisualParent", |o| o.visual_parent(), None, None)
762        }
763    );
764
765    ferro_property!(
766        /// Defines the `ZIndex` property.
767        pub fn z_index_property() -> StyledProperty<i32> {
768            FerroProperty::register::<Visual, _>("ZIndex", 0)
769        }
770    );
771} }
772
773impl Visual {
774    fn static_constructor() {
775        Self::affects_render::<Visual>(&[
776            Self::bounds_property().as_property(),
777            Self::clip_property().as_property(),
778            Self::clip_to_bounds_property().as_property(),
779            Self::is_visible_property().as_property(),
780            Self::opacity_property().as_property(),
781            Self::opacity_mask_property().as_property(),
782            Self::effect_property().as_property(),
783            Self::has_mirror_transform_property().as_property(),
784        ]);
785        Self::render_transform_property().changed().subscribe(Self::render_transform_changed);
786        Self::clip_to_bounds_property().changed().subscribe(|_| bump_geometry_version());
787        Self::render_transform_origin_property().changed().subscribe(|_| bump_geometry_version());
788        Self::z_index_property().changed().subscribe(|e| {
789            let Some(sender) = e.sender().downcast_ref::<Visual>() else { return };
790            let parent = sender.visual_parent();
791            if let Some(parent) = &parent {
792                if sender.z_index() != 0 {
793                    parent.has_non_uniform_z_index_children.set(true);
794                }
795            }
796            sender.invalidate_visual();
797            if let Some(parent) = &parent {
798                if let Some(source) = parent.presentation_source() {
799                    source.renderer().recalculate_children(parent);
800                }
801            }
802        });
803    }
804
805    /// Creates the class data; see [`crate::FerroObject::construct`].
806    pub fn construct() -> Self {
807        Self {
808            base: StyledElement::construct(),
809            bounds: Cell::new(Rect::default()),
810            presentation_source: RefCell::new(None),
811            visual_parent: RefCell::new(None),
812            has_mirror_transform: Cell::new(false),
813            has_render_transform: Cell::new(false),
814            has_manipulation_transform: Cell::new(false),
815            is_effectively_visible: Cell::new(true),
816            has_non_uniform_z_index_children: Cell::new(false),
817            visual_children: OnceCell::new(),
818            attached_to_visual_tree: HandlerList::new(),
819            detached_from_visual_tree: HandlerList::new(),
820            is_effectively_visible_changed: HandlerList::new(),
821            render_options: Cell::new(RenderOptions::default()),
822            text_options: Cell::new(TextOptions::default()),
823            composition_visual: RefCell::new(None),
824            child_composition_visual: RefCell::new(None),
825            composition_extras: RefCell::new(None),
826            render_transform_subscription: RefCell::new(None),
827            affects_render_subscriptions: RefCell::new(Vec::new()),
828        }
829    }
830
831    pub fn new() -> Ref<Self> {
832        instantiate(Self::construct())
833    }
834
835    // --- events -----------------------------------------------------------
836
837    /// Raised when the control is attached to a rooted visual tree.
838    pub fn attached_to_visual_tree(
839        &self,
840        handler: impl Fn(&VisualTreeAttachmentEventArgs) + 'static,
841    ) -> Rc<dyn IDisposable> {
842        let token = self.attached_to_visual_tree.add(Rc::new(handler));
843        let weak = self.to_ref().downgrade();
844        Disposable::create(move || {
845            if let Some(this) = weak.upgrade() {
846                this.attached_to_visual_tree.remove(token);
847            }
848        })
849    }
850
851    /// Raised when the control is detached from a rooted visual tree.
852    pub fn detached_from_visual_tree(
853        &self,
854        handler: impl Fn(&VisualTreeAttachmentEventArgs) + 'static,
855    ) -> Rc<dyn IDisposable> {
856        let token = self.detached_from_visual_tree.add(Rc::new(handler));
857        let weak = self.to_ref().downgrade();
858        Disposable::create(move || {
859            if let Some(this) = weak.upgrade() {
860                this.detached_from_visual_tree.remove(token);
861            }
862        })
863    }
864
865    /// Raised when `is_effectively_visible` changes.
866    pub fn is_effectively_visible_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
867        let token = self.is_effectively_visible_changed.add(Rc::new(handler));
868        let weak = self.to_ref().downgrade();
869        Disposable::create(move || {
870            if let Some(this) = weak.upgrade() {
871                this.is_effectively_visible_changed.remove(token);
872            }
873        })
874    }
875
876    // --- properties -------------------------------------------------------
877
878    /// The bounds of the control relative to its parent.
879    #[inline]
880    pub fn bounds(&self) -> Rect {
881        self.bounds.get()
882    }
883
884    /// Sets the bounds of the control. Called by the layout system.
885    pub fn set_bounds(&self, value: Rect) {
886        if self.bounds.get() != value {
887            bump_geometry_version();
888        }
889        self.set_and_raise_cell(Self::bounds_property(), &self.bounds, value);
890    }
891
892    /// Whether the control should be clipped to its bounds.
893    pub fn clip_to_bounds(&self) -> bool {
894        self.get_value(Self::clip_to_bounds_property())
895    }
896
897    pub fn set_clip_to_bounds(&self, value: bool) {
898        self.set_value(Self::clip_to_bounds_property(), value)
899    }
900
901    /// The geometry clip for this visual.
902    pub fn clip(&self) -> Option<Ref<Geometry>> {
903        self.get_value(Self::clip_property())
904    }
905
906    pub fn set_clip(&self, value: impl Into<Nullable<Geometry>>) {
907        self.set_value(Self::clip_property(), value.into().0)
908    }
909
910    /// The cache mode of the visual.
911    pub fn cache_mode(&self) -> Option<Ref<CacheMode>> {
912        self.get_value(Self::cache_mode_property())
913    }
914
915    pub fn set_cache_mode(&self, value: impl Into<Nullable<CacheMode>>) {
916        self.set_value(Self::cache_mode_property(), value.into().0)
917    }
918
919    /// The effect of the control.
920    pub fn effect(&self) -> Option<Rc<dyn IEffect>> {
921        self.get_value(Self::effect_property())
922    }
923
924    pub fn set_effect(&self, value: Option<Rc<dyn IEffect>>) {
925        self.set_value(Self::effect_property(), value)
926    }
927
928    /// The opacity mask of the control.
929    pub fn opacity_mask(&self) -> Option<Rc<dyn IBrush>> {
930        self.get_value(Self::opacity_mask_property())
931    }
932
933    pub fn set_opacity_mask(&self, value: Option<Rc<dyn IBrush>>) {
934        self.set_value(Self::opacity_mask_property(), value)
935    }
936
937    /// The render transform of the control.
938    pub fn render_transform(&self) -> Option<Rc<dyn ITransform>> {
939        self.get_value(Self::render_transform_property())
940    }
941
942    pub fn set_render_transform(&self, value: Option<Rc<dyn ITransform>>) {
943        self.set_value(Self::render_transform_property(), value)
944    }
945
946    /// The transform origin of the control.
947    pub fn render_transform_origin(&self) -> RelativePoint {
948        self.get_value(Self::render_transform_origin_property())
949    }
950
951    pub fn set_render_transform_origin(&self, value: RelativePoint) {
952        self.set_value(Self::render_transform_origin_property(), value)
953    }
954
955    /// Whether this control and all its parents are visible.
956    #[inline]
957    pub fn is_effectively_visible(&self) -> bool {
958        self.is_effectively_visible.get()
959    }
960
961    /// Whether this control is visible.
962    pub fn is_visible(&self) -> bool {
963        self.get_value(Self::is_visible_property())
964    }
965
966    pub fn set_is_visible(&self, value: bool) {
967        self.set_value(Self::is_visible_property(), value)
968    }
969
970    /// The opacity of the control.
971    pub fn opacity(&self) -> f64 {
972        self.get_value(Self::opacity_property())
973    }
974
975    pub fn set_opacity(&self, value: f64) {
976        self.set_value(Self::opacity_property(), value)
977    }
978
979    /// Whether `RenderTransform` has a value (not upstream: the transforms of the visual tree
980    /// skip the property read when it has none).
981    #[inline]
982    pub(crate) fn has_render_transform(&self) -> bool {
983        self.has_render_transform.get()
984    }
985
986    /// The manipulation transform of the visual (not upstream): a transform its parent applies
987    /// to it after its render transform and before its position, about its top left corner. The
988    /// scroll presenter of a zooming scroll viewer scales its content this way (WinUI applies the
989    /// zoom of DirectManipulation to the content of a scroll viewer); it takes part in rendering,
990    /// hit testing and the transforms between visuals as the render transform does. `None` by
991    /// default.
992    pub fn manipulation_transform(&self) -> Option<crate::Matrix> {
993        if !self.has_manipulation_transform.get() {
994            return None;
995        }
996        self.read_composition_extras(|extras| extras.manipulation_transform).flatten()
997    }
998
999    /// Sets [`manipulation_transform`](Self::manipulation_transform); the identity is no
1000    /// transform.
1001    pub fn set_manipulation_transform(&self, value: Option<crate::Matrix>) {
1002        let value = value.filter(|matrix| !matrix.is_identity());
1003        if self.manipulation_transform() == value {
1004            return;
1005        }
1006        self.has_manipulation_transform.set(value.is_some());
1007        self.with_composition_extras(|extras| extras.manipulation_transform = value);
1008        bump_geometry_version();
1009        self.invalidate_visual();
1010    }
1011
1012    /// Whether the control is mirrored relative to its parent because of its
1013    /// flow direction.
1014    #[inline]
1015    pub fn has_mirror_transform(&self) -> bool {
1016        self.has_mirror_transform.get()
1017    }
1018
1019    /// Sets the mirror transform flag.
1020    pub fn set_has_mirror_transform(&self, value: bool) {
1021        self.set_and_raise_cell(Self::has_mirror_transform_property(), &self.has_mirror_transform, value);
1022    }
1023
1024    /// The text flow direction.
1025    pub fn flow_direction(&self) -> FlowDirection {
1026        self.get_value(Self::flow_direction_property())
1027    }
1028
1029    pub fn set_flow_direction(&self, value: FlowDirection) {
1030        self.set_value(Self::flow_direction_property(), value)
1031    }
1032
1033    /// Gets the value of the attached `FlowDirection` property on a visual.
1034    pub fn get_flow_direction(visual: &Visual) -> FlowDirection {
1035        visual.get_value(Self::flow_direction_property())
1036    }
1037
1038    /// Sets the value of the attached `FlowDirection` property on a visual.
1039    pub fn set_flow_direction_on(visual: &Visual, value: FlowDirection) {
1040        visual.set_value(Self::flow_direction_property(), value)
1041    }
1042
1043    /// The Z index of the control.
1044    ///
1045    /// Controls with a higher Z index appear in front of controls with a
1046    /// lower one. Among controls with the same value, order of appearance in
1047    /// the visual children decides.
1048    pub fn z_index(&self) -> i32 {
1049        self.get_value(Self::z_index_property())
1050    }
1051
1052    pub fn set_z_index(&self, value: i32) {
1053        self.set_value(Self::z_index_property(), value)
1054    }
1055
1056    /// The control's child visuals.
1057    pub fn visual_children(&self) -> &FerroList<Ref<Visual>> {
1058        self.visual_children.get_or_init(|| {
1059            let list = FerroList::new();
1060            list.set_reset_behavior(ResetBehavior::Remove);
1061            let weak = self.to_ref().downgrade();
1062            let validator_owner = weak.clone();
1063            list.set_validate(Some(Rc::new(move |item: &Ref<Visual>| {
1064                if let Some(parent) = item.visual_parent() {
1065                    let owner = validator_owner.upgrade();
1066                    panic!(
1067                        "The control {:?} already has a visual parent {:?} while trying to add it as a child of {:?}.",
1068                        item, parent, owner
1069                    );
1070                }
1071            })));
1072            list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Visual>>| {
1073                if let Some(this) = weak.upgrade() {
1074                    this.visual_children_changed(e);
1075                }
1076            }));
1077            list
1078        })
1079    }
1080
1081    /// The number of visual children, without creating the collection.
1082    #[inline]
1083    pub fn visual_children_count(&self) -> usize {
1084        self.visual_children.get().map_or(0, FerroList::count)
1085    }
1086
1087    /// A snapshot of the visual children, without creating the collection.
1088    #[inline]
1089    pub fn visual_children_snapshot(&self) -> Option<Rc<Vec<Ref<Visual>>>> {
1090        self.visual_children.get().map(FerroList::snapshot)
1091    }
1092
1093    /// The presentation source the visual is attached to.
1094    #[inline]
1095    pub fn presentation_source(&self) -> Option<Rc<dyn IPresentationSource>> {
1096        self.presentation_source.borrow().clone()
1097    }
1098
1099    /// The root of the visual tree, if the control is attached to one.
1100    pub fn visual_root(&self) -> Option<Ref<Visual>> {
1101        self.presentation_source().and_then(|s| s.root_visual())
1102    }
1103
1104    /// The render options of the visual; see the accessors on
1105    /// [`RenderOptions`].
1106    #[inline]
1107    pub(crate) fn render_options(&self) -> RenderOptions {
1108        self.render_options.get()
1109    }
1110
1111    pub(crate) fn set_render_options(&self, value: RenderOptions) {
1112        self.render_options.set(value);
1113        self.invalidate_visual();
1114    }
1115
1116    /// The text options of the visual; see the accessors on
1117    /// [`TextOptions`].
1118    #[inline]
1119    pub(crate) fn text_options(&self) -> TextOptions {
1120        self.text_options.get()
1121    }
1122
1123    pub(crate) fn set_text_options(&self, value: TextOptions) {
1124        self.text_options.set(value);
1125        self.invalidate_visual();
1126    }
1127
1128    /// The composition visual of the visual, while it is attached to a
1129    /// visual tree rendered by a compositor.
1130    pub fn composition_visual(&self) -> Option<CompositionDrawListVisual> {
1131        self.composition_visual.borrow().clone()
1132    }
1133
1134    /// The composition visual shown as the last child of the composition
1135    /// visual of this visual (see `ElementComposition`).
1136    pub fn child_composition_visual(&self) -> Option<Rc<CompositionVisual>> {
1137        self.child_composition_visual.borrow().clone()
1138    }
1139
1140    pub fn set_child_composition_visual(&self, value: Option<Rc<CompositionVisual>>) {
1141        *self.child_composition_visual.borrow_mut() = value;
1142    }
1143
1144    fn with_composition_extras<R>(&self, f: impl FnOnce(&mut CompositionExtras) -> R) -> R {
1145        let mut extras = self.composition_extras.borrow_mut();
1146        f(extras.get_or_insert_with(Default::default))
1147    }
1148
1149    fn read_composition_extras<R>(&self, f: impl FnOnce(&CompositionExtras) -> R) -> Option<R> {
1150        self.composition_extras.borrow().as_deref().map(f)
1151    }
1152
1153    /// `UIElement.Lights`: the XAML lights the element holds. A light added
1154    /// to the collection is connected to the element (`OnConnected`) while
1155    /// the element is in a visual tree, and lights the targets of its id in
1156    /// the subtree of the element (winui-materials.md).
1157    pub fn lights(&self) -> FerroList<Ref<crate::media::XamlLight>> {
1158        if let Some(lights) = self.read_composition_extras(|extras| extras.lights.clone()).flatten() {
1159            return lights;
1160        }
1161        let lights: FerroList<Ref<crate::media::XamlLight>> = FerroList::new();
1162        let subscriptions = crate::media::xaml_light::track_lights(self, &lights);
1163        self.with_composition_extras(|extras| {
1164            extras.lights = Some(lights.clone());
1165            extras.lights_subscriptions = subscriptions;
1166        });
1167        lights
1168    }
1169
1170    /// Whether the element holds a lights collection.
1171    pub fn has_lights(&self) -> bool {
1172        self.read_composition_extras(|extras| extras.lights.as_ref().is_some_and(|lights| lights.count() > 0))
1173            .unwrap_or(false)
1174    }
1175
1176    /// The ids of the XAML lights that target the element.
1177    pub fn light_target_ids(&self) -> Vec<String> {
1178        self.read_composition_extras(|extras| extras.light_target_ids.clone()).unwrap_or_default()
1179    }
1180
1181    /// Adds `id` to the ids of the XAML lights that target the element;
1182    /// returns whether it was added.
1183    pub(crate) fn add_light_target_id(&self, id: &str) -> bool {
1184        let added = self.with_composition_extras(|extras| {
1185            if extras.light_target_ids.iter().any(|existing| existing == id) {
1186                return false;
1187            }
1188            extras.light_target_ids.push(id.to_owned());
1189            true
1190        });
1191        if added && self.read_composition_extras(|extras| extras.light_target_subscription.is_none()).unwrap_or(true) {
1192            let subscription = crate::media::xaml_light::track_light_target(self);
1193            self.with_composition_extras(|extras| extras.light_target_subscription = Some(subscription));
1194        }
1195        added
1196    }
1197
1198    /// Removes `id` from the ids of the XAML lights that target the element;
1199    /// returns whether it was there.
1200    pub(crate) fn remove_light_target_id(&self, id: &str) -> bool {
1201        self.read_composition_extras(|extras| extras.light_target_ids.iter().any(|existing| existing == id))
1202            .unwrap_or(false)
1203            && self.with_composition_extras(|extras| {
1204                extras.light_target_ids.retain(|existing| existing != id);
1205                true
1206            })
1207    }
1208
1209    /// `UIElement.Translation`: the translation of the composition visual of the visual, applied
1210    /// after its render transform. Zero until set.
1211    ///
1212    /// As in WinUI the translation is not a styled property; setting it hands the `Translation`
1213    /// of the composition visual to this facade, which writes it whenever the visual is
1214    /// rendered again. While it was never set, the application may animate the `Translation` of
1215    /// the composition visual itself.
1216    pub fn translation(&self) -> crate::Vector3D {
1217        self.composition_translation().unwrap_or_default()
1218    }
1219
1220    pub fn set_translation(&self, value: crate::Vector3D) {
1221        let old = self.translation();
1222        self.with_composition_extras(|extras| extras.translation = Some(value));
1223        self.invalidate_visual();
1224        if old != value {
1225            self.raise_direct_property_changed(Self::translation_property(), &old, &value);
1226        }
1227    }
1228
1229    fn composition_translation(&self) -> Option<crate::Vector3D> {
1230        self.read_composition_extras(|extras| extras.translation).flatten()
1231    }
1232
1233    /// Whether the composition visual of the visual is hidden by requests of the composition
1234    /// API (see [`add_composition_hide_request`](Self::add_composition_hide_request)).
1235    pub fn is_hidden_by_composition(&self) -> bool {
1236        self.read_composition_extras(|extras| extras.hide_requests > 0).unwrap_or(false)
1237    }
1238
1239    /// Hides the composition visual of the visual until the matching
1240    /// [`remove_composition_hide_request`](Self::remove_composition_hide_request), whatever
1241    /// `IsVisible` says. Requests are counted. A connected animation hides its source and
1242    /// destination this way while it shows them through visual surfaces, which render a hidden
1243    /// visual (WinUI hides the element's visual with `SetConnectedAnimationRunning`).
1244    pub fn add_composition_hide_request(&self) {
1245        self.with_composition_extras(|extras| extras.hide_requests += 1);
1246        self.invalidate_visual();
1247    }
1248
1249    /// Ends a request of [`add_composition_hide_request`](Self::add_composition_hide_request).
1250    pub fn remove_composition_hide_request(&self) {
1251        self.with_composition_extras(|extras| extras.hide_requests = extras.hide_requests.saturating_sub(1));
1252        self.invalidate_visual();
1253    }
1254
1255    /// Whether presentation owners have suppressed this visual and its descendants in all
1256    /// render paths. Suppression changes no styled property, layout or logical visibility.
1257    /// It also applies to immediate bitmap and visual-brush captures; composition-only
1258    /// hiding remains independently capturable for connected animations.
1259    pub fn is_render_suppressed(&self) -> bool {
1260        self.read_composition_extras(|extras| extras.render_suppression_requests > 0)
1261            .unwrap_or(false)
1262    }
1263
1264    /// Suppresses drawing until a matching remove request. Independent owners may hold
1265    /// requests concurrently. Input behavior is deliberately separate: editors that suppress
1266    /// presentation may additionally install their own temporary hit-test policy.
1267    pub fn add_render_suppression_request(&self) {
1268        let changed = self.with_composition_extras(|extras| {
1269            let previous = extras.render_suppression_requests;
1270            extras.render_suppression_requests = previous
1271                .checked_add(1)
1272                .expect("Render suppression request count exhausted");
1273            previous == 0
1274        });
1275        if changed {
1276            self.invalidate_visual();
1277        }
1278    }
1279
1280    /// Releases one request without changing another owner's suppression or authored values.
1281    /// Removing a request when none is active is harmless.
1282    pub fn remove_render_suppression_request(&self) {
1283        if !self.is_render_suppressed() {
1284            return;
1285        }
1286        let changed = self.with_composition_extras(|extras| {
1287            extras.render_suppression_requests -= 1;
1288            extras.render_suppression_requests == 0
1289        });
1290        if changed {
1291            self.invalidate_visual();
1292        }
1293    }
1294
1295    /// Whether the composition visual of the visual is kept visible by requests of the
1296    /// composition API although `IsVisible` is `false` (see
1297    /// [`add_composition_show_request`](Self::add_composition_show_request)).
1298    pub fn is_shown_by_composition(&self) -> bool {
1299        self.read_composition_extras(|extras| extras.show_requests > 0).unwrap_or(false)
1300    }
1301
1302    /// Keeps the composition visual of the visual visible until the matching
1303    /// [`remove_composition_show_request`](Self::remove_composition_show_request), whatever
1304    /// `IsVisible` says. Requests are counted. An implicit hide animation keeps the element
1305    /// visible this way until it completes. A hide request wins over a show request.
1306    pub fn add_composition_show_request(&self) {
1307        self.with_composition_extras(|extras| extras.show_requests += 1);
1308        self.invalidate_visual();
1309    }
1310
1311    /// Ends a request of [`add_composition_show_request`](Self::add_composition_show_request).
1312    pub fn remove_composition_show_request(&self) {
1313        self.with_composition_extras(|extras| extras.show_requests = extras.show_requests.saturating_sub(1));
1314        self.invalidate_visual();
1315    }
1316
1317    /// A composition visual shown above the children of the visual and above its child
1318    /// composition visual (the layer of the connected animations of a top level, WinUI's
1319    /// `ConnectedAnimationRoot`).
1320    pub fn composition_layer_visual(&self) -> Option<Rc<CompositionVisual>> {
1321        self.read_composition_extras(|extras| extras.layer_visual.clone()).flatten()
1322    }
1323
1324    /// Shows `visual` after the children of this visual until
1325    /// [`remove_composition_unloading_visual`](Self::remove_composition_unloading_visual): the
1326    /// image of a removed child that plays its implicit hide animation.
1327    pub(crate) fn add_composition_unloading_visual(&self, visual: Rc<CompositionVisual>) {
1328        self.with_composition_extras(|extras| extras.unloading_visuals.push(visual));
1329        if let Some(source) = self.presentation_source() {
1330            source.renderer().recalculate_children(self);
1331        }
1332    }
1333
1334    /// Ends [`add_composition_unloading_visual`](Self::add_composition_unloading_visual).
1335    pub(crate) fn remove_composition_unloading_visual(&self, visual: &Rc<CompositionVisual>) {
1336        self.with_composition_extras(|extras| extras.unloading_visuals.retain(|v| !Rc::ptr_eq(v, visual)));
1337        if let Some(own) = self.composition_visual() {
1338            own.children().remove(visual);
1339        }
1340        if let Some(source) = self.presentation_source() {
1341            source.renderer().recalculate_children(self);
1342        }
1343    }
1344
1345    /// Sets the visual of [`composition_layer_visual`](Self::composition_layer_visual).
1346    pub fn set_composition_layer_visual(&self, value: Option<Rc<CompositionVisual>>) {
1347        let old = self.with_composition_extras(|extras| std::mem::replace(&mut extras.layer_visual, value.clone()));
1348        if let (Some(old), Some(own)) = (&old, self.composition_visual()) {
1349            if !value.as_ref().is_some_and(|new| Rc::ptr_eq(new, old)) {
1350                own.children().remove(old);
1351            }
1352        }
1353        if let Some(source) = self.presentation_source() {
1354            source.renderer().recalculate_children(self);
1355        }
1356    }
1357
1358    /// Makes sure the visual has a composition visual on `compositor` and
1359    /// returns it.
1360    pub fn attach_to_compositor(&self, compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
1361        let current = self.composition_visual();
1362        match current {
1363            Some(current) if Rc::ptr_eq(current.compositor(), compositor) => current,
1364            _ => {
1365                let created = self.create_composition_visual(compositor);
1366                *self.composition_visual.borrow_mut() = Some(created.clone());
1367                self.composition_visual_created(&created);
1368                created
1369            }
1370        }
1371    }
1372
1373    /// Whether any child has a non-default Z index.
1374    pub fn has_non_uniform_z_index_children(&self) -> bool {
1375        self.has_non_uniform_z_index_children.get()
1376    }
1377
1378    /// Whether this control is attached to a visual root.
1379    #[inline]
1380    pub fn is_attached_to_visual_tree(&self) -> bool {
1381        self.presentation_source.borrow().is_some()
1382    }
1383
1384    /// The control's parent visual.
1385    #[inline]
1386    pub fn visual_parent(&self) -> Option<Ref<Visual>> {
1387        self.visual_parent.borrow().as_ref().and_then(WeakRef::upgrade)
1388    }
1389
1390    /// The number of visuals attached to a rooted visual tree (on this
1391    /// thread; visuals are UI-thread-affine).
1392    pub(crate) fn rooted_visual_children_count() -> i32 {
1393        ROOTED_VISUAL_CHILDREN_COUNT.with(Cell::get)
1394    }
1395
1396    // --- methods ----------------------------------------------------------
1397
1398    /// Invalidates the visual and queues a repaint.
1399    pub fn invalidate_visual(&self) {
1400        if let Some(source) = self.presentation_source() {
1401            source.renderer().add_dirty(self);
1402        }
1403    }
1404
1405    /// Indicates that a property change should cause `invalidate_visual` to
1406    /// be called on objects of class `T`.
1407    ///
1408    /// When the value of such a property is a media object that can change
1409    /// (a brush, pen, geometry, transform, effect or image), its
1410    /// invalidation invalidates the visual as well.
1411    pub fn affects_render<T: ObjectType + Upcast<Visual>>(properties: &[&'static FerroProperty]) {
1412        for property in properties {
1413            if can_value_affect_render(property.property_type()) {
1414                property.changed().subscribe(|e| {
1415                    if let Some(sender) = e.sender().downcast_ref::<T>() {
1416                        let visual: &Visual = sender.upcast();
1417                        visual.update_affects_render_subscription(e);
1418                        visual.invalidate_visual();
1419                    }
1420                });
1421            } else {
1422                property.changed().subscribe(|e| {
1423                    if let Some(sender) = e.sender().downcast_ref::<T>() {
1424                        let visual: &Visual = sender.upcast();
1425                        visual.invalidate_visual();
1426                    }
1427                });
1428            }
1429        }
1430    }
1431
1432    /// Moves the invalidation subscription of a render-affecting property
1433    /// from its old value to its new value.
1434    fn update_affects_render_subscription(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1435        let property = e.property();
1436        let old = {
1437            let mut subscriptions = self.affects_render_subscriptions.borrow_mut();
1438            let position = subscriptions.iter().position(|(p, _)| *p == property);
1439            position.map(|position| subscriptions.swap_remove(position))
1440        };
1441        if let Some((_, subscription)) = old {
1442            subscription.dispose();
1443        }
1444
1445        let weak = self.to_ref().downgrade();
1446        let subscription = subscribe_invalidated(
1447            e.new_value(),
1448            Rc::new(move || {
1449                if let Some(target) = weak.upgrade() {
1450                    target.invalidate_visual();
1451                }
1452            }),
1453        );
1454        if let Some(subscription) = subscription {
1455            self.affects_render_subscriptions.borrow_mut().push((property, subscription));
1456        }
1457    }
1458
1459    /// Called when a visual's render transform property changes: while the
1460    /// visual is attached, the subscription to the transform's `changed`
1461    /// notification moves from the old value to the new one.
1462    fn render_transform_changed(e: &FerroPropertyChangedEventArgs<'_>) {
1463        let Some(sender) = e.sender().downcast_ref::<Visual>() else { return };
1464        let (_, new_value) = e.get_old_and_new_value::<Option<Rc<dyn ITransform>>>();
1465        sender.has_render_transform.set(new_value.is_some());
1466        bump_geometry_version();
1467
1468        if sender.visual_root().is_some() {
1469
1470            // The old transform's subscription is the one held.
1471            let subscription = new_value.as_ref().and_then(|new_value| {
1472                let new_transform = new_value.as_any().downcast_ref::<Transform>()?;
1473                let weak = sender.to_ref().downgrade();
1474                Some(new_transform.changed(move || {
1475                    if let Some(visual) = weak.upgrade() {
1476                        visual.render_transform_value_changed();
1477                    }
1478                }))
1479            });
1480            sender.set_render_transform_subscription(subscription);
1481
1482            sender.invalidate_visual();
1483        }
1484    }
1485
1486    /// Called when the render transform's `changed` notification is raised.
1487    fn render_transform_value_changed(&self) {
1488        bump_geometry_version();
1489        self.invalidate_visual();
1490    }
1491
1492    fn set_render_transform_subscription(&self, value: Option<Rc<dyn IDisposable>>) {
1493        let previous = self.render_transform_subscription.replace(value);
1494        if let Some(previous) = previous {
1495            previous.dispose();
1496        }
1497    }
1498
1499    /// Attaches or detaches the root visual of a presentation source.
1500    pub fn set_presentation_source_for_root_visual(&self, presentation_source: Option<Rc<dyn IPresentationSource>>) {
1501        let current = self.presentation_source();
1502        let same = match (&current, &presentation_source) {
1503            (Some(a), Some(b)) => Rc::ptr_eq(a, b),
1504            (None, None) => true,
1505            _ => false,
1506        };
1507        if same {
1508            return;
1509        }
1510
1511        if let Some(current) = current {
1512            assert!(
1513                presentation_source.is_none(),
1514                "Visual is already attached to a presentation source. Only one presentation source can be \
1515                 attached to a visual tree."
1516            );
1517            self.on_detached_from_visual_tree_core(&VisualTreeAttachmentEventArgs::new(None, current));
1518        }
1519
1520        *self.presentation_source.borrow_mut() = presentation_source.clone();
1521
1522        if let Some(source) = presentation_source {
1523            let e = VisualTreeAttachmentEventArgs::new(None, source);
1524            self.on_attached_to_visual_tree_core(&e);
1525        }
1526    }
1527
1528    fn update_is_effectively_visible(&self, parent_state: bool) {
1529        let is_effectively_visible = parent_state && self.is_visible();
1530        if self.is_effectively_visible.get() == is_effectively_visible {
1531            return;
1532        }
1533        self.is_effectively_visible.set(is_effectively_visible);
1534        if !self.is_effectively_visible_changed.is_empty() {
1535            for (_, handler) in self.is_effectively_visible_changed.snapshot().iter() {
1536                handler();
1537            }
1538        }
1539        // PERF-SENSITIVE: this is called on the entire hierarchy.
1540        if let Some(children) = self.visual_children.get() {
1541            for child in children.snapshot().iter() {
1542                child.update_is_effectively_visible(is_effectively_visible);
1543            }
1544        }
1545    }
1546
1547    fn set_visual_parent(&self, value: Option<&Ref<Visual>>) {
1548        let old = self.visual_parent();
1549        if old.as_ref() == value {
1550            return;
1551        }
1552        *self.visual_parent.borrow_mut() = value.map(Ref::downgrade);
1553        bump_geometry_version();
1554
1555        if let (Some(source), Some(old)) = (self.presentation_source(), &old) {
1556            let e = VisualTreeAttachmentEventArgs::new(Some(old.clone()), source);
1557            self.on_detached_from_visual_tree_core(&e);
1558        }
1559
1560        if let Some(parent) = value {
1561            if let Some(source) = parent.presentation_source() {
1562                let e = VisualTreeAttachmentEventArgs::new(Some(parent.clone()), source);
1563                self.on_attached_to_visual_tree_core(&e);
1564            }
1565        }
1566
1567        self.on_visual_parent_changed(old.as_ref(), value);
1568    }
1569
1570    fn visual_children_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Visual>>) {
1571        match e.action {
1572            NotifyCollectionChangedAction::Add => {
1573                let this = self.to_ref();
1574                for visual in e.new_items {
1575                    visual.set_visual_parent(Some(&this));
1576                }
1577            }
1578            NotifyCollectionChangedAction::Remove => {
1579                for visual in e.old_items {
1580                    visual.set_visual_parent(None);
1581                }
1582            }
1583            NotifyCollectionChangedAction::Replace => {
1584                for visual in e.old_items {
1585                    visual.set_visual_parent(None);
1586                }
1587                let this = self.to_ref();
1588                for visual in e.new_items {
1589                    visual.set_visual_parent(Some(&this));
1590                }
1591            }
1592            NotifyCollectionChangedAction::Move | NotifyCollectionChangedAction::Reset => {}
1593        }
1594    }
1595}