Skip to main content

cranpose_render_common/
hit_graph.rs

1use 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    // The node's handlers sit on its own rect; its clip, at its layer's
79    // bounds, cuts them as it cuts what the layer holds.
80    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;