initial import
[vuplus_webkit] / Source / WebKit2 / UIProcess / qt / ViewportInteractionEngine.cpp
1 /*
2  * Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org>
3  *
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.
8  *
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.
13  *
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.
18  *
19  */
20
21 #include "config.h"
22 #include "ViewportInteractionEngine.h"
23
24 #include "PassOwnPtr.h"
25 #include <QPointF>
26 #include <QtDeclarative/qsgitem.h>
27
28 namespace WebKit {
29
30 static inline QRectF visibleRectInContentCoordinate(const QSGItem* content, const QSGItem* viewport)
31 {
32     const QRectF viewportInContentCoordinate = content->mapRectFromItem(viewport, viewport->boundingRect());
33     const QRectF visibleArea = content->boundingRect().intersected(viewportInContentCoordinate);
34     return visibleArea;
35 }
36
37 static inline QRectF contentRectInViewportCoordinate(const QSGItem* content, const QSGItem* viewport)
38 {
39     return viewport->mapRectFromItem(content, content->boundingRect());
40 }
41
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.
45 //
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.
49 //
50 // The public methods should create the guard if they might update content.
51 class ViewportUpdateGuard {
52 public:
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())
59     {
60         viewportInteractionEngine->m_isUpdatingContent = true;
61     }
62
63     ~ViewportUpdateGuard()
64     {
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();
71         }
72         viewportInteractionEngine->m_isUpdatingContent = wasUpdatingContent;
73     }
74
75 private:
76     ViewportInteractionEngine* const viewportInteractionEngine;
77     const bool wasUpdatingContent;
78     const QPointF previousPosition;
79     const QSizeF previousSize;
80     const qreal previousScale;
81 };
82
83 ViewportInteractionEngine::ViewportInteractionEngine(const QSGItem* viewport, QSGItem* content)
84     : m_viewport(viewport)
85     , m_content(content)
86     , m_isUpdatingContent(false)
87     , m_pinchStartScale(1.f)
88 {
89     reset();
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);
95 }
96
97 ViewportInteractionEngine::~ViewportInteractionEngine()
98 {
99 }
100
101 void ViewportInteractionEngine::reset()
102 {
103     ViewportUpdateGuard guard(this);
104     m_userInteractionFlags = UserHasNotInteractedWithContent;
105     setConstraints(Constraints());
106 }
107
108 void ViewportInteractionEngine::setConstraints(const Constraints& constraints)
109 {
110     if (m_constraints == constraints)
111         return;
112
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
116
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).
121
122     ViewportUpdateGuard guard(this);
123     m_constraints = constraints;
124     updateContentIfNeeded();
125 }
126
127 void ViewportInteractionEngine::panGestureStarted()
128 {
129     // FIXME: suspend the Web engine (stop animated GIF, etc).
130     // FIXME: initialize physics for panning (stop animation, etc).
131     m_userInteractionFlags |= UserHasMovedContent;
132 }
133
134 void ViewportInteractionEngine::panGestureRequestUpdate(qreal deltaX, qreal deltaY)
135 {
136     ViewportUpdateGuard guard(this);
137
138     QPointF itemPositionInItemCoords = m_content->mapFromItem(m_content->parentItem(), m_content->pos());
139     QPointF destInViewportCoords = m_viewport->mapFromItem(m_content, itemPositionInItemCoords + QPointF(deltaX, deltaY));
140
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));
144 }
145
146 void ViewportInteractionEngine::panGestureCancelled()
147 {
148     ViewportUpdateGuard guard(this);
149     // FIXME: reset physics.
150     panGestureEnded();
151 }
152
153 void ViewportInteractionEngine::panGestureEnded()
154 {
155     ViewportUpdateGuard guard(this);
156     animateContentIntoBoundariesIfNeeded();
157     // FIXME: emit viewportTrajectoryVectorChanged(QPointF()) when the pan throw animation ends and the page stops moving.
158 }
159
160 void ViewportInteractionEngine::pinchGestureStarted()
161 {
162     if (!m_constraints.isUserScalable)
163         return;
164
165     m_pinchViewportUpdateDeferrer = adoptPtr(new ViewportUpdateGuard(this));
166
167     m_userInteractionFlags |= UserHasScaledContent;
168     m_userInteractionFlags |= UserHasMovedContent;
169     m_pinchStartScale = m_content->scale();
170
171     // Reset the tiling look-ahead vector so that tiles all around the viewport will be requested on pinch-end.
172     emit viewportTrajectoryVectorChanged(QPointF());
173 }
174
175 void ViewportInteractionEngine::pinchGestureRequestUpdate(const QPointF& pinchCenterInContentCoordinate, qreal totalScaleFactor)
176 {
177     if (!m_constraints.isUserScalable)
178         return;
179
180     ViewportUpdateGuard guard(this);
181
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));
189 }
190
191 void ViewportInteractionEngine::pinchGestureEnded()
192 {
193     if (!m_constraints.isUserScalable)
194         return;
195
196     {
197         ViewportUpdateGuard guard(this);
198         // FIXME: resume the engine after the animation.
199         animateContentIntoBoundariesIfNeeded();
200     }
201     m_pinchViewportUpdateDeferrer.clear();
202 }
203
204 void ViewportInteractionEngine::contentViewportChanged()
205 {
206     if (m_isUpdatingContent)
207         return;
208
209     ViewportUpdateGuard guard(this);
210     updateContentIfNeeded();
211
212     // We must notify the change so the client can rely on us for all change of Geometry.
213     emit viewportUpdateRequested();
214 }
215
216 void ViewportInteractionEngine::updateContentIfNeeded()
217 {
218     updateContentScaleIfNeeded();
219     updateContentPositionIfNeeded();
220 }
221
222 void ViewportInteractionEngine::updateContentScaleIfNeeded()
223 {
224     const qreal currentContentScale = m_content->scale();
225     qreal contentScale = m_content->scale();
226     if (!(m_userInteractionFlags & UserHasScaledContent))
227         contentScale = m_constraints.initialScale;
228
229     contentScale = qBound(m_constraints.minimumScale, contentScale, m_constraints.maximumScale);
230
231     if (contentScale != currentContentScale) {
232         const QPointF centerOfInterest = visibleRectInContentCoordinate(m_content, m_viewport).center();
233         scaleContent(centerOfInterest, contentScale);
234     }
235 }
236
237 void ViewportInteractionEngine::updateContentPositionIfNeeded()
238 {
239     if (!(m_userInteractionFlags & UserHasMovedContent)) {
240         m_content->setX((m_viewport->width() - contentRectInViewportCoordinate(m_content, m_viewport).width()) / 2);
241         m_content->setY(0);
242     }
243
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
246 }
247
248 void ViewportInteractionEngine::animateContentIntoBoundariesIfNeeded()
249 {
250     animateContentScaleIntoBoundariesIfNeeded();
251     animateContentPositionIntoBoundariesIfNeeded();
252 }
253
254 void ViewportInteractionEngine::animateContentPositionIntoBoundariesIfNeeded()
255 {
256     const QRectF contentGeometry = m_viewport->mapRectFromItem(m_content, m_content->boundingRect());
257     QPointF newPos = contentGeometry.topLeft();
258
259     // Horizontal correction.
260     if (contentGeometry.width() < m_viewport->width())
261         newPos.setX((m_viewport->width() - contentGeometry.width()) / 2);
262     else {
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);
267     }
268
269     // Vertical correction.
270     if (contentGeometry.height() < m_viewport->height())
271         newPos.setY(0);
272     else {
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);
277     }
278
279     if (newPos != m_content->pos())
280         // FIXME: Do you know what ANIMATE means?
281         m_content->setPos(newPos);
282 }
283
284 void ViewportInteractionEngine::animateContentScaleIntoBoundariesIfNeeded()
285 {
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);
292     }
293 }
294
295 void ViewportInteractionEngine::scaleContent(const QPointF& centerInContentCoordinate, qreal scale)
296 {
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));
301 }
302
303 #include "moc_ViewportInteractionEngine.cpp"
304
305 }