Index: cc/trees/layer_tree_impl.cc |
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc |
index 97e9c1754c9a18eb8f9740101b72ad3849675808..a4d5cab9f94bf866f23e8d35e6fcfa9b107eb0ee 100644 |
--- a/cc/trees/layer_tree_impl.cc |
+++ b/cc/trees/layer_tree_impl.cc |
@@ -4,6 +4,9 @@ |
#include "cc/trees/layer_tree_impl.h" |
+#include <limits> |
+#include <set> |
+ |
#include "base/debug/trace_event.h" |
#include "cc/animation/keyframed_animation_curve.h" |
#include "cc/animation/scrollbar_animation_controller.h" |
@@ -20,6 +23,7 @@ |
#include "cc/resources/ui_resource_request.h" |
#include "cc/trees/layer_tree_host_common.h" |
#include "cc/trees/layer_tree_host_impl.h" |
+#include "ui/gfx/point_conversions.h" |
#include "ui/gfx/size_conversions.h" |
#include "ui/gfx/vector2d_conversions.h" |
@@ -1022,4 +1026,262 @@ void LayerTreeImpl::ReleaseResourcesRecursive(LayerImpl* current) { |
ReleaseResourcesRecursive(current->children()[i]); |
} |
+template <typename LayerType> |
+static inline bool LayerClipsSubtree(LayerType* layer) { |
+ return layer->masks_to_bounds() || layer->mask_layer(); |
+} |
+ |
+static bool PointHitsRect( |
+ const gfx::PointF& screen_space_point, |
+ const gfx::Transform& local_space_to_screen_space_transform, |
+ const gfx::RectF& local_space_rect, |
+ float* distance_to_camera) { |
+ // If the transform is not invertible, then assume that this point doesn't hit |
+ // this rect. |
+ gfx::Transform inverse_local_space_to_screen_space( |
+ gfx::Transform::kSkipInitialization); |
+ if (!local_space_to_screen_space_transform.GetInverse( |
+ &inverse_local_space_to_screen_space)) |
+ return false; |
+ |
+ // Transform the hit test point from screen space to the local space of the |
+ // given rect. |
+ bool clipped = false; |
+ gfx::Point3F planar_point = MathUtil::ProjectPoint3D( |
+ inverse_local_space_to_screen_space, screen_space_point, &clipped); |
+ gfx::PointF hit_test_point_in_local_space = |
+ gfx::PointF(planar_point.x(), planar_point.y()); |
+ |
+ // If ProjectPoint could not project to a valid value, then we assume that |
+ // this point doesn't hit this rect. |
+ if (clipped) |
+ return false; |
+ |
+ if (!local_space_rect.Contains(hit_test_point_in_local_space)) |
+ return false; |
+ |
+ if (distance_to_camera) { |
+ // To compute the distance to the camera, we have to take the planar point |
+ // and pull it back to world space and compute the displacement along the |
+ // z-axis. |
+ gfx::Point3F planar_point_in_screen_space(planar_point); |
+ local_space_to_screen_space_transform.TransformPoint( |
+ &planar_point_in_screen_space); |
+ *distance_to_camera = planar_point_in_screen_space.z(); |
+ } |
+ |
+ return true; |
+} |
+ |
+static bool PointHitsRegion(const gfx::PointF& screen_space_point, |
+ const gfx::Transform& screen_space_transform, |
+ const Region& layer_space_region, |
+ float layer_content_scale_x, |
+ float layer_content_scale_y) { |
+ // If the transform is not invertible, then assume that this point doesn't hit |
+ // this region. |
+ gfx::Transform inverse_screen_space_transform( |
+ gfx::Transform::kSkipInitialization); |
+ if (!screen_space_transform.GetInverse(&inverse_screen_space_transform)) |
+ return false; |
+ |
+ // Transform the hit test point from screen space to the local space of the |
+ // given region. |
+ bool clipped = false; |
+ gfx::PointF hit_test_point_in_content_space = MathUtil::ProjectPoint( |
+ inverse_screen_space_transform, screen_space_point, &clipped); |
+ gfx::PointF hit_test_point_in_layer_space = |
+ gfx::ScalePoint(hit_test_point_in_content_space, |
+ 1.f / layer_content_scale_x, |
+ 1.f / layer_content_scale_y); |
+ |
+ // If ProjectPoint could not project to a valid value, then we assume that |
+ // this point doesn't hit this region. |
+ if (clipped) |
+ return false; |
+ |
+ return layer_space_region.Contains( |
+ gfx::ToRoundedPoint(hit_test_point_in_layer_space)); |
+} |
+ |
+static bool PointIsClippedBySurfaceOrClipRect( |
+ const gfx::PointF& screen_space_point, |
+ LayerImpl* layer) { |
+ LayerImpl* current_layer = layer; |
+ |
+ // Walk up the layer tree and hit-test any render_surfaces and any layer |
+ // clip rects that are active. |
+ while (current_layer) { |
+ if (current_layer->render_surface() && |
+ !PointHitsRect( |
+ screen_space_point, |
+ current_layer->render_surface()->screen_space_transform(), |
+ current_layer->render_surface()->content_rect(), |
+ NULL)) |
+ return true; |
+ |
+ // Note that drawable content rects are actually in target surface space, so |
+ // the transform we have to provide is the target surface's |
+ // screen_space_transform. |
+ LayerImpl* render_target = current_layer->render_target(); |
+ if (LayerClipsSubtree(current_layer) && |
+ !PointHitsRect( |
+ screen_space_point, |
+ render_target->render_surface()->screen_space_transform(), |
+ current_layer->drawable_content_rect(), |
+ NULL)) |
+ return true; |
+ |
+ current_layer = current_layer->parent(); |
+ } |
+ |
+ // If we have finished walking all ancestors without having already exited, |
+ // then the point is not clipped by any ancestors. |
+ return false; |
+} |
+ |
+static bool PointHitsLayer(LayerImpl* layer, |
+ const gfx::PointF& screen_space_point, |
+ float* distance_to_intersection) { |
+ gfx::RectF content_rect(layer->content_bounds()); |
+ if (!PointHitsRect(screen_space_point, |
+ layer->screen_space_transform(), |
+ content_rect, |
+ distance_to_intersection)) |
+ return false; |
+ |
+ // At this point, we think the point does hit the layer, but we need to walk |
+ // up the parents to ensure that the layer was not clipped in such a way |
+ // that the hit point actually should not hit the layer. |
+ if (PointIsClippedBySurfaceOrClipRect(screen_space_point, layer)) |
+ return false; |
+ |
+ // Skip the HUD layer. |
+ if (layer == layer->layer_tree_impl()->hud_layer()) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+struct FindClosestMatchingLayerDataForRecursion { |
+ FindClosestMatchingLayerDataForRecursion() |
+ : closest_match(NULL), |
+ closest_distance(-std::numeric_limits<float>::infinity()) {} |
+ LayerImpl* closest_match; |
+ // Note that the positive z-axis points towards the camera, so bigger means |
+ // closer in this case, counterintuitively. |
+ float closest_distance; |
+}; |
+ |
+template <typename Functor> |
+static void FindClosestMatchingLayer( |
+ const gfx::PointF& screen_space_point, |
+ LayerImpl* layer, |
+ const Functor& func, |
+ FindClosestMatchingLayerDataForRecursion* data_for_recursion) { |
+ for (int i = layer->children().size() - 1; i >= 0; --i) { |
+ FindClosestMatchingLayer( |
+ screen_space_point, layer->children()[i], func, data_for_recursion); |
+ } |
+ |
+ float distance_to_intersection = 0.f; |
+ if (func(layer) && |
+ PointHitsLayer(layer, screen_space_point, &distance_to_intersection) && |
+ ((!data_for_recursion->closest_match || |
+ distance_to_intersection > data_for_recursion->closest_distance))) { |
+ data_for_recursion->closest_distance = distance_to_intersection; |
+ data_for_recursion->closest_match = layer; |
+ } |
+} |
+ |
+static bool ScrollsAnyDrawnRenderSurfaceLayerListMember(LayerImpl* layer) { |
+ if (!layer->scrollable()) |
+ return false; |
+ if (layer->IsDrawnRenderSurfaceLayerListMember()) |
+ return true; |
+ if (!layer->scroll_children()) |
+ return false; |
+ for (std::set<LayerImpl*>::const_iterator it = |
+ layer->scroll_children()->begin(); |
+ it != layer->scroll_children()->end(); |
+ ++it) { |
+ if ((*it)->IsDrawnRenderSurfaceLayerListMember()) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+struct FindScrollingLayerFunctor { |
+ bool operator()(LayerImpl* layer) const { |
+ return ScrollsAnyDrawnRenderSurfaceLayerListMember(layer); |
+ } |
+}; |
+ |
+LayerImpl* LayerTreeImpl::FindFirstScrollingLayerThatIsHitByPoint( |
+ const gfx::PointF& screen_space_point) { |
+ FindClosestMatchingLayerDataForRecursion data_for_recursion; |
+ FindClosestMatchingLayer(screen_space_point, |
+ root_layer(), |
+ FindScrollingLayerFunctor(), |
+ &data_for_recursion); |
+ return data_for_recursion.closest_match; |
+} |
+ |
+struct HitTestVisibleScrollableOrTouchableFunctor { |
+ bool operator()(LayerImpl* layer) const { |
+ return layer->IsDrawnRenderSurfaceLayerListMember() || |
+ ScrollsAnyDrawnRenderSurfaceLayerListMember(layer) || |
+ !layer->touch_event_handler_region().IsEmpty() || |
+ layer->have_wheel_event_handlers(); |
+ } |
+}; |
+ |
+LayerImpl* LayerTreeImpl::FindLayerThatIsHitByPoint( |
+ const gfx::PointF& screen_space_point) { |
+ FindClosestMatchingLayerDataForRecursion data_for_recursion; |
+ FindClosestMatchingLayer(screen_space_point, |
+ root_layer(), |
+ HitTestVisibleScrollableOrTouchableFunctor(), |
+ &data_for_recursion); |
+ return data_for_recursion.closest_match; |
+} |
+ |
+static bool LayerHasTouchEventHandlersAt(const gfx::PointF& screen_space_point, |
+ LayerImpl* layer_impl) { |
+ if (layer_impl->touch_event_handler_region().IsEmpty()) |
+ return false; |
+ |
+ if (!PointHitsRegion(screen_space_point, |
+ layer_impl->screen_space_transform(), |
+ layer_impl->touch_event_handler_region(), |
+ layer_impl->contents_scale_x(), |
+ layer_impl->contents_scale_y())) |
+ return false; |
+ |
+ // At this point, we think the point does hit the touch event handler region |
+ // on the layer, but we need to walk up the parents to ensure that the layer |
+ // was not clipped in such a way that the hit point actually should not hit |
+ // the layer. |
+ if (PointIsClippedBySurfaceOrClipRect(screen_space_point, layer_impl)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+struct FindTouchEventLayerFunctor { |
+ bool operator()(LayerImpl* layer) const { |
+ return LayerHasTouchEventHandlersAt(screen_space_point, layer); |
+ } |
+ const gfx::PointF screen_space_point; |
+}; |
+ |
+LayerImpl* LayerTreeImpl::FindLayerThatIsHitByPointInTouchHandlerRegion( |
+ const gfx::PointF& screen_space_point) { |
+ FindTouchEventLayerFunctor func = {screen_space_point}; |
+ FindClosestMatchingLayerDataForRecursion data_for_recursion; |
+ FindClosestMatchingLayer( |
+ screen_space_point, root_layer(), func, &data_for_recursion); |
+ return data_for_recursion.closest_match; |
+} |
+ |
} // namespace cc |