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 transformed_quad = transform.map_rect(layer.local_bounds);
79 let transformed_rect = quad_bounds(transformed_quad);
80
81 if transformed_rect.width <= 0.0 || transformed_rect.height <= 0.0 {
82 return;
83 }
84
85 let Some(world_to_local) = transform.inverse() else {
86 return;
87 };
88
89 let mut hit_clip_bounds = parent_hit_clip;
90 let mut pushed_clip = false;
91 if let Some(local_clip) = layer.clip_rect() {
92 let clip_quad = transform.map_rect(local_clip);
93 let clip_bounds = quad_bounds(clip_quad);
94 let resolved_clip_bounds = resolve_clip(parent_hit_clip, Some(clip_bounds));
95 if resolved_clip_bounds.is_some_and(|clip| clip.is_empty()) {
96 return;
97 }
98 hit_clip_bounds = resolved_clip_bounds;
99 hit_clips.push(HitClip {
100 quad: clip_quad,
101 bounds: clip_bounds,
102 });
103 pushed_clip = true;
104 }
105
106 if let (Some(node_id), Some(hit)) = (layer.node_id, &layer.hit_test) {
107 capture_path.clear();
108 capture_path.push(node_id);
109 capture_path.extend(pointer_input_ancestors.iter().rev().copied());
110 sink.push_hit(
111 node_id,
112 capture_path,
113 HitGeometry {
114 rect: transformed_rect,
115 quad: transformed_quad,
116 local_bounds: layer.local_bounds,
117 world_to_local,
118 hit_clip_bounds,
119 hit_clips,
120 },
121 hit,
122 );
123 }
124
125 let pointer_input_ancestor = layer
126 .hit_test
127 .as_ref()
128 .filter(|hit| !hit.handlers.pointer_inputs().is_empty())
129 .and(layer.node_id);
130 if let Some(node_id) = pointer_input_ancestor {
131 pointer_input_ancestors.push(node_id);
132 }
133
134 for child in &layer.children {
135 if let RenderNode::Layer(child_layer) = child {
136 collect_hits_from_graph_inner(
137 child_layer,
138 transform,
139 sink,
140 hit_clip_bounds,
141 hit_clips,
142 pointer_input_ancestors,
143 capture_path,
144 );
145 }
146 }
147
148 if pointer_input_ancestor.is_some() {
149 let _ = pointer_input_ancestors.pop();
150 }
151
152 if pushed_clip {
153 let _ = hit_clips.pop();
154 }
155}
156
157#[cfg(test)]
158#[path = "tests/hit_graph_tests.rs"]
159mod tests;