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        if !visual.visible() {
267            return None;
268        }
269
270        if let Some(filter) = filter {
271            if !filter(visual) {
272                return None;
273            }
274        }
275
276        let readback = visual.try_get_valid_readback()?;
277
278        if !visual.disable_sub_tree_bounds_hit_test_optimization() {
279            let transformed_subtree_bounds = readback.transformed_subtree_bounds?;
280            if !H::transformed_sub_tree_bounds_match(transformed_subtree_bounds, parent_input) {
281                return None;
282            }
283        }
284
285        let inv_matrix = readback.matrix.try_invert()?;
286
287        let input = H::transform(parent_input, inv_matrix);
288
289        if visual.clip_to_bounds() && !H::clipped_bounds_match(visual, &input) {
290            return None;
291        }
292
293        if let Some(clip) = visual.clip() {
294            if !H::clip_matches(&*clip, &input) {
295                return None;
296            }
297        }
298
299        Some(input)
300    }
301
302    /// Finds the topmost visual hit by `input` that passes `result_filter`
303    /// and returns it with how it is hit.
304    pub fn try_hit_test_first<H: ICompositionHitTester>(
305        &self,
306        input: &H::Input,
307        root: Option<&Rc<CompositionVisual>>,
308        filter: VisualFilter<'_>,
309        result_filter: VisualFilter<'_>,
310    ) -> (Option<Rc<CompositionVisual>>, IntersectionResult) {
311        self.compositor().readback().next_read();
312        let none = (None, IntersectionResult::NotCalculated);
313        let root = match root {
314            Some(root) => root.clone(),
315            None => match self.root() {
316                Some(root) => root,
317                None => return none,
318            },
319        };
320
321        // Need to convert transform the point using visual's readback since HitTestCore will use its inverse matrix
322        // NOTE: it can technically break hit-testing of the root visual itself if it has a non-identity transform,
323        // need to investigate that possibility later. We might want a separate mode for root hit-testing.
324        let Some(readback) = root.try_get_valid_readback() else { return none };
325        let parent_input = H::transform(input, readback.matrix);
326
327        self.hit_test_first_core::<H>(&root, &parent_input, filter, result_filter)
328    }
329
330    pub(crate) fn hit_test_first_core<H: ICompositionHitTester>(
331        &self,
332        visual: &Rc<CompositionVisual>,
333        parent_input: &H::Input,
334        filter: VisualFilter<'_>,
335        result_filter: VisualFilter<'_>,
336    ) -> (Option<Rc<CompositionVisual>>, IntersectionResult) {
337        let Some(input) = Self::hit_test_visual::<H>(visual, parent_input, filter) else {
338            return (None, IntersectionResult::NotCalculated);
339        };
340
341        let mut queried_indexed_children = false;
342        if visual.children().count() >= HIT_TEST_AABB_TREE_THRESHOLD {
343            if let Some((hit, intersection_result)) =
344                visual.try_query_first_hit_test_child::<H>(self, &input, filter, result_filter)
345            {
346                queried_indexed_children = true;
347                if hit.is_some() {
348                    return (hit, intersection_result);
349                }
350            }
351        }
352
353        if !queried_indexed_children {
354            // By index, from the last child, as the original: no copy of the
355            // children of every visual the hit test enters.
356            let children = visual.children();
357            let mut index = children.count();
358            while index > 0 {
359                index -= 1;
360                let child = children.get(index);
361                let (hit, intersection_result) = self.hit_test_first_core::<H>(&child, &input, filter, result_filter);
362                if hit.is_some() {
363                    return (hit, intersection_result);
364                }
365            }
366        }
367
368        let intersection_result = H::hit_test(visual, &input);
369        let hit = is_hit(intersection_result) && result_filter.is_none_or(|result_filter| result_filter(visual));
370        (hit.then(|| visual.clone()), intersection_result)
371    }
372
373    pub fn request_redraw(&self) {
374        self.register_for_serialization();
375    }
376}
377
378impl CompositionTargetHooks for CompositionTarget {
379    fn on_root_changed(&self) {
380        if let Some(root) = self.root() {
381            root.set_root(self.this.upgrade());
382        }
383    }
384
385    fn on_root_changing(&self) {
386        if let Some(root) = self.root() {
387            root.set_root(None);
388        }
389    }
390}
391
392impl IRegisterForSerialization for CompositionTarget {
393    fn register_for_serialization(&self) {
394        self.object
395            .register_for_serialization(|| self.this.upgrade().map(|this| this as Rc<dyn ICompositorSerializable>));
396    }
397}
398
399impl ICompositionObjectHost for CompositionTarget {
400    fn server(&self) -> ServerObjectId {
401        self.object.required_server()
402    }
403
404    fn pending_animations(&self) -> &PendingAnimations {
405        self.object.pending_animations()
406    }
407
408    fn implicit_animation(&self, property_name: &str) -> Option<Rc<dyn ICompositionAnimationBase>> {
409        self.object.implicit_animation(property_name)
410    }
411
412    fn start_animation_group(
413        &self,
414        grp: &Rc<dyn ICompositionAnimationBase>,
415        target: &str,
416        final_value: ExpressionVariant,
417    ) -> bool {
418        self.start_animation_group_for(&**grp, target, final_value)
419    }
420}
421
422/// The class has no animated property: upstream does not override
423/// `StartAnimation`, whose base implementation rejects every property.
424impl ICompositionObjectAnimations for CompositionTarget {
425    fn try_start_animation(
426        &self,
427        _property_name: &str,
428        _animation: &dyn ICompositionAnimation,
429        _final_value: Option<ExpressionVariant>,
430    ) -> bool {
431        false
432    }
433
434    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
435        ServerCompositionTargetProps::get_composition_property(property_name)
436    }
437
438    fn composition_object(&self) -> &CompositionObject {
439        &self.object
440    }
441}
442
443impl AsCompositionObject for CompositionTarget {
444    fn as_composition_object(&self) -> &CompositionObject {
445        &self.object
446    }
447
448    fn composition_type_name(&self) -> &'static str {
449        "CompositionTarget"
450    }
451}
452
453impl ICompositionObject for CompositionTarget {
454    fn server(&self) -> ServerObjectId {
455        self.object.required_server()
456    }
457
458    fn as_any(&self) -> &dyn Any {
459        self
460    }
461
462    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
463        self
464    }
465}
466
467impl ICompositorSerializable for CompositionTarget {
468    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
469        self.object.try_get_server(c)
470    }
471
472    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
473        self.object.begin_serialize_changes(c);
474        self.props.serialize_changes_core(self, writer);
475    }
476}