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() || !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 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 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 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
424impl 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}