1use api::{BorderRadius, ClipMode, HitTestResultItem, HitTestResult, ItemTag, PrimitiveFlags};
6use api::{PipelineId, ApiHitTester};
7use api::units::*;
8use crate::clip::{rounded_rectangle_contains_point, ClipNodeId, ClipTreeBuilder};
9use crate::clip::{polygon_contains_point, ClipItemKey, ClipItemKeyKind};
10use crate::prim_store::PolygonKey;
11use crate::scene_builder_thread::Interners;
12use crate::spatial_tree::{SpatialNodeIndex, SpatialTree, get_external_scroll_offset};
13use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo};
14use std::sync::{Arc, Mutex};
15use crate::util::LayoutToWorldFastTransform;
16
17pub struct SharedHitTester {
18 hit_tester: Mutex<Arc<HitTester>>,
23}
24
25impl SharedHitTester {
26 pub fn new() -> Self {
27 SharedHitTester {
28 hit_tester: Mutex::new(Arc::new(HitTester::empty())),
29 }
30 }
31
32 pub fn get_ref(&self) -> Arc<HitTester> {
33 let guard = self.hit_tester.lock().unwrap();
34 Arc::clone(&*guard)
35 }
36
37 pub(crate) fn update(&self, new_hit_tester: Arc<HitTester>) {
38 let mut guard = self.hit_tester.lock().unwrap();
39 *guard = new_hit_tester;
40 }
41}
42
43impl ApiHitTester for SharedHitTester {
44 fn hit_test(&self,
45 point: WorldPoint,
46 ) -> HitTestResult {
47 self.get_ref().hit_test(HitTest::new(point))
48 }
49}
50
51#[derive(MallocSizeOf)]
55struct HitTestSpatialNode {
56 pipeline_id: PipelineId,
58
59 world_content_transform: LayoutToWorldFastTransform,
61
62 world_viewport_transform: LayoutToWorldFastTransform,
64
65 external_scroll_offset: LayoutVector2D,
67}
68
69#[derive(MallocSizeOf)]
70struct HitTestClipNode {
71 region: HitTestRegion,
74 spatial_node_index: SpatialNodeIndex,
76 parent: ClipNodeId,
78}
79
80impl HitTestClipNode {
81 fn new(
82 item: &ClipItemKey,
83 clip_rect: LayoutRect,
84 interners: &Interners,
85 parent: ClipNodeId,
86 spatial_node_index: SpatialNodeIndex,
87 ) -> Self {
88 let region = match item.kind {
89 ClipItemKeyKind::Rectangle(mode) => {
90 HitTestRegion::Rectangle(clip_rect, mode)
91 }
92 ClipItemKeyKind::RoundedRectangle(radius, mode) => {
93 HitTestRegion::RoundedRectangle(clip_rect, radius.into(), mode)
94 }
95 ClipItemKeyKind::ImageMask(_, polygon_handle) => {
96 if let Some(handle) = polygon_handle {
97 let polygon = &interners.polygon[handle];
99 HitTestRegion::Polygon(clip_rect, *polygon)
100 } else {
101 HitTestRegion::Rectangle(clip_rect, ClipMode::Clip)
102 }
103 }
104 };
105
106 HitTestClipNode {
107 region,
108 spatial_node_index,
109 parent,
110 }
111 }
112}
113
114#[derive(Clone, MallocSizeOf)]
115struct HitTestingItem {
116 rect: LayoutRect,
117 tag: ItemTag,
118 animation_id: u64,
119 is_backface_visible: bool,
120 spatial_node_index: SpatialNodeIndex,
121 clip_node_id: ClipNodeId,
122}
123
124impl HitTestingItem {
125 fn new(
126 tag: ItemTag,
127 animation_id: u64,
128 info: &LayoutPrimitiveInfo,
129 spatial_node_index: SpatialNodeIndex,
130 clip_node_id: ClipNodeId,
131 ) -> HitTestingItem {
132 HitTestingItem {
133 rect: info.rect,
134 tag,
135 animation_id,
136 is_backface_visible: info.flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
137 spatial_node_index,
138 clip_node_id,
139 }
140 }
141}
142
143pub struct HitTestingSceneStats {
146 pub clip_nodes_count: usize,
147 pub items_count: usize,
148}
149
150impl HitTestingSceneStats {
151 pub fn empty() -> Self {
152 HitTestingSceneStats {
153 clip_nodes_count: 0,
154 items_count: 0,
155 }
156 }
157}
158
159#[derive(MallocSizeOf)]
166pub struct HitTestingScene {
167 clip_nodes: FastHashMap<ClipNodeId, HitTestClipNode>,
168
169 items: Vec<HitTestingItem>,
171}
172
173impl HitTestingScene {
174 pub fn new(stats: &HitTestingSceneStats) -> Self {
177 HitTestingScene {
178 clip_nodes: FastHashMap::default(),
179 items: Vec::with_capacity(stats.items_count),
180 }
181 }
182
183 pub fn reset(&mut self) {
184 self.clip_nodes.clear();
185 self.items.clear();
186 }
187
188 pub fn get_stats(&self) -> HitTestingSceneStats {
190 HitTestingSceneStats {
191 clip_nodes_count: 0,
192 items_count: self.items.len(),
193 }
194 }
195
196 fn add_clip_node(
197 &mut self,
198 clip_node_id: ClipNodeId,
199 clip_tree_builder: &ClipTreeBuilder,
200 interners: &Interners,
201 ) {
202 if clip_node_id == ClipNodeId::NONE {
203 return;
204 }
205
206 if !self.clip_nodes.contains_key(&clip_node_id) {
207 let src_clip_node = clip_tree_builder.get_node(clip_node_id);
208 let clip_item = &interners.clip[src_clip_node.handle];
209
210 let clip_node = HitTestClipNode::new(
216 &clip_item.key,
217 src_clip_node.unsnapped_clip_rect,
218 interners,
219 src_clip_node.parent,
220 src_clip_node.spatial_node_index,
221 );
222
223 self.clip_nodes.insert(clip_node_id, clip_node);
224
225 self.add_clip_node(
226 src_clip_node.parent,
227 clip_tree_builder,
228 interners,
229 );
230 }
231 }
232
233 pub fn add_item(
235 &mut self,
236 tag: ItemTag,
237 anim_id: u64,
238 info: &LayoutPrimitiveInfo,
239 spatial_node_index: SpatialNodeIndex,
240 clip_node_id: ClipNodeId,
241 clip_tree_builder: &ClipTreeBuilder,
242 interners: &Interners,
243 ) {
244 self.add_clip_node(
245 clip_node_id,
246 clip_tree_builder,
247 interners,
248 );
249
250 let item = HitTestingItem::new(
251 tag,
252 anim_id,
253 info,
254 spatial_node_index,
255 clip_node_id,
256 );
257
258 self.items.push(item);
259 }
260}
261
262#[derive(MallocSizeOf)]
263enum HitTestRegion {
264 Rectangle(LayoutRect, ClipMode),
265 RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
266 Polygon(LayoutRect, PolygonKey),
267}
268
269impl HitTestRegion {
270 fn contains(&self, point: &LayoutPoint) -> bool {
271 match *self {
272 HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
273 rectangle.contains(*point),
274 HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
275 !rectangle.contains(*point),
276 HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
277 rounded_rectangle_contains_point(point, &rect, &radii),
278 HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
279 !rounded_rectangle_contains_point(point, &rect, &radii),
280 HitTestRegion::Polygon(rect, polygon) =>
281 polygon_contains_point(point, &rect, &polygon),
282 }
283 }
284}
285
286#[derive(MallocSizeOf)]
287pub struct HitTester {
288 #[ignore_malloc_size_of = "Arc"]
289 scene: Arc<HitTestingScene>,
290 spatial_nodes: FastHashMap<SpatialNodeIndex, HitTestSpatialNode>,
291 pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
292}
293
294impl HitTester {
295 pub fn empty() -> Self {
296 HitTester {
297 scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
298 spatial_nodes: FastHashMap::default(),
299 pipeline_root_nodes: FastHashMap::default(),
300 }
301 }
302
303 pub fn new(
304 scene: Arc<HitTestingScene>,
305 spatial_tree: &SpatialTree,
306 ) -> HitTester {
307 let mut hit_tester = HitTester {
308 scene,
309 spatial_nodes: FastHashMap::default(),
310 pipeline_root_nodes: FastHashMap::default(),
311 };
312 hit_tester.read_spatial_tree(spatial_tree);
313 hit_tester
314 }
315
316 fn read_spatial_tree(
317 &mut self,
318 spatial_tree: &SpatialTree,
319 ) {
320 self.spatial_nodes.clear();
321 self.spatial_nodes.reserve(spatial_tree.spatial_node_count());
322
323 self.pipeline_root_nodes.clear();
324
325 spatial_tree.visit_nodes(|index, node| {
326 self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
329
330 self.spatial_nodes.insert(index, HitTestSpatialNode {
335 pipeline_id: node.pipeline_id,
336 world_content_transform: spatial_tree
337 .get_world_transform(index)
338 .into_fast_transform(),
339 world_viewport_transform: spatial_tree
340 .get_world_viewport_transform(index)
341 .into_fast_transform(),
342 external_scroll_offset: get_external_scroll_offset(spatial_tree, index),
343 });
344 });
345 }
346
347 pub fn hit_test(&self, test: HitTest) -> HitTestResult {
348 let mut result = HitTestResult::default();
349
350 let mut current_spatial_node_index = SpatialNodeIndex::INVALID;
351 let mut point_in_layer = None;
352 let mut current_root_spatial_node_index = SpatialNodeIndex::INVALID;
353 let mut point_in_viewport = None;
354
355 for item in self.scene.items.iter().rev() {
357 let scroll_node = &self.spatial_nodes[&item.spatial_node_index];
358 let pipeline_id = scroll_node.pipeline_id;
359
360 if item.spatial_node_index != current_spatial_node_index {
363 point_in_layer = scroll_node
364 .world_content_transform
365 .inverse()
366 .and_then(|inverted| inverted.project_point2d(test.point));
367 current_spatial_node_index = item.spatial_node_index;
368 }
369
370 let point_in_layer = match point_in_layer {
372 Some(p) => p,
373 None => continue,
374 };
375
376 if !item.rect.contains(point_in_layer) {
379 continue;
380 }
381
382 let mut current_clip_node_id = item.clip_node_id;
384 let mut is_valid = true;
385
386 while current_clip_node_id != ClipNodeId::NONE {
387 let clip_node = &self.scene.clip_nodes[¤t_clip_node_id];
388
389 let transform = self
390 .spatial_nodes[&clip_node.spatial_node_index]
391 .world_content_transform;
392 if let Some(transformed_point) = transform
393 .inverse()
394 .and_then(|inverted| inverted.project_point2d(test.point))
395 {
396 if !clip_node.region.contains(&transformed_point) {
397 is_valid = false;
398 break;
399 }
400 }
401
402 current_clip_node_id = clip_node.parent;
403 }
404
405 if !is_valid {
406 continue;
407 }
408
409 if !item.is_backface_visible && scroll_node.world_content_transform.is_backface_visible() {
411 continue;
412 }
413
414 let root_spatial_node_index = self.pipeline_root_nodes[&pipeline_id];
419 if root_spatial_node_index != current_root_spatial_node_index {
420 let root_node = &self.spatial_nodes[&root_spatial_node_index];
421 point_in_viewport = root_node
422 .world_viewport_transform
423 .inverse()
424 .and_then(|inverted| inverted.transform_point2d(test.point))
425 .map(|pt| pt - scroll_node.external_scroll_offset);
426
427 current_root_spatial_node_index = root_spatial_node_index;
428 }
429
430 if let Some(point_in_viewport) = point_in_viewport {
431 result.items.push(HitTestResultItem {
432 pipeline: pipeline_id,
433 tag: item.tag,
434 animation_id: item.animation_id,
435 point_in_viewport,
436 });
437 }
438 }
439
440 result.items.dedup();
441 result
442 }
443}
444
445#[derive(MallocSizeOf)]
446pub struct HitTest {
447 point: WorldPoint,
448}
449
450impl HitTest {
451 pub fn new(
452 point: WorldPoint,
453 ) -> HitTest {
454 HitTest {
455 point,
456 }
457 }
458}