cranpose_render_common/
hit_graph.rs1use cranpose_core::NodeId;
2use cranpose_ui_graphics::Rect;
3use smallvec::SmallVec;
4
5use crate::{
6 graph::{HitTestNode, LayerNode, ProjectiveTransform, RenderNode, quad_bounds},
7 graph_scene::{HitClip, HitGeometry, HitTargetSpec, Scene},
8 primitive_emit::resolve_clip,
9};
10
11pub trait HitGraphSink {
12 fn push_hit(
13 &mut self,
14 node_id: NodeId,
15 capture_path: &[NodeId],
16 geometry: HitGeometry<'_>,
17 hit: &HitTestNode,
18 );
19}
20
21impl HitGraphSink for Scene {
22 fn push_hit(
23 &mut self,
24 node_id: NodeId,
25 capture_path: &[NodeId],
26 geometry: HitGeometry<'_>,
27 hit: &HitTestNode,
28 ) {
29 Scene::push_hit(
30 self,
31 node_id,
32 capture_path,
33 geometry,
34 HitTargetSpec {
35 shape: hit.shape,
36 handlers: &hit.handlers,
37 },
38 );
39 }
40}
41
42pub fn collect_hits_from_graph<S: HitGraphSink>(
43 layer: &LayerNode,
44 parent_transform: ProjectiveTransform,
45 sink: &mut S,
46 parent_hit_clip: Option<Rect>,
47) {
48 if !layer.has_hit_targets {
49 return;
50 }
51 let mut hit_clips = Vec::new();
52 let mut pointer_input_ancestors = Vec::new();
53 let mut capture_path = SmallVec::<[NodeId; 8]>::new();
54 collect_hits_from_graph_inner(
55 layer,
56 parent_transform,
57 sink,
58 parent_hit_clip,
59 &mut hit_clips,
60 &mut pointer_input_ancestors,
61 &mut capture_path,
62 );
63}
64
65fn collect_hits_from_graph_inner<S: HitGraphSink>(
66 layer: &LayerNode,
67 parent_transform: ProjectiveTransform,
68 sink: &mut S,
69 parent_hit_clip: Option<Rect>,
70 hit_clips: &mut Vec<HitClip>,
71 pointer_input_ancestors: &mut Vec<NodeId>,
72 capture_path: &mut SmallVec<[NodeId; 8]>,
73) {
74 if !layer.has_hit_targets {
75 return;
76 }
77 let transform = layer.transform_to_parent.then(parent_transform);
78 let node_rect = layer.node_rect();
81 let transformed_quad = transform.map_rect(node_rect);
82 let transformed_rect = quad_bounds(transformed_quad);
83
84 if transformed_rect.width <= 0.0 || transformed_rect.height <= 0.0 {
85 return;
86 }
87
88 let Some(world_to_local) = transform.inverse() else {
89 return;
90 };
91
92 let mut hit_clip_bounds = parent_hit_clip;
93 let mut pushed_clip = false;
94 if let Some(local_clip) = layer.clip_rect() {
95 let clip_quad = transform.map_rect(local_clip);
96 let clip_bounds = quad_bounds(clip_quad);
97 let resolved_clip_bounds = resolve_clip(parent_hit_clip, Some(clip_bounds));
98 if resolved_clip_bounds.is_some_and(|clip| clip.is_empty()) {
99 return;
100 }
101 hit_clip_bounds = resolved_clip_bounds;
102 hit_clips.push(HitClip {
103 quad: clip_quad,
104 bounds: clip_bounds,
105 });
106 pushed_clip = true;
107 }
108
109 if let (Some(node_id), Some(hit)) = (layer.node_id, &layer.hit_test) {
110 capture_path.clear();
111 capture_path.push(node_id);
112 capture_path.extend(pointer_input_ancestors.iter().rev().copied());
113 sink.push_hit(
114 node_id,
115 capture_path,
116 HitGeometry {
117 rect: transformed_rect,
118 quad: transformed_quad,
119 local_bounds: node_rect,
120 world_to_local,
121 hit_clip_bounds,
122 hit_clips,
123 },
124 hit,
125 );
126 }
127
128 let pointer_input_ancestor = layer
129 .hit_test
130 .as_ref()
131 .filter(|hit| !hit.handlers.pointer_inputs().is_empty())
132 .and(layer.node_id);
133 if let Some(node_id) = pointer_input_ancestor {
134 pointer_input_ancestors.push(node_id);
135 }
136
137 for child in &layer.children {
138 if let RenderNode::Layer(child_layer) = child {
139 collect_hits_from_graph_inner(
140 child_layer,
141 transform,
142 sink,
143 hit_clip_bounds,
144 hit_clips,
145 pointer_input_ancestors,
146 capture_path,
147 );
148 }
149 }
150
151 if pointer_input_ancestor.is_some() {
152 let _ = pointer_input_ancestors.pop();
153 }
154
155 if pushed_clip {
156 let _ = hit_clips.pop();
157 }
158}
159
160#[cfg(test)]
161#[path = "tests/hit_graph_tests.rs"]
162mod tests;