2 * Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this program; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 #include "ViewportInteractionEngine.h"
24 #include "PassOwnPtr.h"
26 #include <QtDeclarative/qsgitem.h>
30 static inline QRectF visibleRectInContentCoordinate(const QSGItem* content, const QSGItem* viewport)
32 const QRectF viewportInContentCoordinate = content->mapRectFromItem(viewport, viewport->boundingRect());
33 const QRectF visibleArea = content->boundingRect().intersected(viewportInContentCoordinate);
37 static inline QRectF contentRectInViewportCoordinate(const QSGItem* content, const QSGItem* viewport)
39 return viewport->mapRectFromItem(content, content->boundingRect());
42 // Updating content properties cause the notify signals to be sent by the content item itself.
43 // Since we manage those differently, we do not want to respond
44 // to them when we are the one changing the content.
46 // The guard make sure the signal viewportUpdateRequested() is sent if necessary.
47 // When multiple guards are alive, their lifetime must be perfectly imbricated (e.g. if used ouside stack frames).
48 // We rely on the first one to trigger the update at the end since it only uses one bool internally.
50 // The public methods should create the guard if they might update content.
51 class ViewportUpdateGuard {
53 ViewportUpdateGuard(ViewportInteractionEngine* viewportInteractionEngine)
54 : viewportInteractionEngine(viewportInteractionEngine)
55 , wasUpdatingContent(viewportInteractionEngine->m_isUpdatingContent)
56 , previousPosition(viewportInteractionEngine->m_content->pos())
57 , previousSize(viewportInteractionEngine->m_content->width(), viewportInteractionEngine->m_content->height())
58 , previousScale(viewportInteractionEngine->m_content->scale())
60 viewportInteractionEngine->m_isUpdatingContent = true;
63 ~ViewportUpdateGuard()
65 if (!wasUpdatingContent) {
66 if (previousPosition != viewportInteractionEngine->m_content->pos()
67 || previousSize.width() != viewportInteractionEngine->m_content->width()
68 || previousSize.height() != viewportInteractionEngine->m_content->height()
69 || previousScale != viewportInteractionEngine->m_content->scale())
70 emit viewportInteractionEngine->viewportUpdateRequested();
72 viewportInteractionEngine->m_isUpdatingContent = wasUpdatingContent;
76 ViewportInteractionEngine* const viewportInteractionEngine;
77 const bool wasUpdatingContent;
78 const QPointF previousPosition;
79 const QSizeF previousSize;
80 const qreal previousScale;
83 ViewportInteractionEngine::ViewportInteractionEngine(const QSGItem* viewport, QSGItem* content)
84 : m_viewport(viewport)
86 , m_isUpdatingContent(false)
87 , m_pinchStartScale(1.f)
90 connect(m_content, SIGNAL(xChanged()), this, SLOT(contentViewportChanged()), Qt::DirectConnection);
91 connect(m_content, SIGNAL(yChanged()), this, SLOT(contentViewportChanged()), Qt::DirectConnection);
92 connect(m_content, SIGNAL(widthChanged()), this, SLOT(contentViewportChanged()), Qt::DirectConnection);
93 connect(m_content, SIGNAL(heightChanged()), this, SLOT(contentViewportChanged()), Qt::DirectConnection);
94 connect(m_content, SIGNAL(scaleChanged()), this, SLOT(contentViewportChanged()), Qt::DirectConnection);
97 ViewportInteractionEngine::~ViewportInteractionEngine()
101 void ViewportInteractionEngine::reset()
103 ViewportUpdateGuard guard(this);
104 m_userInteractionFlags = UserHasNotInteractedWithContent;
105 setConstraints(Constraints());
108 void ViewportInteractionEngine::setConstraints(const Constraints& constraints)
110 if (m_constraints == constraints)
113 // FIXME: if a pinch gesture is ongoing, we have to:
114 // -cancel the gesture if isUserScalable becomes true
115 // -update the page on the fly without modifying its position
117 // FIXME: if a pan gesture is ongoing, we have to
118 // -update the page without changing the position
119 // -animate the page back in viewport if necessary (if the page is fully in
120 // viewport, it does not pan horizontally anymore).
122 ViewportUpdateGuard guard(this);
123 m_constraints = constraints;
124 updateContentIfNeeded();
127 void ViewportInteractionEngine::panGestureStarted()
129 // FIXME: suspend the Web engine (stop animated GIF, etc).
130 // FIXME: initialize physics for panning (stop animation, etc).
131 m_userInteractionFlags |= UserHasMovedContent;
134 void ViewportInteractionEngine::panGestureRequestUpdate(qreal deltaX, qreal deltaY)
136 ViewportUpdateGuard guard(this);
138 QPointF itemPositionInItemCoords = m_content->mapFromItem(m_content->parentItem(), m_content->pos());
139 QPointF destInViewportCoords = m_viewport->mapFromItem(m_content, itemPositionInItemCoords + QPointF(deltaX, deltaY));
141 m_content->setPos(destInViewportCoords);
142 // This must be emitted before viewportUpdateRequested so that the web process knows where to look for tiles.
143 emit viewportTrajectoryVectorChanged(QPointF(-deltaX, -deltaY));
146 void ViewportInteractionEngine::panGestureCancelled()
148 ViewportUpdateGuard guard(this);
149 // FIXME: reset physics.
153 void ViewportInteractionEngine::panGestureEnded()
155 ViewportUpdateGuard guard(this);
156 animateContentIntoBoundariesIfNeeded();
157 // FIXME: emit viewportTrajectoryVectorChanged(QPointF()) when the pan throw animation ends and the page stops moving.
160 void ViewportInteractionEngine::pinchGestureStarted()
162 if (!m_constraints.isUserScalable)
165 m_pinchViewportUpdateDeferrer = adoptPtr(new ViewportUpdateGuard(this));
167 m_userInteractionFlags |= UserHasScaledContent;
168 m_userInteractionFlags |= UserHasMovedContent;
169 m_pinchStartScale = m_content->scale();
171 // Reset the tiling look-ahead vector so that tiles all around the viewport will be requested on pinch-end.
172 emit viewportTrajectoryVectorChanged(QPointF());
175 void ViewportInteractionEngine::pinchGestureRequestUpdate(const QPointF& pinchCenterInContentCoordinate, qreal totalScaleFactor)
177 if (!m_constraints.isUserScalable)
180 ViewportUpdateGuard guard(this);
182 // Changes of the center position should move the page even if the zoom factor
183 // does not change. Both the zoom and the panning should be handled through the physics engine.
184 const qreal scale = m_pinchStartScale * totalScaleFactor;
185 QPointF oldPinchCenterOnParent = m_content->mapToItem(m_content->parentItem(), pinchCenterInContentCoordinate);
186 m_content->setScale(scale);
187 QPointF newPinchCenterOnParent = m_content->mapToItem(m_content->parentItem(), pinchCenterInContentCoordinate);
188 m_content->setPos(m_content->pos() - (newPinchCenterOnParent - oldPinchCenterOnParent));
191 void ViewportInteractionEngine::pinchGestureEnded()
193 if (!m_constraints.isUserScalable)
197 ViewportUpdateGuard guard(this);
198 // FIXME: resume the engine after the animation.
199 animateContentIntoBoundariesIfNeeded();
201 m_pinchViewportUpdateDeferrer.clear();
204 void ViewportInteractionEngine::contentViewportChanged()
206 if (m_isUpdatingContent)
209 ViewportUpdateGuard guard(this);
210 updateContentIfNeeded();
212 // We must notify the change so the client can rely on us for all change of Geometry.
213 emit viewportUpdateRequested();
216 void ViewportInteractionEngine::updateContentIfNeeded()
218 updateContentScaleIfNeeded();
219 updateContentPositionIfNeeded();
222 void ViewportInteractionEngine::updateContentScaleIfNeeded()
224 const qreal currentContentScale = m_content->scale();
225 qreal contentScale = m_content->scale();
226 if (!(m_userInteractionFlags & UserHasScaledContent))
227 contentScale = m_constraints.initialScale;
229 contentScale = qBound(m_constraints.minimumScale, contentScale, m_constraints.maximumScale);
231 if (contentScale != currentContentScale) {
232 const QPointF centerOfInterest = visibleRectInContentCoordinate(m_content, m_viewport).center();
233 scaleContent(centerOfInterest, contentScale);
237 void ViewportInteractionEngine::updateContentPositionIfNeeded()
239 if (!(m_userInteractionFlags & UserHasMovedContent)) {
240 m_content->setX((m_viewport->width() - contentRectInViewportCoordinate(m_content, m_viewport).width()) / 2);
244 // FIXME: if the item can be fully in the viewport and is over a side, push it back in view
245 // FIXME: if the item cannot be fully in viewport, and is not covering the viewport, push it back in view
248 void ViewportInteractionEngine::animateContentIntoBoundariesIfNeeded()
250 animateContentScaleIntoBoundariesIfNeeded();
251 animateContentPositionIntoBoundariesIfNeeded();
254 void ViewportInteractionEngine::animateContentPositionIntoBoundariesIfNeeded()
256 const QRectF contentGeometry = m_viewport->mapRectFromItem(m_content, m_content->boundingRect());
257 QPointF newPos = contentGeometry.topLeft();
259 // Horizontal correction.
260 if (contentGeometry.width() < m_viewport->width())
261 newPos.setX((m_viewport->width() - contentGeometry.width()) / 2);
263 newPos.setX(qMin(0., newPos.x()));
264 const qreal rightSideGap = m_viewport->boundingRect().right() - contentGeometry.right();
265 if (rightSideGap > 0.)
266 newPos.setX(newPos.x() + rightSideGap);
269 // Vertical correction.
270 if (contentGeometry.height() < m_viewport->height())
273 newPos.setY(qMin(0., newPos.y()));
274 const qreal bottomSideGap = m_viewport->boundingRect().bottom() - contentGeometry.bottom();
275 if (bottomSideGap > 0.)
276 newPos.setY(newPos.y() + bottomSideGap);
279 if (newPos != m_content->pos())
280 // FIXME: Do you know what ANIMATE means?
281 m_content->setPos(newPos);
284 void ViewportInteractionEngine::animateContentScaleIntoBoundariesIfNeeded()
286 const qreal currentScale = m_content->scale();
287 const qreal boundedScale = qBound(m_constraints.minimumScale, currentScale, m_constraints.maximumScale);
288 if (currentScale != boundedScale) {
289 // FIXME: Do you know what ANIMATE means?
290 const QPointF centerOfInterest = visibleRectInContentCoordinate(m_content, m_viewport).center();
291 scaleContent(centerOfInterest, boundedScale);
295 void ViewportInteractionEngine::scaleContent(const QPointF& centerInContentCoordinate, qreal scale)
297 QPointF oldPinchCenterOnParent = m_content->mapToItem(m_content->parentItem(), centerInContentCoordinate);
298 m_content->setScale(scale);
299 QPointF newPinchCenterOnParent = m_content->mapToItem(m_content->parentItem(), centerInContentCoordinate);
300 m_content->setPos(m_content->pos() - (newPinchCenterOnParent - oldPinchCenterOnParent));
303 #include "moc_ViewportInteractionEngine.cpp"