From a2cf8e583d5476cc5eb68c401fe58b23038639d9 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Sat, 26 Sep 2015 12:42:44 -0700 Subject: [PATCH] Make DisplayList hit testing a method on DisplayList This will allow us to hit test into DisplayLists that are not directly contained in StackingContexts, but instead are children of PaintLayers. It also makes things slightly simpler. --- components/gfx/display_list/mod.rs | 233 +++++++++++++++-------------- 1 file changed, 119 insertions(+), 114 deletions(-) diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 6c001ab310f..05f1f5a6983 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -323,6 +323,121 @@ impl DisplayList { paint_subcontext.pop_clip_if_applicable(); paint_subcontext.draw_target.set_transform(&old_transform) } + + pub fn hit_test(&self, + point: Point2D, + result: &mut Vec, + topmost_only: bool) { + fn hit_test_in_list<'a, I>(point: Point2D, + result: &mut Vec, + topmost_only: bool, + iterator: I) + where I: Iterator { + for item in iterator { + // TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit + // test elements with `border-radius`, for example. + if !item.base().clip.might_intersect_point(&point) { + // Clipped out. + continue + } + if !geometry::rect_contains_point(item.bounds(), point) { + // Can't possibly hit. + continue + } + if item.base().metadata.pointing.is_none() { + // `pointer-events` is `none`. Ignore this item. + continue + } + + if let DisplayItem::BorderClass(ref border) = *item { + // If the point is inside the border, it didn't hit the border! + let interior_rect = + Rect::new( + Point2D::new(border.base.bounds.origin.x + + border.border_widths.left, + border.base.bounds.origin.y + + border.border_widths.top), + Size2D::new(border.base.bounds.size.width - + (border.border_widths.left + + border.border_widths.right), + border.base.bounds.size.height - + (border.border_widths.top + + border.border_widths.bottom))); + if geometry::rect_contains_point(interior_rect, point) { + continue + } + } + + // We found a hit! + result.push(item.base().metadata); + if topmost_only { + return + } + } + } + + // Layers are positioned on top of this layer should get a shot at the hit test first. + for layer in self.layered_children.iter().rev() { + layer.stacking_context.hit_test(point, result, topmost_only); + if topmost_only && !result.is_empty() { + return + } + } + + // Iterate through display items in reverse stacking order. Steps here refer to the + // painting steps in CSS 2.1 Appendix E. + // + // Step 10: Outlines. + hit_test_in_list(point, result, topmost_only, self.outlines.iter().rev()); + if topmost_only && !result.is_empty() { + return + } + + // Steps 9 and 8: Positioned descendants with nonnegative z-indices. + for kid in self.children.iter().rev() { + if kid.z_index < 0 { + continue + } + kid.hit_test(point, result, topmost_only); + if topmost_only && !result.is_empty() { + return + } + } + + // Steps 8, 7, 5, and 4: Positioned content, content, floats, and block backgrounds and + // borders. + // + // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. + for display_list in &[ + &self.positioned_content, + &self.content, + &self.floats, + &self.block_backgrounds_and_borders, + ] { + hit_test_in_list(point, result, topmost_only, display_list.iter().rev()); + if topmost_only && !result.is_empty() { + return + } + } + + // Step 3: Positioned descendants with negative z-indices. + for kid in self.children.iter().rev() { + if kid.z_index >= 0 { + continue + } + kid.hit_test(point, result, topmost_only); + if topmost_only && !result.is_empty() { + return + } + } + + // Steps 2 and 1: Borders and background for the root. + hit_test_in_list(point, + result, + topmost_only, + self.background_and_borders.iter().rev()) + + } } #[derive(HeapSizeOf, Deserialize, Serialize)] @@ -489,128 +604,18 @@ impl StackingContext { /// the `pointer-events` CSS property If `topmost_only` is true, stops after placing one node /// into the list. `result` must be empty upon entry to this function. pub fn hit_test(&self, - mut point: Point2D, + point: Point2D, result: &mut Vec, topmost_only: bool) { - fn hit_test_in_list<'a, I>(point: Point2D, - result: &mut Vec, - topmost_only: bool, - iterator: I) - where I: Iterator { - for item in iterator { - // TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit - // test elements with `border-radius`, for example. - if !item.base().clip.might_intersect_point(&point) { - // Clipped out. - continue - } - if !geometry::rect_contains_point(item.bounds(), point) { - // Can't possibly hit. - continue - } - if item.base().metadata.pointing.is_none() { - // `pointer-events` is `none`. Ignore this item. - continue - } - match *item { - DisplayItem::BorderClass(ref border) => { - // If the point is inside the border, it didn't hit the border! - let interior_rect = - Rect::new( - Point2D::new(border.base.bounds.origin.x + - border.border_widths.left, - border.base.bounds.origin.y + - border.border_widths.top), - Size2D::new(border.base.bounds.size.width - - (border.border_widths.left + - border.border_widths.right), - border.base.bounds.size.height - - (border.border_widths.top + - border.border_widths.bottom))); - if geometry::rect_contains_point(interior_rect, point) { - continue - } - } - _ => {} - } - - // We found a hit! - result.push(item.base().metadata); - if topmost_only { - return - } - } - } - // Convert the point into stacking context local space - point = point - self.bounds.origin; + let point = point - self.bounds.origin; debug_assert!(!topmost_only || result.is_empty()); let inv_transform = self.transform.invert(); let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(), point.y.to_f32_px())); - point = Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y)); - - // Layers are positioned on top of this layer should get a shot at the hit test first. - for layer in self.display_list.layered_children.iter().rev() { - layer.stacking_context.hit_test(point, result, topmost_only); - if topmost_only && !result.is_empty() { - return - } - } - - // Iterate through display items in reverse stacking order. Steps here refer to the - // painting steps in CSS 2.1 Appendix E. - // - // Step 10: Outlines. - hit_test_in_list(point, result, topmost_only, self.display_list.outlines.iter().rev()); - if topmost_only && !result.is_empty() { - return - } - - // Steps 9 and 8: Positioned descendants with nonnegative z-indices. - for kid in self.display_list.children.iter().rev() { - if kid.z_index < 0 { - continue - } - kid.hit_test(point, result, topmost_only); - if topmost_only && !result.is_empty() { - return - } - } - - // Steps 8, 7, 5, and 4: Positioned content, content, floats, and block backgrounds and - // borders. - // - // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. - for display_list in &[ - &self.display_list.positioned_content, - &self.display_list.content, - &self.display_list.floats, - &self.display_list.block_backgrounds_and_borders, - ] { - hit_test_in_list(point, result, topmost_only, display_list.iter().rev()); - if topmost_only && !result.is_empty() { - return - } - } - - // Step 3: Positioned descendants with negative z-indices. - for kid in self.display_list.children.iter().rev() { - if kid.z_index >= 0 { - continue - } - kid.hit_test(point, result, topmost_only); - if topmost_only && !result.is_empty() { - return - } - } - - // Steps 2 and 1: Borders and background for the root. - hit_test_in_list(point, - result, - topmost_only, - self.display_list.background_and_borders.iter().rev()) + let point = Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y)); + self.display_list.hit_test(point, result, topmost_only) } pub fn print(&self, title: String) {