diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-09-10 19:10:20 +0200 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-09-10 19:10:20 +0200 |
commit | 284837daa07b29d6a63a748544a90b1f5842ac5c (patch) | |
tree | ecd258180bde91fe741e0cfd2638beb3c6da7e8e /Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp | |
parent | 2e2ba8ff45915f40ed3e014101269c175f2a89a0 (diff) | |
download | qtwebkit-284837daa07b29d6a63a748544a90b1f5842ac5c.tar.gz |
Imported WebKit commit 68645295d2e3e09af2c942f092556f06aa5f8b0d (http://svn.webkit.org/repository/webkit/trunk@128073)
New snapshot
Diffstat (limited to 'Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp')
-rw-r--r-- | Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp b/Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp new file mode 100644 index 000000000..3d134ea93 --- /dev/null +++ b/Source/WebKit2/UIProcess/qt/PageViewportControllerClientQt.cpp @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2011, 2012 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + +#include "config.h" +#include "PageViewportControllerClientQt.h" + +#include "qquickwebpage_p.h" +#include "qquickwebview_p.h" +#include "qwebkittest_p.h" +#include <QPointF> +#include <QTransform> +#include <QtQuick/qquickitem.h> +#include <WebCore/FloatRect.h> +#include <WebCore/FloatSize.h> + +namespace WebKit { + +static const int kScaleAnimationDurationMillis = 250; + +PageViewportControllerClientQt::PageViewportControllerClientQt(QQuickWebView* viewportItem, QQuickWebPage* pageItem) + : m_viewportItem(viewportItem) + , m_pageItem(pageItem) + , m_scaleAnimation(new ScaleAnimation(this)) + , m_pinchStartScale(-1) + , m_lastCommittedScale(-1) + , m_zoomOutScale(0) + , m_ignoreViewportChanges(true) +{ + m_scaleAnimation->setDuration(kScaleAnimationDurationMillis); + m_scaleAnimation->setEasingCurve(QEasingCurve::OutCubic); + + connect(m_viewportItem, SIGNAL(movementStarted()), SLOT(flickMoveStarted()), Qt::DirectConnection); + connect(m_viewportItem, SIGNAL(movementEnded()), SLOT(flickMoveEnded()), Qt::DirectConnection); + connect(m_viewportItem, SIGNAL(contentXChanged()), SLOT(pageItemPositionChanged())); + connect(m_viewportItem, SIGNAL(contentYChanged()), SLOT(pageItemPositionChanged())); + + + connect(m_scaleAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), + SLOT(scaleAnimationStateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); +} + +void PageViewportControllerClientQt::ScaleAnimation::updateCurrentValue(const QVariant& value) +{ + // Resetting the end value, the easing curve or the duration of the scale animation + // triggers a recalculation of the animation interval. This might change the current + // value of the animated property. + // Make sure we only act on animation value changes if the animation is active. + if (!m_controllerClient->scaleAnimationActive()) + return; + + QRectF itemRect = value.toRectF(); + float itemScale = m_controllerClient->viewportScaleForRect(itemRect); + + m_controllerClient->setContentRectVisiblePositionAtScale(itemRect.topLeft(), itemScale); +} + +PageViewportControllerClientQt::~PageViewportControllerClientQt() +{ +} + +void PageViewportControllerClientQt::setContentRectVisiblePositionAtScale(const QPointF& location, qreal itemScale) +{ + ASSERT(itemScale >= 0); + + scaleContent(itemScale); + + // To animate the position together with the scale we multiply the position with the current scale + // and add it to the page position (displacement on the flickable contentItem because of additional items). + QPointF newPosition(m_pageItem->pos() + location * itemScale); + + m_viewportItem->setContentPos(newPosition); +} + +void PageViewportControllerClientQt::animateContentRectVisible(const QRectF& contentRect) +{ + ASSERT(m_scaleAnimation->state() == QAbstractAnimation::Stopped); + + ASSERT(!scrollAnimationActive()); + if (scrollAnimationActive()) + return; + + QRectF viewportRectInContentCoords = m_viewportItem->mapRectToWebContent(m_viewportItem->boundingRect()); + if (contentRect == viewportRectInContentCoords) { + updateViewportController(); + return; + } + + // Since we have to animate scale and position at the same time the scale animation interpolates + // from the current viewport rect in content coordinates to a visible rect of the content. + m_scaleAnimation->setStartValue(viewportRectInContentCoords); + m_scaleAnimation->setEndValue(contentRect); + + // Inform the web process about the requested visible content rect + // if zooming-out so that no white tiles are exposed during animation. + if (viewportRectInContentCoords.width() / contentRect.width() < m_pageItem->contentsScale()) + m_controller->setVisibleContentsRect(contentRect, viewportScaleForRect(contentRect)); + + m_scaleAnimation->start(); +} + +void PageViewportControllerClientQt::flickMoveStarted() +{ + Q_ASSERT(m_viewportItem->isMoving()); + m_scrollUpdateDeferrer.reset(new ViewportUpdateDeferrer(m_controller, ViewportUpdateDeferrer::DeferUpdateAndSuspendContent)); + + m_lastScrollPosition = m_viewportItem->contentPos(); + + m_ignoreViewportChanges = false; +} + +void PageViewportControllerClientQt::flickMoveEnded() +{ + Q_ASSERT(!m_viewportItem->isMoving()); + // This method is called on the end of the pan or pan kinetic animation. + + m_ignoreViewportChanges = true; + + m_scrollUpdateDeferrer.reset(); +} + +void PageViewportControllerClientQt::pageItemPositionChanged() +{ + if (m_ignoreViewportChanges) + return; + + QPointF newPosition = m_viewportItem->contentPos(); + + updateViewportController(m_lastScrollPosition - newPosition); + + m_lastScrollPosition = newPosition; +} + +void PageViewportControllerClientQt::scaleAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State /*oldState*/) +{ + switch (newState) { + case QAbstractAnimation::Running: + m_viewportItem->cancelFlick(); + ASSERT(!m_animationUpdateDeferrer); + m_animationUpdateDeferrer.reset(new ViewportUpdateDeferrer(m_controller, ViewportUpdateDeferrer::DeferUpdateAndSuspendContent)); + break; + case QAbstractAnimation::Stopped: + m_animationUpdateDeferrer.reset(); + break; + default: + break; + } +} + +void PageViewportControllerClientQt::touchBegin() +{ + m_controller->setHadUserInteraction(true); + + // Prevents resuming the page between the user's flicks of the page while the animation is running. + if (scrollAnimationActive()) + m_touchUpdateDeferrer.reset(new ViewportUpdateDeferrer(m_controller, ViewportUpdateDeferrer::DeferUpdateAndSuspendContent)); +} + +void PageViewportControllerClientQt::touchEnd() +{ + m_touchUpdateDeferrer.reset(); +} + +void PageViewportControllerClientQt::focusEditableArea(const QRectF& caretArea, const QRectF& targetArea) +{ + // This can only happen as a result of a user interaction. + ASSERT(m_controller->hadUserInteraction()); + + QRectF endArea = m_controller->convertToViewport(targetArea); + + qreal endItemScale = m_controller->convertToViewport(m_controller->innerBoundedContentsScale(2.0)); + const QRectF viewportRect = m_viewportItem->boundingRect(); + + qreal x; + const qreal borderOffset = 10; + if ((endArea.width() + borderOffset) * endItemScale <= viewportRect.width()) { + // Center the input field in the middle of the view, if it is smaller than + // the view at the scale target. + x = viewportRect.center().x() - endArea.width() * endItemScale / 2.0; + } else { + // Ensure that the caret always has borderOffset contents pixels to the right + // of it, and secondarily (if possible), that the area has borderOffset + // contents pixels to the left of it. + qreal caretOffset = m_controller->convertToViewport(caretArea.x()) - endArea.x(); + x = qMin(viewportRect.width() - (caretOffset + borderOffset) * endItemScale, borderOffset * endItemScale); + } + + const QPointF hotspot = QPointF(endArea.x(), endArea.center().y()); + const QPointF viewportHotspot = QPointF(x, /* FIXME: visibleCenter */ viewportRect.center().y()); + + QPointF endPosition = hotspot * endItemScale - viewportHotspot; + QRectF endPosRange = m_controller->positionRangeForContentAtScale(endItemScale); + + endPosition = boundPosition(endPosRange.topLeft(), endPosition, endPosRange.bottomRight()); + + QRectF endVisibleContentRect(endPosition / endItemScale, viewportRect.size() / endItemScale); + + animateContentRectVisible(endVisibleContentRect); +} + +void PageViewportControllerClientQt::zoomToAreaGestureEnded(const QPointF& touchPoint, const QRectF& targetArea) +{ + // This can only happen as a result of a user interaction. + ASSERT(m_controller->hadUserInteraction()); + + if (!targetArea.isValid()) + return; + + if (m_controller->hasSuspendedContent()) + return; + + const int margin = 10; // We want at least a little bit of margin. + QRectF endArea = m_controller->convertToViewport(targetArea.adjusted(-margin, -margin, margin, margin)); + + const QRectF viewportRect = m_viewportItem->boundingRect(); + + qreal targetCSSScale = viewportRect.size().width() / endArea.size().width(); + qreal endCSSScale = m_controller->innerBoundedContentsScale(qMin(targetCSSScale, qreal(2.5))); + qreal endItemScale = m_controller->convertToViewport(endCSSScale); + qreal currentScale = m_pageItem->contentsScale(); + + // We want to end up with the target area filling the whole width of the viewport (if possible), + // and centralized vertically where the user requested zoom. Thus our hotspot is the center of + // the targetArea x-wise and the requested zoom position, y-wise. + const QPointF hotspot = QPointF(endArea.center().x(), m_controller->convertToViewport(touchPoint.y())); + const QPointF viewportHotspot = viewportRect.center(); + + QPointF endPosition = hotspot * endCSSScale - viewportHotspot; + + QRectF endPosRange = m_controller->positionRangeForContentAtScale(endItemScale); + endPosition = boundPosition(endPosRange.topLeft(), endPosition, endPosRange.bottomRight()); + + QRectF endVisibleContentRect(endPosition / endItemScale, viewportRect.size() / endItemScale); + + enum { ZoomIn, ZoomBack, ZoomOut, NoZoom } zoomAction = ZoomIn; + + if (!m_scaleStack.isEmpty()) { + // Zoom back out if attempting to scale to the same current scale, or + // attempting to continue scaling out from the inner most level. + // Use fuzzy compare with a fixed error to be able to deal with largish differences due to pixel rounding. + if (fuzzyCompare(endItemScale, currentScale, 0.01)) { + // If moving the viewport would expose more of the targetRect and move at least 40 pixels, update position but do not scale out. + QRectF currentContentRect(visibleContentsRect()); + QRectF targetIntersection = endVisibleContentRect.intersected(targetArea); + if (!currentContentRect.contains(targetIntersection) + && (qAbs(endVisibleContentRect.top() - currentContentRect.top()) >= 40 + || qAbs(endVisibleContentRect.left() - currentContentRect.left()) >= 40)) + zoomAction = NoZoom; + else + zoomAction = ZoomBack; + } else if (fuzzyCompare(endItemScale, m_zoomOutScale, 0.01)) + zoomAction = ZoomBack; + else if (endItemScale < currentScale) + zoomAction = ZoomOut; + } + + switch (zoomAction) { + case ZoomIn: + m_scaleStack.append(ScaleStackItem(currentScale, m_viewportItem->contentPos().x())); + m_zoomOutScale = endItemScale; + break; + case ZoomBack: { + ScaleStackItem lastScale = m_scaleStack.takeLast(); + endItemScale = lastScale.scale; + endCSSScale = m_controller->convertFromViewport(lastScale.scale); + // Recalculate endPosition and bound it according to new scale. + endPosition.setY(hotspot.y() * endCSSScale - viewportHotspot.y()); + endPosition.setX(lastScale.xPosition); + endPosRange = m_controller->positionRangeForContentAtScale(endItemScale); + endPosition = boundPosition(endPosRange.topLeft(), endPosition, endPosRange.bottomRight()); + endVisibleContentRect = QRectF(endPosition / endItemScale, viewportRect.size() / endItemScale); + break; + } + case ZoomOut: + // Unstack all scale-levels deeper than the new level, so a zoom-back won't end up zooming in. + while (!m_scaleStack.isEmpty() && m_scaleStack.last().scale >= endItemScale) + m_scaleStack.removeLast(); + m_zoomOutScale = endItemScale; + break; + case NoZoom: + break; + } + + animateContentRectVisible(endVisibleContentRect); +} + +QRectF PageViewportControllerClientQt::nearestValidVisibleContentsRect() const +{ + float cssScale = m_controller->convertFromViewport(m_pageItem->contentsScale()); + float endItemScale = m_controller->convertToViewport(m_controller->innerBoundedContentsScale(cssScale)); + + const QRectF viewportRect = m_viewportItem->boundingRect(); + QPointF viewportHotspot = viewportRect.center(); + QPointF endPosition = m_viewportItem->mapToWebContent(viewportHotspot) * endItemScale - viewportHotspot; + + FloatRect endPosRange = m_controller->positionRangeForContentAtScale(endItemScale); + endPosition = boundPosition(endPosRange.minXMinYCorner(), endPosition, endPosRange.maxXMaxYCorner()); + + QRectF endVisibleContentRect(endPosition / endItemScale, viewportRect.size() / endItemScale); + + return endVisibleContentRect; +} + +void PageViewportControllerClientQt::setContentsPosition(const FloatPoint& localPoint) +{ + QPointF newPosition(m_pageItem->pos() + QPointF(localPoint)); + m_viewportItem->setContentPos(newPosition); + updateViewportController(); +} + +void PageViewportControllerClientQt::setContentsScale(float localScale, bool treatAsInitialValue) +{ + if (treatAsInitialValue) { + m_zoomOutScale = 0; + m_scaleStack.clear(); + setContentRectVisiblePositionAtScale(QPointF(), localScale); + } else + scaleContent(localScale); + + updateViewportController(); +} + +void PageViewportControllerClientQt::setContentsRectToNearestValidBounds() +{ + ViewportUpdateDeferrer guard(m_controller); + float validCSSScale = m_controller->innerBoundedContentsScale(m_controller->convertFromViewport(m_pageItem->contentsScale())); + setContentRectVisiblePositionAtScale(nearestValidVisibleContentsRect().topLeft(), m_controller->convertToViewport(validCSSScale)); +} + +void PageViewportControllerClientQt::didResumeContent() +{ + updateViewportController(); +} + +bool PageViewportControllerClientQt::scrollAnimationActive() const +{ + return m_viewportItem->isFlicking(); +} + +bool PageViewportControllerClientQt::panGestureActive() const +{ + return m_controller->hadUserInteraction() && m_viewportItem->isDragging(); +} + +void PageViewportControllerClientQt::panGestureStarted(const QPointF& position, qint64 eventTimestampMillis) +{ + // This can only happen as a result of a user interaction. + ASSERT(m_controller->hadUserInteraction()); + + m_viewportItem->handleFlickableMousePress(position, eventTimestampMillis); + m_lastPinchCenterInViewportCoordinates = position; +} + +void PageViewportControllerClientQt::panGestureRequestUpdate(const QPointF& position, qint64 eventTimestampMillis) +{ + m_viewportItem->handleFlickableMouseMove(position, eventTimestampMillis); + m_lastPinchCenterInViewportCoordinates = position; +} + +void PageViewportControllerClientQt::panGestureEnded(const QPointF& position, qint64 eventTimestampMillis) +{ + m_viewportItem->handleFlickableMouseRelease(position, eventTimestampMillis); + m_lastPinchCenterInViewportCoordinates = position; +} + +void PageViewportControllerClientQt::panGestureCancelled() +{ + // Reset the velocity samples of the flickable. + // This should only be called by the recognizer if we have a recognized + // pan gesture and receive a touch event with multiple touch points + // (ie. transition to a pinch gesture) as it does not move the content + // back inside valid bounds. + // When the pinch gesture ends, the content is positioned and scaled + // back to valid boundaries. + m_viewportItem->cancelFlick(); +} + +bool PageViewportControllerClientQt::scaleAnimationActive() const +{ + return m_scaleAnimation->state() == QAbstractAnimation::Running; +} + +void PageViewportControllerClientQt::cancelScrollAnimation() +{ + if (!scrollAnimationActive()) + return; + + // If the pan gesture recognizer receives a touch begin event + // during an ongoing kinetic scroll animation of a previous + // pan gesture, the animation is stopped and the content is + // immediately positioned back to valid boundaries. + + m_viewportItem->cancelFlick(); + setContentsRectToNearestValidBounds(); +} + +void PageViewportControllerClientQt::interruptScaleAnimation() +{ + // This interrupts the scale animation exactly where it is, even if it is out of bounds. + m_scaleAnimation->stop(); +} + +bool PageViewportControllerClientQt::pinchGestureActive() const +{ + return m_controller->hadUserInteraction() && (m_pinchStartScale > 0); +} + +void PageViewportControllerClientQt::pinchGestureStarted(const QPointF& pinchCenterInViewportCoordinates) +{ + // This can only happen as a result of a user interaction. + ASSERT(m_controller->hadUserInteraction()); + + if (!m_controller->allowsUserScaling()) + return; + + m_scaleStack.clear(); + m_zoomOutScale = 0.0; + + m_scaleUpdateDeferrer.reset(new ViewportUpdateDeferrer(m_controller, ViewportUpdateDeferrer::DeferUpdateAndSuspendContent)); + + m_lastPinchCenterInViewportCoordinates = pinchCenterInViewportCoordinates; + m_pinchStartScale = m_pageItem->contentsScale(); +} + +void PageViewportControllerClientQt::pinchGestureRequestUpdate(const QPointF& pinchCenterInViewportCoordinates, qreal totalScaleFactor) +{ + ASSERT(m_controller->hasSuspendedContent()); + + if (!m_controller->allowsUserScaling()) + return; + + // Changes of the center position should move the page even if the zoom factor does not change. + const qreal cssScale = m_controller->convertFromViewport(m_pinchStartScale * totalScaleFactor); + + // Allow zooming out beyond mimimum scale on pages that do not explicitly disallow it. + const qreal targetItemScale = m_controller->convertToViewport(m_controller->outerBoundedContentsScale(cssScale)); + + scaleContent(targetItemScale, m_viewportItem->mapToWebContent(pinchCenterInViewportCoordinates)); + + const QPointF positionDiff = pinchCenterInViewportCoordinates - m_lastPinchCenterInViewportCoordinates; + m_lastPinchCenterInViewportCoordinates = pinchCenterInViewportCoordinates; + + m_viewportItem->setContentPos(m_viewportItem->contentPos() - positionDiff); + + // Inform the web process to render the currently visible area with low-resolution tiles not + // to expose white tiles during pinch gestures and to show fixed position layers correctly. + // The actual scale is restored after the pinch gesture ends. + updateViewportController(QPointF(), 1); +} + +void PageViewportControllerClientQt::pinchGestureEnded() +{ + ASSERT(m_controller->hasSuspendedContent()); + + if (!m_controller->allowsUserScaling()) + return; + + m_pinchStartScale = -1; + + animateContentRectVisible(nearestValidVisibleContentsRect()); + m_scaleUpdateDeferrer.reset(); // Clear after starting potential animation, which takes over deferring. +} + +void PageViewportControllerClientQt::pinchGestureCancelled() +{ + m_pinchStartScale = -1; + m_scaleUpdateDeferrer.reset(); +} + +QRectF PageViewportControllerClientQt::visibleContentsRect() const +{ + const QRectF visibleRect(m_viewportItem->boundingRect().intersected(m_pageItem->boundingRect())); + return m_viewportItem->mapRectToWebContent(visibleRect); +} + +void PageViewportControllerClientQt::didChangeContentsSize() +{ + // Emit for testing purposes, so that it can be verified that + // we didn't do scale adjustment. + emit m_viewportItem->experimental()->test()->contentsScaleCommitted(); + + if (!m_controller->hasSuspendedContent()) + setContentsRectToNearestValidBounds(); +} + +void PageViewportControllerClientQt::didChangeVisibleContents() +{ + qreal scale = m_pageItem->contentsScale(); + + if (scale != m_lastCommittedScale) + emit m_viewportItem->experimental()->test()->contentsScaleCommitted(); + m_lastCommittedScale = scale; + + // Ensure that updatePaintNode is always called before painting. + m_pageItem->update(); +} + +void PageViewportControllerClientQt::didChangeViewportAttributes() +{ + // Make sure we apply the new initial scale when deferring ends. + ViewportUpdateDeferrer guard(m_controller); + + emit m_viewportItem->experimental()->test()->devicePixelRatioChanged(); + emit m_viewportItem->experimental()->test()->viewportChanged(); +} + +void PageViewportControllerClientQt::updateViewportController(const QPointF& trajectory, qreal scale) +{ + FloatRect currentVisibleRect(visibleContentsRect()); + float viewportScale = (scale < 0) ? viewportScaleForRect(currentVisibleRect) : scale; + m_controller->setVisibleContentsRect(currentVisibleRect, viewportScale, trajectory); +} + +void PageViewportControllerClientQt::scaleContent(qreal itemScale, const QPointF& centerInCSSCoordinates) +{ + QPointF oldPinchCenterOnViewport = m_viewportItem->mapFromWebContent(centerInCSSCoordinates); + m_pageItem->setContentsScale(itemScale); + QPointF newPinchCenterOnViewport = m_viewportItem->mapFromWebContent(centerInCSSCoordinates); + m_viewportItem->setContentPos(m_viewportItem->contentPos() + (newPinchCenterOnViewport - oldPinchCenterOnViewport)); +} + +float PageViewportControllerClientQt::viewportScaleForRect(const QRectF& rect) const +{ + return static_cast<float>(m_viewportItem->width()) / static_cast<float>(rect.width()); +} + +} // namespace WebKit + +#include "moc_PageViewportControllerClientQt.cpp" |