[chromium] Convert screen space scroll gestures to layer space
[WebKit-https.git] / Source / WebCore / platform / graphics / chromium / cc / CCLayerTreeHostImpl.cpp
index a5138ea..1bccf7c 100644 (file)
@@ -35,6 +35,7 @@
 #include "CCLayerIterator.h"
 #include "CCLayerTreeHost.h"
 #include "CCLayerTreeHostCommon.h"
+#include "CCMathUtil.h"
 #include "CCOverdrawMetrics.h"
 #include "CCPageScaleAnimation.h"
 #include "CCPrioritizedTextureManager.h"
@@ -119,6 +120,7 @@ CCLayerTreeHostImpl::CCLayerTreeHostImpl(const CCLayerTreeSettings& settings, CC
     , m_rootScrollLayerImpl(0)
     , m_currentlyScrollingLayerImpl(0)
     , m_scrollingLayerIdFromPreviousTree(-1)
+    , m_scrollDeltaIsInScreenSpace(false)
     , m_settings(settings)
     , m_deviceScaleFactor(1)
     , m_visible(true)
@@ -888,36 +890,95 @@ CCInputHandlerClient::ScrollStatus CCLayerTreeHostImpl::scrollBegin(const IntPoi
 
     if (potentiallyScrollingLayerImpl) {
         m_currentlyScrollingLayerImpl = potentiallyScrollingLayerImpl;
+        // Gesture events need to be transformed from screen coordinates to local layer coordinates
+        // so that the scrolling contents exactly follow the user's finger. In contrast, wheel
+        // events are already in local layer coordinates so we can just apply them directly.
+        m_scrollDeltaIsInScreenSpace = (type == Gesture);
         return ScrollStarted;
     }
     return ScrollIgnored;
 }
 
-void CCLayerTreeHostImpl::scrollBy(const IntSize& scrollDelta)
+static FloatSize scrollLayerWithScreenSpaceDelta(CCLayerImpl& layerImpl, const FloatPoint& screenSpacePoint, const FloatSize& screenSpaceDelta)
+{
+    // Layers with non-invertible screen space transforms should not have passed the scroll hit
+    // test in the first place.
+    ASSERT(layerImpl.screenSpaceTransform().isInvertible());
+    WebTransformationMatrix inverseScreenSpaceTransform = layerImpl.screenSpaceTransform().inverse();
+
+    // First project the scroll start and end points to local layer space to find the scroll delta
+    // in layer coordinates.
+    bool startClipped, endClipped;
+    FloatPoint screenSpaceEndPoint = screenSpacePoint + screenSpaceDelta;
+    FloatPoint localStartPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpacePoint, startClipped);
+    FloatPoint localEndPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpaceEndPoint, endClipped);
+
+    // In general scroll point coordinates should not get clipped.
+    ASSERT(!startClipped);
+    ASSERT(!endClipped);
+    if (startClipped || endClipped)
+        return FloatSize();
+
+    // Apply the scroll delta.
+    FloatSize previousDelta(layerImpl.scrollDelta());
+    layerImpl.scrollBy(localEndPoint - localStartPoint);
+
+    // Calculate the applied scroll delta in screen space coordinates.
+    FloatPoint actualLocalEndPoint = localStartPoint + layerImpl.scrollDelta() - previousDelta;
+    FloatPoint actualScreenSpaceEndPoint = CCMathUtil::mapPoint(layerImpl.screenSpaceTransform(), actualLocalEndPoint, endClipped);
+    ASSERT(!endClipped);
+    if (endClipped)
+        return FloatSize();
+    return actualScreenSpaceEndPoint - screenSpacePoint;
+}
+
+static FloatSize scrollLayerWithLocalDelta(CCLayerImpl& layerImpl, const FloatSize& localDelta)
+{
+    FloatSize previousDelta(layerImpl.scrollDelta());
+    layerImpl.scrollBy(localDelta);
+    return layerImpl.scrollDelta() - previousDelta;
+}
+
+void CCLayerTreeHostImpl::scrollBy(const IntPoint& viewportPoint, const IntSize& scrollDelta)
 {
     TRACE_EVENT0("cc", "CCLayerTreeHostImpl::scrollBy");
     if (!m_currentlyScrollingLayerImpl)
         return;
 
     FloatSize pendingDelta(scrollDelta);
-    pendingDelta.scale(1 / m_pageScaleDelta);
-
-    for (CCLayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl && !pendingDelta.isZero(); layerImpl = layerImpl->parent()) {
+    for (CCLayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl; layerImpl = layerImpl->parent()) {
         if (!layerImpl->scrollable())
             continue;
-        FloatSize previousDelta(layerImpl->scrollDelta());
-        layerImpl->scrollBy(pendingDelta);
-        // Reset the pending scroll delta to zero if the layer was able to move along the requested
-        // axis. This is to ensure it is possible to scroll exactly to the beginning or end of a
-        // scroll area regardless of the scroll step. For diagonal scrolls this also avoids applying
-        // the scroll on one axis to multiple layers.
-        if (previousDelta.width() != layerImpl->scrollDelta().width())
-            pendingDelta.setWidth(0);
-        if (previousDelta.height() != layerImpl->scrollDelta().height())
-            pendingDelta.setHeight(0);
+
+        FloatSize appliedDelta;
+        if (m_scrollDeltaIsInScreenSpace)
+            appliedDelta = scrollLayerWithScreenSpaceDelta(*layerImpl, viewportPoint, pendingDelta);
+        else
+            appliedDelta = scrollLayerWithLocalDelta(*layerImpl, pendingDelta);
+
+        // If the layer wasn't able to move, try the next one in the hierarchy.
+        float moveThresholdSquared = 0.1 * 0.1;
+        if (appliedDelta.diagonalLengthSquared() < moveThresholdSquared)
+            continue;
+
+        // If the applied delta is within 45 degrees of the input delta, bail out to make it easier
+        // to scroll just one layer in one direction without affecting any of its parents.
+        float angleThreshold = 45;
+        if (CCMathUtil::smallestAngleBetweenVectors(appliedDelta, pendingDelta) < angleThreshold) {
+            pendingDelta = FloatSize();
+            break;
+        }
+
+        // Allow further movement only on an axis perpendicular to the direction in which the layer
+        // moved.
+        FloatSize perpendicularAxis(-appliedDelta.height(), appliedDelta.width());
+        pendingDelta = CCMathUtil::projectVector(pendingDelta, perpendicularAxis);
+
+        if (flooredIntSize(pendingDelta).isZero())
+            break;
     }
 
-    if (!scrollDelta.isZero() && pendingDelta.isEmpty()) {
+    if (!scrollDelta.isZero() && flooredIntSize(pendingDelta).isEmpty()) {
         m_client->setNeedsCommitOnImplThread();
         m_client->setNeedsRedrawOnImplThread();
     }