1use super::{IRenderDataVisitor, RenderDataStream};
2use crate::media::{
3 BoxShadows, CombinedGeometry, EllipseGeometry, Geometry, IBrush, IPen, ImmutableGeometry,
4 IntersectionResult, LineGeometry, MatrixTransform, RectangleGeometry, RenderOptions, TextOptions,
5};
6use crate::platform::{IGeometryImpl, IGlyphRunImpl};
7use crate::rendering::scene_graph::ICustomDrawOperation;
8use crate::{Matrix, Point, Rect, Ref, RoundedRect};
9use std::sync::Arc;
10
11pub struct HitTestScope {
13 saved_live: bool,
14 restore_point: bool,
15 saved_point: Option<Point>,
16}
17
18impl HitTestScope {
19 fn new(saved_live: bool) -> Self {
20 Self { saved_live, restore_point: false, saved_point: None }
21 }
22}
23
24pub struct HitTestVisitor {
26 stop_visiting: bool,
27 pub hit_found: bool,
28 pub hit_result: IntersectionResult,
29 current_geometry: Option<Ref<Geometry>>,
30 pub current_point: Option<Point>,
31 pub live: bool,
32 saved_geometries: Option<Vec<Ref<Geometry>>>,
33}
34
35impl HitTestVisitor {
36 fn new() -> Self {
37 Self {
38 stop_visiting: false,
39 hit_found: false,
40 hit_result: IntersectionResult::NotCalculated,
41 current_geometry: None,
42 current_point: None,
43 live: true,
44 saved_geometries: None,
45 }
46 }
47
48 pub fn for_geometry(geometry: Ref<Geometry>) -> Self {
49 Self { current_geometry: Some(geometry), saved_geometries: Some(Vec::new()), ..Self::new() }
50 }
51
52 pub fn for_point(point: Point) -> Self {
53 Self { current_point: Some(point), ..Self::new() }
54 }
55
56 fn hit(&mut self) {
57 self.hit_found = true;
58 self.stop_visiting = true;
59 }
60
61 fn hit_with(&mut self, result: IntersectionResult) {
62 self.hit_result = result;
63 self.stop_visiting = true;
64 }
65
66 fn get_intersection_detail(first_rect: Rect, second_rect: Rect) -> IntersectionResult {
67 if first_rect.contains_rect(second_rect) {
68 return IntersectionResult::FullyContains;
69 }
70 if second_rect.contains_rect(second_rect) {
73 return IntersectionResult::FullyInside;
74 }
75 if first_rect.intersects(second_rect) {
76 return IntersectionResult::Intersects;
77 }
78 IntersectionResult::Empty
79 }
80}
81
82impl IRenderDataVisitor for HitTestVisitor {
83 type Scope = HitTestScope;
84
85 fn stop_visiting(&self) -> bool {
86 self.stop_visiting
87 }
88
89 fn on_draw_line(&mut self, _server_pen: Option<&dyn IPen>, client_pen: Option<&dyn IPen>, p1: Point, p2: Point) {
90 if !self.live {
91 return;
92 }
93 if let Some(point) = self.current_point {
94 if hit_test_line(client_pen, p1, p2, point) {
95 self.hit();
96 return;
97 }
98 }
99 if let Some(geometry) = self.current_geometry.clone() {
100 self.hit_with(hit_test_line_geometry(client_pen, p1, p2, &geometry));
101 }
102 }
103
104 fn on_draw_rectangle(
105 &mut self,
106 server_brush: Option<&dyn IBrush>,
107 _server_pen: Option<&dyn IPen>,
108 client_pen: Option<&dyn IPen>,
109 rect: RoundedRect,
110 _box_shadows: &BoxShadows,
111 ) {
112 if !self.live {
113 return;
114 }
115 if let Some(point) = self.current_point {
116 if hit_test_rectangle(server_brush, client_pen, rect, point) {
117 self.hit();
118 return;
119 }
120 }
121 if let Some(geometry) = self.current_geometry.clone() {
122 self.hit_with(hit_test_rectangle_geometry(server_brush, client_pen, rect, &geometry));
123 }
124 }
125
126 fn on_draw_ellipse(
127 &mut self,
128 server_brush: Option<&dyn IBrush>,
129 _server_pen: Option<&dyn IPen>,
130 client_pen: Option<&dyn IPen>,
131 rect: Rect,
132 ) {
133 if !self.live {
134 return;
135 }
136 if let Some(point) = self.current_point {
137 if hit_test_ellipse(server_brush, client_pen, rect, point) {
138 self.hit();
139 return;
140 }
141 }
142 if let Some(geometry) = self.current_geometry.clone() {
143 self.hit_with(hit_test_ellipse_geometry(server_brush, client_pen, rect, &geometry));
144 }
145 }
146
147 fn on_draw_geometry(
148 &mut self,
149 server_brush: Option<&dyn IBrush>,
150 _server_pen: Option<&dyn IPen>,
151 client_pen: Option<&dyn IPen>,
152 geometry: Option<&Arc<dyn IGeometryImpl>>,
153 ) {
154 let Some(geometry) = geometry else { return };
155 if !self.live {
156 return;
157 }
158 if let Some(point) = self.current_point {
159 if (server_brush.is_some() && geometry.fill_contains(point))
160 || (client_pen.is_some() && geometry.stroke_contains(client_pen, point))
161 {
162 self.hit();
163 return;
164 }
165 }
166 if let Some(current_geometry_impl) = self.current_geometry.as_ref().and_then(|g| g.platform_impl()) {
167 let result = hit_test_geometry(server_brush, client_pen, Some(¤t_geometry_impl), Some(geometry));
168 if (result as i32) > (IntersectionResult::Empty as i32) {
169 self.hit_with(result);
170 }
171 }
172 }
173
174 fn on_draw_glyph_run(&mut self, _server_brush: Option<&dyn IBrush>, glyph_run: Option<&std::sync::Arc<dyn IGlyphRunImpl>>) {
175 let Some(glyph_run) = glyph_run else { return };
176 if !self.live {
177 return;
178 }
179 if let Some(point) = self.current_point {
180 if glyph_run.bounds().contains_exclusive(point) {
181 self.hit();
182 return;
183 }
184 }
185 if let Some(geometry) = &self.current_geometry {
186 let result = Self::get_intersection_detail(glyph_run.bounds(), geometry.bounds());
187 self.hit_with(result);
188 }
189 }
190
191 fn on_draw_bitmap(&mut self, _bitmap: Option<&std::sync::Arc<crate::platform::SharedBitmapImpl>>, _opacity: f64, _source_rect: Rect, dest_rect: Rect) {
192 if !self.live {
193 return;
194 }
195 if let Some(point) = self.current_point {
196 if dest_rect.contains(point) {
197 self.hit();
198 return;
199 }
200 }
201 if let Some(geometry) = &self.current_geometry {
202 let result = Self::get_intersection_detail(dest_rect, geometry.bounds());
203 self.hit_with(result);
204 }
205 }
206
207 fn on_draw_custom(&mut self, operation: Option<&std::sync::Arc<dyn ICustomDrawOperation>>) {
208 if !self.live {
209 return;
210 }
211 let Some(operation) = operation else { return };
212 if let Some(point) = self.current_point {
213 if operation.hit_test(point) {
214 self.hit();
215 return;
216 }
217 }
218 if let Some(geometry) = self.current_geometry.clone() {
219 self.hit_with(operation.hit_test_geometry(&geometry));
220 }
221 }
222
223 fn on_push_clip(&mut self, clip: RoundedRect) -> HitTestScope {
224 let scope = HitTestScope::new(self.live);
225 if self.live
226 && (self.current_point.is_some_and(|point| !clip.rect.contains(point))
227 || self.current_geometry.as_ref().is_some_and(|geometry| !clip.rect.contains_rect(geometry.bounds())))
228 {
229 self.live = false;
230 }
231 scope
232 }
233
234 fn on_push_geometry_clip(&mut self, geometry: Option<&Arc<dyn IGeometryImpl>>) -> HitTestScope {
235 let scope = HitTestScope::new(self.live);
236 if let (true, Some(geometry)) = (self.live, geometry) {
237 if self.current_point.is_some_and(|point| !geometry.fill_contains(point))
238 || self.current_geometry.as_ref().and_then(|g| g.platform_impl()).is_some_and(|current| {
239 current.get_fill_intersection_result(&**geometry) == IntersectionResult::Empty
240 })
241 {
242 self.live = false;
243 }
244 }
245 scope
246 }
247
248 fn on_push_opacity(&mut self, _opacity: f64) -> HitTestScope {
249 HitTestScope::new(self.live)
250 }
251
252 fn on_push_opacity_mask(&mut self, _brush: Option<&dyn IBrush>, _bounds: Rect) -> HitTestScope {
253 HitTestScope::new(self.live)
254 }
255
256 fn on_push_transform(&mut self, matrix: Matrix) -> HitTestScope {
257 let mut scope = HitTestScope::new(self.live);
258 if self.live {
259 match matrix.try_invert() {
260 Some(inverted) => {
261 scope.restore_point = true;
262 if let Some(point) = self.current_point {
263 scope.saved_point = Some(point);
264 self.current_point = Some(inverted.transform(point));
265 } else if let Some(current) = self.current_geometry.take() {
266 if let Some(saved) = self.saved_geometries.as_mut() {
267 saved.push(current.clone());
268 }
269 let clone = current.clone_geometry();
270 let existing = clone.transform().map_or(Matrix::IDENTITY, |t| t.value());
271 clone.set_transform(MatrixTransform::with_matrix(existing * inverted));
272 self.current_geometry = Some(clone);
273 }
274 }
275 None => self.live = false,
276 }
277 }
278 scope
279 }
280
281 fn on_push_render_options(&mut self, _options: RenderOptions) -> HitTestScope {
282 HitTestScope::new(self.live)
283 }
284
285 fn on_push_text_options(&mut self, _options: TextOptions) -> HitTestScope {
286 HitTestScope::new(self.live)
287 }
288
289 fn on_push_effect(&mut self, _effect: Option<&std::sync::Arc<dyn crate::media::IImmutableEffect>>, _bounds: Rect) -> HitTestScope {
290 HitTestScope::new(self.live)
291 }
292
293 fn on_pop(&mut self, scope: HitTestScope) {
294 self.live = scope.saved_live;
295 if scope.restore_point {
296 self.current_point = scope.saved_point;
297 self.current_geometry = self.saved_geometries.as_mut().and_then(Vec::pop);
298 }
299 }
300}
301
302impl RenderDataStream {
303 pub fn hit_test(&self, point: Point) -> bool {
305 let mut visitor = HitTestVisitor::for_point(point);
306 self.visit(&mut visitor);
307 visitor.hit_found
308 }
309
310 pub fn hit_test_geometry(&self, geometry: &Ref<Geometry>) -> IntersectionResult {
312 let mut visitor = HitTestVisitor::for_geometry(geometry.clone());
313 self.visit(&mut visitor);
314 visitor.hit_result
315 }
316}
317
318fn hit_test_line(client_pen: Option<&dyn IPen>, p1: Point, p2: Point, p: Point) -> bool {
319 let Some(client_pen) = client_pen else { return false };
320
321 let half_thickness = client_pen.thickness() / 2.0;
322 let min_x = p1.x.min(p2.x) - half_thickness;
323 let max_x = p1.x.max(p2.x) + half_thickness;
324 let min_y = p1.y.min(p2.y) - half_thickness;
325 let max_y = p1.y.max(p2.y) + half_thickness;
326
327 if p.x < min_x || p.x > max_x || p.y < min_y || p.y > max_y {
328 return false;
329 }
330
331 let length = |x: f64, y: f64| (x * x + y * y).sqrt();
332
333 let (ap_x, ap_y) = (p.x - p1.x, p.y - p1.y);
334 let dot1 = (p2.x - p1.x) * ap_x + (p2.y - p1.y) * ap_y;
335 if dot1 < 0.0 {
336 return length(ap_x, ap_y) <= half_thickness;
337 }
338
339 let (bp_x, bp_y) = (p.x - p2.x, p.y - p2.y);
340 let dot2 = (p1.x - p2.x) * bp_x + (p1.y - p2.y) * bp_y;
341 if dot2 < 0.0 {
342 return length(bp_x, bp_y) <= half_thickness;
343 }
344
345 let b_xa_x = p2.x - p1.x;
346 let b_ya_y = p2.y - p1.y;
347
348 let distance = (b_xa_x * (p.y - p1.y) - b_ya_y * (p.x - p1.x)) / (b_xa_x * b_xa_x + b_ya_y * b_ya_y).sqrt();
349
350 distance.abs() <= half_thickness
351}
352
353fn hit_test_line_geometry(client_pen: Option<&dyn IPen>, p1: Point, p2: Point, geometry: &Ref<Geometry>) -> IntersectionResult {
354 if client_pen.is_none() {
355 return IntersectionResult::NotCalculated;
356 }
357 hit_test_geometry(
358 None,
359 client_pen,
360 geometry.platform_impl().as_ref(),
361 LineGeometry::with_points(p1, p2).platform_impl().as_ref(),
362 )
363}
364
365fn hit_test_rectangle(server_brush: Option<&dyn IBrush>, client_pen: Option<&dyn IPen>, rect: RoundedRect, p: Point) -> bool {
366 let stroke_thickness_adjustment = client_pen.map_or(0.0, |pen| pen.thickness() / 2.0);
367
368 if rect.is_rounded() {
369 let outer = rect.inflate(stroke_thickness_adjustment, stroke_thickness_adjustment);
370 if outer.contains_exclusive(p) {
371 if server_brush.is_some() {
372 return true;
373 }
374 let inner = rect.deflate(stroke_thickness_adjustment, stroke_thickness_adjustment);
375 return !inner.contains_exclusive(p);
376 }
377 } else {
378 let outer = rect.rect.inflate(stroke_thickness_adjustment);
379 if outer.contains_exclusive(p) {
380 if server_brush.is_some() {
381 return true;
382 }
383 let inner = rect.rect.deflate(stroke_thickness_adjustment);
384 return !inner.contains_exclusive(p);
385 }
386 }
387
388 false
389}
390
391fn hit_test_rectangle_geometry(
392 server_brush: Option<&dyn IBrush>,
393 client_pen: Option<&dyn IPen>,
394 rect: RoundedRect,
395 geometry: &Ref<Geometry>,
396) -> IntersectionResult {
397 let target = if rect.is_rounded() {
398 RectangleGeometry::with_rect_and_radii(rect.rect, rect.radii_top_left.x, rect.radii_top_left.y)
399 } else {
400 RectangleGeometry::with_rect(rect.rect)
401 };
402 hit_test_geometry(server_brush, client_pen, geometry.platform_impl().as_ref(), target.platform_impl().as_ref())
403}
404
405fn hit_test_geometry(
406 server_brush: Option<&dyn IBrush>,
407 client_pen: Option<&dyn IPen>,
408 current_geometry: Option<&Arc<dyn IGeometryImpl>>,
409 target_geometry: Option<&Arc<dyn IGeometryImpl>>,
410) -> IntersectionResult {
411 let (Some(current_geometry), Some(target_geometry)) = (current_geometry, target_geometry) else {
412 return IntersectionResult::Empty;
413 };
414
415 match (server_brush, client_pen) {
416 (None, Some(client_pen)) => {
417 current_geometry.get_fill_intersection_result(&*target_geometry.get_widened_geometry(client_pen))
418 }
419 (Some(_), Some(client_pen)) if client_pen.thickness() > 0.0 => {
420 let stroke_geometry = target_geometry.get_widened_geometry(client_pen);
421 let combined = CombinedGeometry::with_geometries(
422 ImmutableGeometry::new(Some(stroke_geometry)).upcast(),
423 ImmutableGeometry::new(Some(target_geometry.clone())).upcast(),
424 );
425 match combined.platform_impl() {
426 Some(combined_impl) => current_geometry.get_fill_intersection_result(&*combined_impl),
427 None => IntersectionResult::Empty,
428 }
429 }
430 (Some(_), _) => current_geometry.get_fill_intersection_result(&**target_geometry),
431 (None, None) => IntersectionResult::Empty,
432 }
433}
434
435fn hit_test_ellipse(server_brush: Option<&dyn IBrush>, client_pen: Option<&dyn IPen>, rect: Rect, p: Point) -> bool {
436 let center = rect.center();
437
438 let stroke_thickness = client_pen.map_or(0.0, |pen| pen.thickness());
439
440 let mut rx = rect.width / 2.0 + stroke_thickness / 2.0;
441 let mut ry = rect.height / 2.0 + stroke_thickness / 2.0;
442
443 let dx = p.x - center.x;
444 let dy = p.y - center.y;
445
446 if dx.abs() > rx || dy.abs() > ry {
447 return false;
448 }
449
450 if server_brush.is_some() {
451 return ellipse_contains(dx, dy, rx, ry);
452 }
453
454 if stroke_thickness > 0.0 {
455 let in_stroke = ellipse_contains(dx, dy, rx, ry);
456
457 rx = rect.width / 2.0 - stroke_thickness / 2.0;
458 ry = rect.height / 2.0 - stroke_thickness / 2.0;
459
460 let in_inner = ellipse_contains(dx, dy, rx, ry);
461
462 return in_stroke && !in_inner;
463 }
464
465 false
466}
467
468fn hit_test_ellipse_geometry(
469 server_brush: Option<&dyn IBrush>,
470 client_pen: Option<&dyn IPen>,
471 rect: Rect,
472 geometry: &Ref<Geometry>,
473) -> IntersectionResult {
474 hit_test_geometry(
475 server_brush,
476 client_pen,
477 geometry.platform_impl().as_ref(),
478 EllipseGeometry::with_rect(rect).platform_impl().as_ref(),
479 )
480}
481
482fn ellipse_contains(dx: f64, dy: f64, radius_x: f64, radius_y: f64) -> bool {
483 let rx2 = radius_x * radius_x;
484 let ry2 = radius_y * radius_y;
485
486 let distance = ry2 * dx * dx + rx2 * dy * dy;
487
488 distance <= rx2 * ry2
489}