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    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;