void QtViewportInteractionEngine::animateItemRectVisible(const QRectF& itemRect) { ASSERT(m_scaleAnimation->state() == QAbstractAnimation::Stopped); ASSERT(!scrollAnimationActive()); if (scrollAnimationActive()) return; QRectF currentItemRectVisible = m_viewport->mapRectToWebContent(m_viewport->boundingRect()); if (itemRect == currentItemRectVisible) return; // FIXME: Investigate why that animation doesn't run when we are unfocused. if (!m_viewport->isVisible() || !m_viewport->hasFocus()) { // Apply the end result immediately when we are non-visible. setItemRectVisible(itemRect); return; } QEasingCurve easingCurve; easingCurve.setCustomType(physicalOvershoot); m_scaleAnimation->setDuration(kScaleAnimationDurationMillis); m_scaleAnimation->setEasingCurve(easingCurve); m_scaleAnimation->setStartValue(currentItemRectVisible); m_scaleAnimation->setEndValue(itemRect); m_scaleAnimation->start(); }
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) { resumeAndUpdateContent(); return; } // Inform the web process about the requested visible content rect immediately so that new tiles // are rendered at the final destination during the animation. m_controller->didChangeContentsVisibility(contentRect.topLeft(), viewportScaleForRect(contentRect)); // 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); m_scaleAnimation->start(); }
void QtViewportInteractionEngine::wheelEvent(QWheelEvent* ev) { if (scrollAnimationActive() || scaleAnimationActive() || pinchGestureActive()) return; // Ignore. // A normal scroll-tick should have a delta of 120 (1/8) degrees. Convert this to // local standard scroll step of 3 lines of 20 pixels. static const int cDefaultQtScrollStep = 20; static const int wheelScrollLines = 3; const int wheelTick = wheelScrollLines * cDefaultQtScrollStep; int pixelDelta = ev->delta() * (wheelTick / 120.f); QPointF newPosition = m_viewport->contentPos(); if (ev->orientation() == Qt::Horizontal) newPosition.rx() -= pixelDelta; else newPosition.ry() -= pixelDelta; QRectF endPosRange = computePosRangeForItemAtScale(m_content->contentsScale()); QPointF currentPosition = m_viewport->contentPos(); newPosition = boundPosition(endPosRange.topLeft(), newPosition, endPosRange.bottomRight()); m_viewport->setContentPos(newPosition); emit contentViewportChanged(currentPosition - newPosition); }
bool QtViewportInteractionEngine::ensureContentWithinViewportBoundary(bool immediate) { ASSERT(m_suspendCount); if (scrollAnimationActive() || scaleAnimationActive()) return false; qreal endItemScale = itemScaleFromCSS(innerBoundedCSSScale(currentCSSScale())); const QRectF viewportRect = m_viewport->boundingRect(); QPointF viewportHotspot = viewportRect.center(); QPointF endPosition = m_viewport->mapToWebContent(viewportHotspot) * endItemScale - viewportHotspot; QRectF endPosRange = computePosRangeForItemAtScale(endItemScale); endPosition = boundPosition(endPosRange.topLeft(), endPosition, endPosRange.bottomRight()); QRectF endVisibleContentRect(endPosition / endItemScale, viewportRect.size() / endItemScale); if (immediate) { setItemRectVisible(endVisibleContentRect); return true; } return !animateItemRectVisible(endVisibleContentRect); }
void QtViewportInteractionEngine::zoomToAreaGestureEnded(const QPointF& touchPoint, const QRectF& targetArea) { if (!targetArea.isValid()) return; if (scrollAnimationActive() || scaleAnimationActive()) return; const int margin = 10; // We want at least a little bit or margin. QRectF endArea = itemRectFromCSS(targetArea.adjusted(-margin, -margin, margin, margin)); const QRectF viewportRect = m_viewport->boundingRect(); qreal targetCSSScale = cssScaleFromItem(viewportRect.size().width() / endArea.size().width()); qreal endItemScale = itemScaleFromCSS(innerBoundedCSSScale(qMin(targetCSSScale, qreal(2.5)))); // 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(), touchPoint.y() * m_constraints.devicePixelRatio); const QPointF viewportHotspot = viewportRect.center(); QPointF endPosition = hotspot * endItemScale - viewportHotspot; QRectF endPosRange = computePosRangeForItemAtScale(endItemScale); endPosition = boundPosition(endPosRange.topLeft(), endPosition, endPosRange.bottomRight()); QRectF endVisibleContentRect(endPosition / endItemScale, viewportRect.size() / endItemScale); animateItemRectVisible(endVisibleContentRect); }
void QtViewportInteractionEngine::wheelEvent(QWheelEvent* ev) { if (scrollAnimationActive() || scaleAnimationActive() || pinchGestureActive()) return; // Ignore. int delta = ev->delta(); // A delta that is not mod 120 indicates a device that is sending // fine-resolution scroll events, so use the delta as number of wheel ticks // and number of pixels to scroll. See also webkit.org/b/29601 bool fullTick = !(delta % 120); static const int cDefaultQtScrollStep = 20; static const int wheelScrollLines = 3; int scrollLines = (fullTick) ? wheelScrollLines * cDefaultQtScrollStep : 1; delta = (fullTick) ? delta / 120.0f : delta; delta *= scrollLines; QPointF newPosition = m_viewport->contentPos(); if (ev->orientation() == Qt::Horizontal) newPosition.rx() += delta; else newPosition.ry() += delta; QRectF endPosRange = computePosRangeForItemAtScale(m_content->contentsScale()); QPointF currentPosition = m_viewport->contentPos(); newPosition = boundPosition(endPosRange.topLeft(), newPosition, endPosRange.bottomRight()); m_viewport->setContentPos(newPosition); emit contentViewportChanged(currentPosition - newPosition); }
void QtViewportHandler::touchBegin() { m_hadUserInteraction = true; // Prevents resuming the page between the user's flicks of the page while the animation is running. if (scrollAnimationActive()) m_touchUpdateDeferrer = adoptPtr(new ViewportUpdateDeferrer(this, ViewportUpdateDeferrer::DeferUpdateAndSuspendContent)); }
void QtViewportHandler::animatePageItemRectVisible(const QRectF& itemRect) { ASSERT(m_scaleAnimation->state() == QAbstractAnimation::Stopped); ASSERT(!scrollAnimationActive()); if (scrollAnimationActive()) return; QRectF currentPageItemRectVisible = m_viewportItem->mapRectToWebContent(m_viewportItem->boundingRect()); if (itemRect == currentPageItemRectVisible) return; m_scaleAnimation->setStartValue(currentPageItemRectVisible); m_scaleAnimation->setEndValue(itemRect); m_scaleAnimation->start(); }
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 QtViewportInteractionEngine::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_viewport->cancelFlick(); setItemRectVisible(nearestValidBounds()); }
void QtViewportInteractionEngine::zoomToAreaGestureEnded(const QPointF& touchPoint, const QRectF& targetArea) { if (!targetArea.isValid()) return; if (scrollAnimationActive() || scaleAnimationActive()) return; const int margin = 10; // We want at least a little bit of margin. QRectF endArea = itemRectFromCSS(targetArea.adjusted(-margin, -margin, margin, margin)); const QRectF viewportRect = m_viewport->boundingRect(); qreal targetCSSScale = cssScaleFromItem(viewportRect.size().width() / endArea.size().width()); qreal endItemScale = itemScaleFromCSS(innerBoundedCSSScale(qMin(targetCSSScale, qreal(2.5)))); qreal currentScale = m_content->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(), touchPoint.y() * m_devicePixelRatio); const QPointF viewportHotspot = viewportRect.center(); QPointF endPosition = hotspot * endItemScale - viewportHotspot; QRectF endPosRange = computePosRangeForItemAtScale(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(m_viewport->contentPos() / currentScale, viewportRect.size() / currentScale); 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_viewport->contentPos().x())); m_zoomOutScale = endItemScale; break; case ZoomBack: { ScaleStackItem lastScale = m_scaleStack.takeLast(); endItemScale = lastScale.scale; // Recalculate endPosition and bound it according to new scale. endPosition.setY(hotspot.y() * endItemScale - viewportHotspot.y()); endPosition.setX(lastScale.xPosition); endPosRange = computePosRangeForItemAtScale(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; } animateItemRectVisible(endVisibleContentRect); }