ferroui_base/rendering/composition/
composition_target.rs1use 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
25pub 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 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 pub fn server(&self) -> ServerObjectId {
63 self.object.required_server()
64 }
65
66 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 pub(crate) fn mark_disposed_out_of_band(&self) {
82 self.object.mark_disposed();
83 }
84
85 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 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 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 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 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 self.hit_test_children::<H>(visual, &input, result, filter);
211
212 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 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 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 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 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 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
422impl 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}