1use super::IHitTester;
2use crate::media::{Geometry, GeometryHitTestResult, IntersectionResult, MatrixTransform, RectangleGeometry};
3use crate::visual_tree::local_transform;
4use crate::{Matrix, Point, Rect, Ref, Visual};
5
6#[derive(Clone, Copy, Debug, Default)]
25pub struct ManagedHitTester;
26
27impl ManagedHitTester {
28 pub const fn new() -> Self {
30 Self
31 }
32
33 fn hit_test_core(
36 visual: &Visual,
37 p: Point,
38 filter: Option<&dyn Fn(&Visual) -> bool>,
39 visit: &mut dyn FnMut(&Visual) -> bool,
40 ) -> bool {
41 if !visual.is_visible() {
42 return false;
43 }
44
45 if let Some(filter) = filter {
46 if !filter(visual) {
47 return false;
48 }
49 }
50
51 let contains_point = Rect::from_size(visual.bounds().size()).contains(p);
52
53 if visual.clip_to_bounds() && !contains_point {
54 return false;
55 }
56
57 if let Some(clip) = visual.clip() {
58 if !clip.fill_contains(p) {
59 return false;
60 }
61 }
62
63 if let Some(children) = visual.visual_children_snapshot() {
64 if visual.has_non_uniform_z_index_children() {
65 for child in Visual::sort_by_z_index(&children).iter() {
66 if Self::hit_test_child(child, p, filter, visit) {
67 return true;
68 }
69 }
70 } else {
71 for child in children.iter().rev() {
72 if Self::hit_test_child(child, p, filter, visit) {
73 return true;
74 }
75 }
76 }
77 }
78
79 let is_hit = visual.custom_hit_test(p).unwrap_or(contains_point);
81
82 is_hit && visit(visual)
83 }
84
85 fn hit_test_child(
86 child: &Visual,
87 parent_point: Point,
88 filter: Option<&dyn Fn(&Visual) -> bool>,
89 visit: &mut dyn FnMut(&Visual) -> bool,
90 ) -> bool {
91 match local_transform(child).try_invert() {
92 Some(inverted) => Self::hit_test_core(child, parent_point.transform(inverted), filter, visit),
93 None => false,
94 }
95 }
96
97 fn transform_geometry(input: &Geometry, matrix: Matrix) -> Ref<Geometry> {
100 let result = input.clone_geometry();
101 let current = input.transform().map_or(Matrix::IDENTITY, |transform| transform.value());
102 result.set_transform(MatrixTransform::with_matrix(current * matrix));
103 result
104 }
105
106 fn is_hit(result: IntersectionResult) -> bool {
107 !matches!(result, IntersectionResult::NotCalculated | IntersectionResult::Empty)
108 }
109
110 fn hit_test_geometry_core(
113 visual: &Visual,
114 geometry: &Geometry,
115 filter: Option<&dyn Fn(&Visual) -> bool>,
116 visit: &mut dyn FnMut(&Visual, IntersectionResult) -> bool,
117 ) -> bool {
118 if !visual.is_visible() {
119 return false;
120 }
121
122 if let Some(filter) = filter {
123 if !filter(visual) {
124 return false;
125 }
126 }
127
128 let bounds = Rect::from_size(visual.bounds().size());
129
130 if visual.clip_to_bounds() {
131 let geometry_bounds = geometry.bounds();
132 if !(geometry_bounds.width > 0.0 && geometry_bounds.height > 0.0 && geometry_bounds.intersects(bounds)) {
133 return false;
134 }
135 }
136
137 if let Some(clip) = visual.clip() {
138 if !clip.get_fill_intersection_result(geometry).is_some_and(Self::is_hit) {
139 return false;
140 }
141 }
142
143 if let Some(children) = visual.visual_children_snapshot() {
144 if visual.has_non_uniform_z_index_children() {
145 for child in Visual::sort_by_z_index(&children).iter() {
146 if Self::hit_test_geometry_child(child, geometry, filter, visit) {
147 return true;
148 }
149 }
150 } else {
151 for child in children.iter().rev() {
152 if Self::hit_test_geometry_child(child, geometry, filter, visit) {
153 return true;
154 }
155 }
156 }
157 }
158
159 if let Some(result) = visual.custom_hit_test_geometry(&geometry.to_ref()) {
162 return Self::is_hit(result) && visit(visual, result);
163 }
164
165 let target = RectangleGeometry::with_rect(bounds);
166 match geometry.get_fill_intersection_result(&target) {
167 Some(result) if Self::is_hit(result) => visit(visual, result),
168 _ => false,
169 }
170 }
171
172 fn hit_test_geometry_child(
173 child: &Visual,
174 parent_geometry: &Geometry,
175 filter: Option<&dyn Fn(&Visual) -> bool>,
176 visit: &mut dyn FnMut(&Visual, IntersectionResult) -> bool,
177 ) -> bool {
178 match local_transform(child).try_invert() {
179 Some(inverted) => {
180 let geometry = Self::transform_geometry(parent_geometry, inverted);
181 Self::hit_test_geometry_core(child, &geometry, filter, visit)
182 }
183 None => false,
184 }
185 }
186}
187
188impl IHitTester for ManagedHitTester {
189 fn hit_test(&self, p: Point, root: &Visual, filter: Option<&dyn Fn(&Visual) -> bool>) -> Vec<Ref<Visual>> {
190 let mut result = Vec::new();
191 Self::hit_test_core(root, p, filter, &mut |visual| {
192 result.push(visual.to_ref());
193 false
194 });
195 result
196 }
197
198 fn hit_test_first(
199 &self,
200 p: Point,
201 root: &Visual,
202 filter: Option<&dyn Fn(&Visual) -> bool>,
203 ) -> Option<Ref<Visual>> {
204 let mut result = None;
205 Self::hit_test_core(root, p, filter, &mut |visual| {
206 result = Some(visual.to_ref());
207 true
208 });
209 result
210 }
211
212 fn hit_test_geometry(
213 &self,
214 geometry: &Geometry,
215 root: &Visual,
216 filter: Option<&dyn Fn(&Visual) -> bool>,
217 ) -> Vec<GeometryHitTestResult> {
218 let mut result = Vec::new();
219 Self::hit_test_geometry_core(root, geometry, filter, &mut |visual, intersection| {
220 result.push(GeometryHitTestResult::new(visual.to_ref(), intersection));
221 false
222 });
223 result
224 }
225
226 fn hit_test_first_geometry(
227 &self,
228 geometry: &Geometry,
229 root: &Visual,
230 filter: Option<&dyn Fn(&Visual) -> bool>,
231 ) -> Option<GeometryHitTestResult> {
232 let mut result = None;
233 Self::hit_test_geometry_core(root, geometry, filter, &mut |visual, intersection| {
234 result = Some(GeometryHitTestResult::new(visual.to_ref(), intersection));
235 true
236 });
237 result
238 }
239}