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 interners: &Interners,
84 parent: ClipNodeId,
85 ) -> Self {
86 let region = match item.kind {
87 ClipItemKeyKind::Rectangle(rect, mode) => {
88 HitTestRegion::Rectangle(rect.into(), mode)
89 }
90 ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
91 HitTestRegion::RoundedRectangle(rect.into(), radius.into(), mode)
92 }
93 ClipItemKeyKind::ImageMask(rect, _, polygon_handle) => {
94 if let Some(handle) = polygon_handle {
95 let polygon = &interners.polygon[handle];
97 HitTestRegion::Polygon(rect.into(), *polygon)
98 } else {
99 HitTestRegion::Rectangle(rect.into(), ClipMode::Clip)
100 }
101 }
102 ClipItemKeyKind::BoxShadow(..) => HitTestRegion::Invalid,
103 };
104
105 HitTestClipNode {
106 region,
107 spatial_node_index: item.spatial_node_index,
108 parent,
109 }
110 }
111}
112
113#[derive(Clone, MallocSizeOf)]
114struct HitTestingItem {
115 rect: LayoutRect,
116 tag: ItemTag,
117 animation_id: u64,
118 is_backface_visible: bool,
119 spatial_node_index: SpatialNodeIndex,
120 clip_node_id: ClipNodeId,
121}
122
123impl HitTestingItem {
124 fn new(
125 tag: ItemTag,
126 animation_id: u64,
127 info: &LayoutPrimitiveInfo,
128 spatial_node_index: SpatialNodeIndex,
129 clip_node_id: ClipNodeId,
130 ) -> HitTestingItem {
131 HitTestingItem {
132 rect: info.rect,
133 tag,
134 animation_id,
135 is_backface_visible: info.flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
136 spatial_node_index,
137 clip_node_id,
138 }
139 }
140}
141
142pub struct HitTestingSceneStats {
145 pub clip_nodes_count: usize,
146 pub items_count: usize,
147}
148
149impl HitTestingSceneStats {
150 pub fn empty() -> Self {
151 HitTestingSceneStats {
152 clip_nodes_count: 0,
153 items_count: 0,
154 }
155 }
156}
157
158#[derive(MallocSizeOf)]
165pub struct HitTestingScene {
166 clip_nodes: FastHashMap<ClipNodeId, HitTestClipNode>,
167
168 items: Vec<HitTestingItem>,
170}
171
172impl HitTestingScene {
173 pub fn new(stats: &HitTestingSceneStats) -> Self {
176 HitTestingScene {
177 clip_nodes: FastHashMap::default(),
178 items: Vec::with_capacity(stats.items_count),
179 }
180 }
181
182 pub fn reset(&mut self) {
183 self.clip_nodes.clear();
184 self.items.clear();
185 }
186
187 pub fn get_stats(&self) -> HitTestingSceneStats {
189 HitTestingSceneStats {
190 clip_nodes_count: 0,
191 items_count: self.items.len(),
192 }
193 }
194
195 fn add_clip_node(
196 &mut self,
197 clip_node_id: ClipNodeId,
198 clip_tree_builder: &ClipTreeBuilder,
199 interners: &Interners,
200 ) {
201 if clip_node_id == ClipNodeId::NONE {
202 return;
203 }
204
205 if !self.clip_nodes.contains_key(&clip_node_id) {
206 let src_clip_node = clip_tree_builder.get_node(clip_node_id);
207 let clip_item = &interners.clip[src_clip_node.handle];
208
209 let clip_node = HitTestClipNode::new(
210 &clip_item.key,
211 interners,
212 src_clip_node.parent,
213 );
214
215 self.clip_nodes.insert(clip_node_id, clip_node);
216
217 self.add_clip_node(
218 src_clip_node.parent,
219 clip_tree_builder,
220 interners,
221 );
222 }
223 }
224
225 pub fn add_item(
227 &mut self,
228 tag: ItemTag,
229 anim_id: u64,
230 info: &LayoutPrimitiveInfo,
231 spatial_node_index: SpatialNodeIndex,
232 clip_node_id: ClipNodeId,
233 clip_tree_builder: &ClipTreeBuilder,
234 interners: &Interners,
235 ) {
236 self.add_clip_node(
237 clip_node_id,
238 clip_tree_builder,
239 interners,
240 );
241
242 let item = HitTestingItem::new(
243 tag,
244 anim_id,
245 info,
246 spatial_node_index,
247 clip_node_id,
248 );
249
250 self.items.push(item);
251 }
252}
253
254#[derive(MallocSizeOf)]
255enum HitTestRegion {
256 Invalid,
257 Rectangle(LayoutRect, ClipMode),
258 RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
259 Polygon(LayoutRect, PolygonKey),
260}
261
262impl HitTestRegion {
263 fn contains(&self, point: &LayoutPoint) -> bool {
264 match *self {
265 HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
266 rectangle.contains(*point),
267 HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
268 !rectangle.contains(*point),
269 HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
270 rounded_rectangle_contains_point(point, &rect, &radii),
271 HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
272 !rounded_rectangle_contains_point(point, &rect, &radii),
273 HitTestRegion::Polygon(rect, polygon) =>
274 polygon_contains_point(point, &rect, &polygon),
275 HitTestRegion::Invalid => true,
276 }
277 }
278}
279
280#[derive(MallocSizeOf)]
281pub struct HitTester {
282 #[ignore_malloc_size_of = "Arc"]
283 scene: Arc<HitTestingScene>,
284 spatial_nodes: FastHashMap<SpatialNodeIndex, HitTestSpatialNode>,
285 pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
286}
287
288impl HitTester {
289 pub fn empty() -> Self {
290 HitTester {
291 scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
292 spatial_nodes: FastHashMap::default(),
293 pipeline_root_nodes: FastHashMap::default(),
294 }
295 }
296
297 pub fn new(
298 scene: Arc<HitTestingScene>,
299 spatial_tree: &SpatialTree,
300 ) -> HitTester {
301 let mut hit_tester = HitTester {
302 scene,
303 spatial_nodes: FastHashMap::default(),
304 pipeline_root_nodes: FastHashMap::default(),
305 };
306 hit_tester.read_spatial_tree(spatial_tree);
307 hit_tester
308 }
309
310 fn read_spatial_tree(
311 &mut self,
312 spatial_tree: &SpatialTree,
313 ) {
314 self.spatial_nodes.clear();
315 self.spatial_nodes.reserve(spatial_tree.spatial_node_count());
316
317 self.pipeline_root_nodes.clear();
318
319 spatial_tree.visit_nodes(|index, node| {
320 self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
323
324 self.spatial_nodes.insert(index, HitTestSpatialNode {
329 pipeline_id: node.pipeline_id,
330 world_content_transform: spatial_tree
331 .get_world_transform(index)
332 .into_fast_transform(),
333 world_viewport_transform: spatial_tree
334 .get_world_viewport_transform(index)
335 .into_fast_transform(),
336 external_scroll_offset: get_external_scroll_offset(spatial_tree, index),
337 });
338 });
339 }
340
341 pub fn hit_test(&self, test: HitTest) -> HitTestResult {
342 let mut result = HitTestResult::default();
343
344 let mut current_spatial_node_index = SpatialNodeIndex::INVALID;
345 let mut point_in_layer = None;
346 let mut current_root_spatial_node_index = SpatialNodeIndex::INVALID;
347 let mut point_in_viewport = None;
348
349 for item in self.scene.items.iter().rev() {
351 let scroll_node = &self.spatial_nodes[&item.spatial_node_index];
352 let pipeline_id = scroll_node.pipeline_id;
353
354 if item.spatial_node_index != current_spatial_node_index {
357 point_in_layer = scroll_node
358 .world_content_transform
359 .inverse()
360 .and_then(|inverted| inverted.project_point2d(test.point));
361 current_spatial_node_index = item.spatial_node_index;
362 }
363
364 let point_in_layer = match point_in_layer {
366 Some(p) => p,
367 None => continue,
368 };
369
370 if !item.rect.contains(point_in_layer) {
373 continue;
374 }
375
376 let mut current_clip_node_id = item.clip_node_id;
378 let mut is_valid = true;
379
380 while current_clip_node_id != ClipNodeId::NONE {
381 let clip_node = &self.scene.clip_nodes[¤t_clip_node_id];
382
383 let transform = self
384 .spatial_nodes[&clip_node.spatial_node_index]
385 .world_content_transform;
386 if let Some(transformed_point) = transform
387 .inverse()
388 .and_then(|inverted| inverted.project_point2d(test.point))
389 {
390 if !clip_node.region.contains(&transformed_point) {
391 is_valid = false;
392 break;
393 }
394 }
395
396 current_clip_node_id = clip_node.parent;
397 }
398
399 if !is_valid {
400 continue;
401 }
402
403 if !item.is_backface_visible && scroll_node.world_content_transform.is_backface_visible() {
405 continue;
406 }
407
408 let root_spatial_node_index = self.pipeline_root_nodes[&pipeline_id];
413 if root_spatial_node_index != current_root_spatial_node_index {
414 let root_node = &self.spatial_nodes[&root_spatial_node_index];
415 point_in_viewport = root_node
416 .world_viewport_transform
417 .inverse()
418 .and_then(|inverted| inverted.transform_point2d(test.point))
419 .map(|pt| pt - scroll_node.external_scroll_offset);
420
421 current_root_spatial_node_index = root_spatial_node_index;
422 }
423
424 if let Some(point_in_viewport) = point_in_viewport {
425 result.items.push(HitTestResultItem {
426 pipeline: pipeline_id,
427 tag: item.tag,
428 animation_id: item.animation_id,
429 point_in_viewport,
430 });
431 }
432 }
433
434 result.items.dedup();
435 result
436 }
437}
438
439#[derive(MallocSizeOf)]
440pub struct HitTest {
441 point: WorldPoint,
442}
443
444impl HitTest {
445 pub fn new(
446 point: WorldPoint,
447 ) -> HitTest {
448 HitTest {
449 point,
450 }
451 }
452}