PageViewportControllerClientQt.cpp [plain text]
#include "config.h"
#include "PageViewportControllerClientQt.h"
#include "WebPageProxy.h"
#include "qquickwebpage_p.h"
#include "qquickwebview_p.h"
#include "qwebkittest_p.h"
#include <QPointF>
#include <QTransform>
#include <QtQuick/qquickitem.h>
#include <WKAPICast.h>
#include <WebCore/FloatRect.h>
#include <WebCore/FloatSize.h>
using namespace WebCore;
namespace WebKit {
static const int kScaleAnimationDurationMillis = 250;
PageViewportControllerClientQt::PageViewportControllerClientQt(QQuickWebView* viewportItem, QQuickWebPage* pageItem)
: m_viewportItem(viewportItem)
, m_pageItem(pageItem)
, m_scaleChange(this)
, m_scrollChange(this)
, m_touchInteraction(this, false )
, m_scaleAnimation(new ScaleAnimation(this))
, m_activeInteractionCount(0)
, m_pinchStartScale(-1)
, m_lastCommittedScale(-1)
, m_zoomOutScale(0)
{
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)
{
if (!m_controllerClient->scaleAnimationActive())
return;
QRectF itemRect = value.toRectF();
float itemScale = m_controllerClient->viewportScaleForRect(itemRect);
m_controllerClient->setContentRectVisiblePositionAtScale(itemRect.topLeft(), itemScale);
}
void PageViewportControllerClientQt::ViewportInteractionTracker::begin()
{
if (m_inProgress)
return;
m_inProgress = true;
if (m_shouldSuspend)
toImpl(m_controllerClient->m_viewportItem->pageRef())->suspendActiveDOMObjectsAndAnimations();
++(m_controllerClient->m_activeInteractionCount);
}
void PageViewportControllerClientQt::ViewportInteractionTracker::end()
{
if (!m_inProgress)
return;
m_inProgress = false;
ASSERT(m_controllerClient->m_activeInteractionCount > 0);
if (!(--(m_controllerClient->m_activeInteractionCount)))
toImpl(m_controllerClient->m_viewportItem->pageRef())->resumeActiveDOMObjectsAndAnimations();
}
PageViewportControllerClientQt::~PageViewportControllerClientQt()
{
}
void PageViewportControllerClientQt::setContentRectVisiblePositionAtScale(const QPointF& location, qreal itemScale)
{
ASSERT(itemScale >= 0);
scaleContent(itemScale);
QPointF newPosition(m_pageItem->position() + location * itemScale);
m_viewportItem->setContentPos(newPosition);
}
void PageViewportControllerClientQt::animateContentRectVisible(const QRectF& contentRect)
{
ASSERT(!scaleAnimationActive());
ASSERT(!scrollAnimationActive());
QRectF viewportRectInContentCoords = m_viewportItem->mapRectToWebContent(m_viewportItem->boundingRect());
if (contentRect == viewportRectInContentCoords) {
m_scaleChange.end();
updateViewportController();
return;
}
m_controller->didChangeContentsVisibility(contentRect.topLeft(), viewportScaleForRect(contentRect));
m_scaleAnimation->setStartValue(viewportRectInContentCoords);
m_scaleAnimation->setEndValue(contentRect);
m_scaleAnimation->start();
}
void PageViewportControllerClientQt::flickMoveStarted()
{
m_scrollChange.begin();
m_lastScrollPosition = m_viewportItem->contentPos();
}
void PageViewportControllerClientQt::flickMoveEnded()
{
m_scrollChange.end();
updateViewportController();
}
void PageViewportControllerClientQt::pageItemPositionChanged()
{
if (m_scaleChange.inProgress())
return;
QPointF newPosition = m_viewportItem->contentPos();
updateViewportController(m_lastScrollPosition - newPosition);
m_lastScrollPosition = newPosition;
}
void PageViewportControllerClientQt::scaleAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State )
{
switch (newState) {
case QAbstractAnimation::Running:
m_scaleChange.begin();
break;
case QAbstractAnimation::Stopped:
m_scaleChange.end();
updateViewportController();
break;
default:
break;
}
}
void PageViewportControllerClientQt::touchBegin()
{
ASSERT(!m_viewportItem->isDragging());
ASSERT(!(m_pinchStartScale > 0));
m_controller->setHadUserInteraction(true);
m_touchInteraction.begin();
}
void PageViewportControllerClientQt::touchEnd()
{
m_touchInteraction.end();
}
void PageViewportControllerClientQt::focusEditableArea(const QRectF& caretArea, const QRectF& targetArea)
{
ASSERT(m_controller->hadUserInteraction());
const float editingFixedScale = 2;
float targetScale = m_controller->innerBoundedViewportScale(editingFixedScale);
const QRectF viewportRect = m_viewportItem->boundingRect();
qreal x;
const qreal borderOffset = 10;
if ((targetArea.width() + borderOffset) * targetScale <= viewportRect.width()) {
x = viewportRect.center().x() - targetArea.width() * targetScale / 2.0;
} else {
qreal caretOffset = caretArea.x() - targetArea.x();
x = qMin(viewportRect.width() - (caretOffset + borderOffset) * targetScale, borderOffset * targetScale);
}
const QPointF hotspot = QPointF(targetArea.x(), targetArea.center().y());
const QPointF viewportHotspot = QPointF(x, viewportRect.center().y());
QPointF endPosition = hotspot - viewportHotspot / targetScale;
endPosition = m_controller->boundContentsPositionAtScale(endPosition, targetScale);
QRectF endVisibleContentRect(endPosition, viewportRect.size() / targetScale);
animateContentRectVisible(endVisibleContentRect);
}
void PageViewportControllerClientQt::zoomToAreaGestureEnded(const QPointF& touchPoint, const QRectF& targetArea)
{
ASSERT(m_controller->hadUserInteraction());
if (!targetArea.isValid())
return;
if (m_scrollChange.inProgress() || m_scaleChange.inProgress())
return;
const float margin = 10; QRectF endArea = targetArea.adjusted(-margin, -margin, margin, margin);
const QRectF viewportRect = m_viewportItem->boundingRect();
const qreal minViewportScale = qreal(2.5);
qreal targetScale = viewportRect.size().width() / endArea.size().width();
targetScale = m_controller->innerBoundedViewportScale(qMin(minViewportScale, targetScale));
qreal currentScale = m_pageItem->contentsScale();
const QPointF hotspot = QPointF(endArea.center().x(), touchPoint.y());
const QPointF viewportHotspot = viewportRect.center();
QPointF endPosition = hotspot - viewportHotspot / targetScale;
endPosition = m_controller->boundContentsPositionAtScale(endPosition, targetScale);
QRectF endVisibleContentRect(endPosition, viewportRect.size() / targetScale);
enum { ZoomIn, ZoomBack, ZoomOut, NoZoom } zoomAction = ZoomIn;
if (!m_scaleStack.isEmpty() && fuzzyCompare(targetScale, currentScale, 0.01)) {
QRectF currentContentRect(m_viewportItem->mapRectToWebContent(viewportRect));
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(targetScale, m_zoomOutScale, 0.01))
zoomAction = ZoomBack;
else if (targetScale < currentScale)
zoomAction = ZoomOut;
switch (zoomAction) {
case ZoomIn:
m_scaleStack.append(ScaleStackItem(currentScale, m_viewportItem->contentPos().x() / currentScale));
m_zoomOutScale = targetScale;
break;
case ZoomBack: {
if (m_scaleStack.isEmpty()) {
targetScale = m_controller->minimumScale();
endPosition.setY(hotspot.y() - viewportHotspot.y() / targetScale);
endPosition.setX(0);
m_zoomOutScale = 0;
} else {
ScaleStackItem lastScale = m_scaleStack.takeLast();
targetScale = lastScale.scale;
endPosition.setY(hotspot.y() - viewportHotspot.y() / targetScale);
endPosition.setX(lastScale.xPosition);
}
endPosition = m_controller->boundContentsPositionAtScale(endPosition, targetScale);
endVisibleContentRect = QRectF(endPosition, viewportRect.size() / targetScale);
break;
}
case ZoomOut:
while (!m_scaleStack.isEmpty() && m_scaleStack.last().scale >= targetScale)
m_scaleStack.removeLast();
m_zoomOutScale = targetScale;
break;
case NoZoom:
break;
}
animateContentRectVisible(endVisibleContentRect);
}
void PageViewportControllerClientQt::clearRelativeZoomState()
{
m_zoomOutScale = 0;
m_scaleStack.clear();
}
QRectF PageViewportControllerClientQt::nearestValidVisibleContentsRect() const
{
float targetScale = m_controller->innerBoundedViewportScale(m_pageItem->contentsScale());
const QRectF viewportRect = m_viewportItem->boundingRect();
QPointF viewportHotspot = viewportRect.center();
QPointF endPosition = m_viewportItem->mapToWebContent(viewportHotspot) - viewportHotspot / targetScale;
endPosition = m_controller->boundContentsPositionAtScale(endPosition, targetScale);
return QRectF(endPosition, viewportRect.size() / targetScale);
}
void PageViewportControllerClientQt::setViewportPosition(const FloatPoint& contentsPoint)
{
QPointF newPosition((m_pageItem->position() + QPointF(contentsPoint)) * m_pageItem->contentsScale());
m_viewportItem->setContentPos(newPosition);
}
void PageViewportControllerClientQt::setPageScaleFactor(float localScale)
{
scaleContent(localScale);
}
void PageViewportControllerClientQt::setContentsRectToNearestValidBounds()
{
float targetScale = m_controller->innerBoundedViewportScale(m_pageItem->contentsScale());
setContentRectVisiblePositionAtScale(nearestValidVisibleContentsRect().topLeft(), targetScale);
updateViewportController();
}
bool PageViewportControllerClientQt::scrollAnimationActive() const
{
return m_viewportItem->isFlicking();
}
void PageViewportControllerClientQt::panGestureStarted(const QPointF& position, qint64 eventTimestampMillis)
{
ASSERT(m_touchInteraction.inProgress());
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()
{
m_viewportItem->cancelFlick();
}
bool PageViewportControllerClientQt::scaleAnimationActive() const
{
return m_scaleAnimation->state() == QAbstractAnimation::Running;
}
void PageViewportControllerClientQt::cancelScrollAnimation()
{
if (!scrollAnimationActive())
return;
m_viewportItem->cancelFlick();
setContentsRectToNearestValidBounds();
}
void PageViewportControllerClientQt::interruptScaleAnimation()
{
m_scaleAnimation->stop();
}
void PageViewportControllerClientQt::pinchGestureStarted(const QPointF& pinchCenterInViewportCoordinates)
{
ASSERT(m_touchInteraction.inProgress());
if (!m_controller->allowsUserScaling() || !m_viewportItem->isInteractive())
return;
clearRelativeZoomState();
m_scaleChange.begin();
m_lastPinchCenterInViewportCoordinates = pinchCenterInViewportCoordinates;
m_pinchStartScale = m_pageItem->contentsScale();
}
void PageViewportControllerClientQt::pinchGestureRequestUpdate(const QPointF& pinchCenterInViewportCoordinates, qreal totalScaleFactor)
{
if (!m_controller->allowsUserScaling() || !m_viewportItem->isInteractive())
return;
ASSERT(m_scaleChange.inProgress());
ASSERT(m_pinchStartScale > 0);
const qreal pinchScale = m_pinchStartScale * totalScaleFactor;
const qreal targetScale = m_controller->outerBoundedViewportScale(pinchScale);
scaleContent(targetScale, m_viewportItem->mapToWebContent(pinchCenterInViewportCoordinates));
const QPointF positionDiff = pinchCenterInViewportCoordinates - m_lastPinchCenterInViewportCoordinates;
m_lastPinchCenterInViewportCoordinates = pinchCenterInViewportCoordinates;
m_viewportItem->setContentPos(m_viewportItem->contentPos() - positionDiff);
}
void PageViewportControllerClientQt::pinchGestureEnded()
{
if (m_pinchStartScale < 0)
return;
ASSERT(m_scaleChange.inProgress());
m_pinchStartScale = -1;
animateContentRectVisible(nearestValidVisibleContentsRect());
}
void PageViewportControllerClientQt::pinchGestureCancelled()
{
m_pinchStartScale = -1;
m_scaleChange.end();
updateViewportController();
}
void PageViewportControllerClientQt::didChangeContentsSize(const IntSize& newSize)
{
m_pageItem->setContentsSize(QSizeF(newSize));
emit m_viewportItem->experimental()->test()->contentsScaleCommitted();
if (!m_scaleChange.inProgress() && !m_scrollChange.inProgress())
setContentsRectToNearestValidBounds();
}
void PageViewportControllerClientQt::didChangeVisibleContents()
{
qreal scale = m_pageItem->contentsScale();
if (scale != m_lastCommittedScale)
emit m_viewportItem->experimental()->test()->contentsScaleCommitted();
m_lastCommittedScale = scale;
m_pageItem->update();
}
void PageViewportControllerClientQt::didChangeViewportAttributes()
{
clearRelativeZoomState();
emit m_viewportItem->experimental()->test()->viewportChanged();
}
void PageViewportControllerClientQt::updateViewportController(const QPointF& trajectory)
{
FloatPoint viewportPos = m_viewportItem->mapToWebContent(QPointF());
m_controller->didChangeContentsVisibility(viewportPos, m_pageItem->contentsScale(), 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());
}
}
#include "moc_PageViewportControllerClientQt.cpp"