Skip to main content

ferroui_base/rendering/composition/
composition_target.rs

1use super::animations::{ICompositionAnimation, ICompositionAnimationBase};
2use super::container_visual::HIT_TEST_AABB_TREE_THRESHOLD;
3use super::expressions::ExpressionVariant;
4use super::generated::{CompositionTargetHooks, CompositionTargetProps, ServerCompositionTargetProps};
5use super::hit_testing::{is_hit, ICompositionHitTester};
6use super::server::{CompositionProperty, RenderSurfaces, ServerCompositionTarget, ServerObjectId};
7use super::transport::{BatchStreamWriter, IRegisterForSerialization};
8use super::{
9    AsCompositionObject, CompositionObject, CompositionTransparencyLevel, ICompositionObjectAnimations, CompositionVisual, Compositor, ICompositionObject,
10    ICompositionObjectHost, ICompositionTargetDebugEvents, ICompositorSerializable, PendingAnimations,
11};
12use crate::media::IntersectionResult;
13use crate::rendering::{LayoutPassTiming, RendererDebugOverlays};
14use crate::Size;
15use std::any::Any;
16use std::cell::RefCell;
17use std::sync::Arc;
18use std::rc::{Rc, Weak};
19use std::sync::atomic::{AtomicI64, Ordering};
20
21static NEXT_ID: AtomicI64 = AtomicI64::new(1);
22
23type VisualFilter<'a> = Option<&'a dyn Fn(&Rc<CompositionVisual>) -> bool>;
24
25/// The root of a composition visual tree: what a renderer draws to a
26/// render target.
27pub struct CompositionTarget {
28    this: Weak<CompositionTarget>,
29    object: CompositionObject,
30    props: CompositionTargetProps,
31    id: i64,
32    hit_test_child_candidates: RefCell<Vec<Rc<CompositionVisual>>>,
33}
34
35impl CompositionTarget {
36    pub(crate) fn new(compositor: &Rc<Compositor>, surfaces: RenderSurfaces) -> Rc<CompositionTarget> {
37        let id = NEXT_ID.fetch_add(1, Ordering::SeqCst) + 1;
38        // The render interface may have been registered after the compositor
39        // was created. It is looked up here, on the thread that has the
40        // services, before a frame of the render thread needs it.
41        compositor.with_server(|server| {
42            server.render_interface().capture_platform_render_interface();
43        });
44        let server =
45            compositor.create_server_object(move |compositor, _| ServerCompositionTarget::new(compositor, surfaces, id));
46        let target = Rc::new_cyclic(|this: &Weak<CompositionTarget>| CompositionTarget {
47            this: this.clone(),
48            object: CompositionObject::new(compositor, Some(server)),
49            props: CompositionTargetProps::new(),
50            id,
51            hit_test_child_candidates: RefCell::new(Vec::new()),
52        });
53        target.props.initialize_defaults(&*target);
54        target
55    }
56
57    pub fn compositor(&self) -> &Rc<Compositor> {
58        self.object.compositor()
59    }
60
61    /// The id of the server-side target.
62    pub fn server(&self) -> ServerObjectId {
63        self.object.required_server()
64    }
65
66    /// The identity of the target, shared with its server side.
67    pub fn id(&self) -> i64 {
68        self.id
69    }
70
71    pub fn is_disposed(&self) -> bool {
72        self.object.is_disposed()
73    }
74
75    pub fn dispose(&self) {
76        self.object.dispose();
77    }
78
79    /// Marks the target disposed after its server side has been disposed
80    /// out of band.
81    pub(crate) fn mark_disposed_out_of_band(&self) {
82        self.object.mark_disposed();
83    }
84
85    // --- generated properties -----------------------------------------------------
86
87    pub fn root(&self) -> Option<Rc<CompositionVisual>> {
88        self.props.root().and_then(|o| o.into_any_rc().downcast::<CompositionVisual>().ok())
89    }
90
91    pub fn set_root(&self, value: Option<Rc<CompositionVisual>>) {
92        self.props.set_root(self, value.map(|v| v as Rc<dyn ICompositionObject>));
93    }
94
95    pub fn is_enabled(&self) -> bool {
96        self.props.is_enabled()
97    }
98
99    pub fn set_is_enabled(&self, value: bool) {
100        self.props.set_is_enabled(self, value);
101    }
102
103    pub fn debug_overlays(&self) -> RendererDebugOverlays {
104        self.props.debug_overlays()
105    }
106
107    pub fn set_debug_overlays(&self, value: RendererDebugOverlays) {
108        if !value.is_empty() {
109            // The text of the overlays is made from the default typeface,
110            // on this thread; the frames that draw it may run on another.
111            self.compositor().with_server(|server| {
112                server.diagnostic_text_renderer();
113            });
114        }
115        self.props.set_debug_overlays(self, value);
116    }
117
118    pub fn last_layout_pass_timing(&self) -> LayoutPassTiming {
119        self.props.last_layout_pass_timing()
120    }
121
122    pub fn set_last_layout_pass_timing(&self, value: LayoutPassTiming) {
123        self.props.set_last_layout_pass_timing(self, value);
124    }
125
126    pub fn scaling(&self) -> f64 {
127        self.props.scaling()
128    }
129
130    pub fn set_scaling(&self, value: f64) {
131        self.props.set_scaling(self, value);
132    }
133
134    pub fn size(&self) -> Size {
135        self.props.size()
136    }
137
138    pub fn set_size(&self, value: Size) {
139        self.props.set_size(self, value);
140    }
141
142    pub fn transparency_level(&self) -> CompositionTransparencyLevel {
143        self.props.transparency_level()
144    }
145
146    pub fn set_transparency_level(&self, value: CompositionTransparencyLevel) {
147        self.props.set_transparency_level(self, value);
148    }
149
150    pub fn platform_specific_scene_info(&self) -> Option<std::sync::Arc<dyn Any + Send + Sync>> {
151        self.props.platform_specific_scene_info()
152    }
153
154    pub fn set_platform_specific_scene_info(&self, value: Option<std::sync::Arc<dyn Any + Send + Sync>>) {
155        self.props.set_platform_specific_scene_info(self, value);
156    }
157
158    /// Sets the receiver of the debug events of the server-side target.
159    /// It is handed over with the next batch.
160    pub fn set_debug_events(&self, events: Option<Arc<dyn ICompositionTargetDebugEvents>>) {
161        let server = self.server();
162        self.compositor().post_server_job(
163            move |compositor| {
164                if let Some(target) = compositor.get::<ServerCompositionTarget>(server) {
165                    target.set_debug_events(events);
166                }
167            },
168            false,
169        );
170    }
171
172    // --- hit testing --------------------------------------------------------------
173
174    /// Collects the visuals hit by `input` (in the coordinates of `root`,
175    /// or of the root of the target), topmost first, with how they are
176    /// hit. `None` when the tree has not been rendered yet.
177    pub fn try_hit_test<H: ICompositionHitTester>(
178        &self,
179        input: &H::Input,
180        root: Option<&Rc<CompositionVisual>>,
181        filter: VisualFilter<'_>,
182    ) -> Option<Vec<(IntersectionResult, Rc<CompositionVisual>)>> {
183        self.compositor().readback().next_read();
184        let root = match root {
185            Some(root) => root.clone(),
186            None => self.root()?,
187        };
188
189        // Need to convert transform the point using visual's readback since HitTestCore will use its inverse matrix
190        // NOTE: it can technically break hit-testing of the root visual itself if it has a non-identity transform,
191        // need to investigate that possibility later. We might want a separate mode for root hit-testing.
192        let readback = root.try_get_valid_readback()?;
193        let parent_input = H::transform(input, readback.matrix);
194
195        let mut res = Vec::new();
196        self.hit_test_core::<H>(&root, &parent_input, &mut res, filter);
197        Some(res)
198    }
199
200    fn hit_test_core<H: ICompositionHitTester>(
201        &self,
202        visual: &Rc<CompositionVisual>,
203        parent_input: &H::Input,
204        result: &mut Vec<(IntersectionResult, Rc<CompositionVisual>)>,
205        filter: VisualFilter<'_>,
206    ) {
207        let Some(input) = Self::hit_test_visual::<H>(visual, parent_input, filter) else { return };
208
209        // Inspect children
210        self.hit_test_children::<H>(visual, &input, result, filter);
211
212        // Hit-test the current node
213        let intersection_result = H::hit_test(visual, &input);
214        if is_hit(intersection_result) {
215            result.push((intersection_result, visual.clone()));
216        }
217    }
218
219    fn hit_test_children<H: ICompositionHitTester>(
220        &self,
221        visual: &Rc<CompositionVisual>,
222        input: &H::Input,
223        result: &mut Vec<(IntersectionResult, Rc<CompositionVisual>)>,
224        filter: VisualFilter<'_>,
225    ) {
226        if visual.children().count() >= HIT_TEST_AABB_TREE_THRESHOLD {
227            // The candidate list of the target is reused unless a query is
228            // already running (a nested container).
229            let mut candidates = match self.hit_test_child_candidates.try_borrow_mut() {
230                Ok(mut pooled) => std::mem::take(&mut *pooled),
231                Err(_) => Vec::new(),
232            };
233            candidates.clear();
234            let queried = visual.try_query_hit_test_children::<H>(input, &mut candidates);
235            if queried {
236                for child in &candidates {
237                    self.hit_test_core::<H>(child, input, result, filter);
238                }
239            }
240            candidates.clear();
241            if let Ok(mut pooled) = self.hit_test_child_candidates.try_borrow_mut() {
242                if pooled.capacity() < candidates.capacity() {
243                    *pooled = candidates;
244                }
245            }
246            if queried {
247                return;
248            }
249        }
250
251        // By index, from the last child, as the original: no copy of the
252        // children of every visual the hit test enters.
253        let children = visual.children();
254        let mut index = children.count();
255        while index > 0 {
256            index -= 1;
257            self.hit_test_core::<H>(&children.get(index), input, result, filter);
258        }
259    }
260
261    fn hit_test_visual<H: ICompositionHitTester>(
262        visual: &Rc<CompositionVisual>,
263        parent_input: &H::Input,
264        filter: VisualFilter<'_>,
265    ) -> Option<H::Input> {
266        // A visual that is not hit test visible (WinUI `IsHitTestVisible`)
267        // is skipped with its subtree.
268        if !visual.visible() || !visual.is_hit_test_visible() {
269            return None;
270        }
271
272        if let Some(filter) = filter {
273            if !filter(visual) {
274                return None;
275            }
276        }
277
278        let readback = visual.try_get_valid_readback()?;
279
280        if !visual.disable_sub_tree_bounds_hit_test_optimization() {
281            let transformed_subtree_bounds = readback.transformed_subtree_bounds?;
282            if !H::transformed_sub_tree_bounds_match(transformed_subtree_bounds, parent_input) {
283                return None;
284            }
285        }
286
287        let inv_matrix = readback.matrix.try_invert()?;
288
289        let input = H::transform(parent_input, inv_matrix);
290
291        if visual.clip_to_bounds() && !H::clipped_bounds_match(visual, &input) {
292            return None;
293        }
294
295        if let Some(clip) = visual.clip() {
296            if !H::clip_matches(&*clip, &input) {
297                return None;
298            }
299        }
300
301        Some(input)
302    }
303
304    /// Finds the topmost visual hit by `input` that passes `result_filter`
305    /// and returns it with how it is hit.
306    pub fn try_hit_test_first<H: ICompositionHitTester>(
307        &self,
308        input: &H::Input,
309        root: Option<&Rc<CompositionVisual>>,
310        filter: VisualFilter<'_>,
311        result_filter: VisualFilter<'_>,
312    ) -> (Option<Rc<CompositionVisual>>, IntersectionResult) {
313        self.compositor().readback().next_read();
314        let none = (None, IntersectionResult::NotCalculated);
315        let root = match root {
316            Some(root) => root.clone(),
317            None => match self.root() {
318                Some(root) => root,
319                None => return none,
320            },
321        };
322
323        // Need to convert transform the point using visual's readback since HitTestCore will use its inverse matrix
324        // NOTE: it can technically break hit-testing of the root visual itself if it has a non-identity transform,
325        // need to investigate that possibility later. We might want a separate mode for root hit-testing.
326        let Some(readback) = root.try_get_valid_readback() else { return none };
327        let parent_input = H::transform(input, readback.matrix);
328
329        self.hit_test_first_core::<H>(&root, &parent_input, filter, result_filter)
330    }
331
332    pub(crate) fn hit_test_first_core<H: ICompositionHitTester>(
333        &self,
334        visual: &Rc<CompositionVisual>,
335        parent_input: &H::Input,
336        filter: VisualFilter<'_>,
337        result_filter: VisualFilter<'_>,
338    ) -> (Option<Rc<CompositionVisual>>, IntersectionResult) {
339        let Some(input) = Self::hit_test_visual::<H>(visual, parent_input, filter) else {
340            return (None, IntersectionResult::NotCalculated);
341        };
342
343        let mut queried_indexed_children = false;
344        if visual.children().count() >= HIT_TEST_AABB_TREE_THRESHOLD {
345            if let Some((hit, intersection_result)) =
346                visual.try_query_first_hit_test_child::<H>(self, &input, filter, result_filter)
347            {
348                queried_indexed_children = true;
349                if hit.is_some() {
350                    return (hit, intersection_result);
351                }
352            }
353        }
354
355        if !queried_indexed_children {
356            // By index, from the last child, as the original: no copy of the
357            // children of every visual the hit test enters.
358            let children = visual.children();
359            let mut index = children.count();
360            while index > 0 {
361                index -= 1;
362                let child = children.get(index);
363                let (hit, intersection_result) = self.hit_test_first_core::<H>(&child, &input, filter, result_filter);
364                if hit.is_some() {
365                    return (hit, intersection_result);
366                }
367            }
368        }
369
370        let intersection_result = H::hit_test(visual, &input);
371        let hit = is_hit(intersection_result) && result_filter.is_none_or(|result_filter| result_filter(visual));
372        (hit.then(|| visual.clone()), intersection_result)
373    }
374
375    pub fn request_redraw(&self) {
376        self.register_for_serialization();
377    }
378}
379
380impl CompositionTargetHooks for CompositionTarget {
381    fn on_root_changed(&self) {
382        if let Some(root) = self.root() {
383            root.set_root(self.this.upgrade());
384        }
385    }
386
387    fn on_root_changing(&self) {
388        if let Some(root) = self.root() {
389            root.set_root(None);
390        }
391    }
392}
393
394impl IRegisterForSerialization for CompositionTarget {
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 CompositionTarget {
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
424/// The class has no animated property: upstream does not override
425/// `StartAnimation`, whose base implementation rejects every property.
426impl ICompositionObjectAnimations for CompositionTarget {
427    fn try_start_animation(
428        &self,
429        _property_name: &str,
430        _animation: &dyn ICompositionAnimation,
431        _final_value: Option<ExpressionVariant>,
432    ) -> bool {
433        false
434    }
435
436    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
437        ServerCompositionTargetProps::get_composition_property(property_name)
438    }
439
440    fn composition_object(&self) -> &CompositionObject {
441        &self.object
442    }
443}
444
445impl AsCompositionObject for CompositionTarget {
446    fn as_composition_object(&self) -> &CompositionObject {
447        &self.object
448    }
449
450    fn composition_type_name(&self) -> &'static str {
451        "CompositionTarget"
452    }
453}
454
455impl ICompositionObject for CompositionTarget {
456    fn server(&self) -> ServerObjectId {
457        self.object.required_server()
458    }
459
460    fn as_any(&self) -> &dyn Any {
461        self
462    }
463
464    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
465        self
466    }
467}
468
469impl ICompositorSerializable for CompositionTarget {
470    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
471        self.object.try_get_server(c)
472    }
473
474    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
475        self.object.begin_serialize_changes(c);
476        self.props.serialize_changes_core(self, writer);
477    }
478}