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