ginebra2/ScrollHelper.cpp
changeset 16 3c88a81ff781
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/ScrollHelper.cpp	Fri Oct 15 17:30:59 2010 -0400
@@ -0,0 +1,404 @@
+#include <QtCore>
+#include "ScrollHelper.h"
+#include "qstmfilelogger.h"
+
+
+#define SCROLL_TIMEOUT   16
+
+namespace GVA
+{
+static const int MaxFlickDurartion = 1500;
+static const int MidFlickDurartion = 800;
+static const int MinFlickDurartion = 500;
+static const qreal MaxFlickSpeed = 2.0;
+static const qreal MidFlickSpeed = 1.2;
+static const qreal MinFlickSpeed = 0.5;
+static const qreal MaxFlickInViewportUnits = 1.0;
+static const qreal MidFlickInViewportUnits = 0.8;
+static const qreal MinFlickInViewportUnits = 0.2;
+static const qreal DefaultDecel = 0.005;
+
+ScrollHelper::ScrollHelper(QObject* scrolledWidget) : QObject(),
+                                  m_scrolledWidget(scrolledWidget),
+                                  m_minFlickDuration(MinFlickDurartion),
+                                  m_maxFlickDuration(MaxFlickDurartion),
+                                  m_midFlickDuration(MidFlickDurartion),
+                                  m_scrollDelta(QPointF()),
+                                  m_minFlickSpeed(MinFlickSpeed),
+                                  m_maxFlickSpeed(MaxFlickSpeed),
+                                  m_midFlickSpeed(MidFlickSpeed),
+                                  m_maxFlickInViewportUnits(MaxFlickInViewportUnits),
+                                  m_minFlickInViewportUnits(MinFlickInViewportUnits),
+                                  m_midFlickInViewportUnits(MidFlickInViewportUnits),
+                                  m_viewportSize(QSize()),
+                                  m_lockToY(false),
+                                  m_lockToX(false),
+                                  m_decel(DefaultDecel)
+                                  
+{
+    m_scrollAnimation = new QPropertyAnimation(scrolledWidget, "widgetScrollPosition");
+    connect(m_scrollAnimation, SIGNAL(finished()), this, SLOT(stopScroll()));
+    connect(m_scrollAnimation, SIGNAL(stateChanged(QAbstractAnimation::State,QAbstractAnimation::State)),
+            this, SLOT(scrollAnimationStateChanged(QAbstractAnimation::State,QAbstractAnimation::State)));
+    m_scrollTimer = UiTimer::New();    
+    m_scrollTimer->setPriority(50);   
+    m_scrollTimer->setTimerCallback("scrollTimerCallback");
+    m_easingCurve = new QEasingCurve(QEasingCurve::OutCubic);
+    m_easingCurveOvershoot= new QEasingCurve(QEasingCurve::OutBack);
+
+}
+
+ScrollHelper::~ScrollHelper()
+{
+    m_scrollTimer->stop();
+    delete m_scrollTimer;
+    delete m_easingCurve;
+    delete m_easingCurveOvershoot;
+}
+
+
+void ScrollHelper::setFlickDurationLimits(int minDuration, int midDuration, int maxDuration)
+{
+    m_minFlickDuration = minDuration;
+    m_midFlickDuration = midDuration;
+    m_maxFlickDuration = maxDuration;
+}
+
+void ScrollHelper::setFlickSpeedLimits(qreal minSpeed, qreal midSpeed, qreal maxSpeed)
+{
+    m_minFlickSpeed = minSpeed;
+    m_midFlickSpeed = midSpeed;
+    m_maxFlickSpeed = maxSpeed;
+}
+
+void ScrollHelper::setFlickLimits(qreal minFlick, qreal midFlick, qreal maxFlick)
+{
+    m_minFlickInViewportUnits = minFlick;
+    m_midFlickInViewportUnits = midFlick;
+    m_maxFlickInViewportUnits = maxFlick;
+}
+
+
+void ScrollHelper::setDeceleration(qreal decel)
+{
+    m_decel = decel;
+}
+
+qreal ScrollHelper::getDeceleration()
+{
+    return m_decel;
+}
+
+
+void ScrollHelper::scrollTimerCallback()
+{
+    m_scrollTimer->stop();
+    if (m_scrollState != ScrollHelper::ActiveState) {
+        return;
+    }
+    qstmDebug() << "ScrollHelper::scrollTimerCallback. m_scrollDelta: " << m_scrollDelta <<
+            ", m_scrollMode: " << m_scrollMode << "\n";
+    
+    QPointF speed = m_kineticSpeed;
+    if (m_scrollMode == ScrollHelper::KineticScrollMode) {
+        m_scrollDuration += SCROLL_TIMEOUT;
+        qreal progress = (qreal)m_scrollDuration / m_scrollTotalDuration;
+        if (progress > 0.0 && progress < 1.0) {
+            qreal val = m_curEasingCurve->valueForProgress(progress);
+            QPointF tp = m_startScrollPos + val * (m_targetScrollPos - m_startScrollPos);
+            m_curScrollPos = tp;
+            setScrollPos(tp);
+            m_scrollTimer->start(SCROLL_TIMEOUT, this);
+        }
+        else {
+            stopScroll();
+        }
+    }
+    else if (m_scrollMode == ScrollHelper::PanScrollMode) {
+        doScroll(m_scrollDelta);
+        m_scrollDelta = QPointF();
+        m_scrollTimer->start(SCROLL_TIMEOUT, this);
+    }
+}
+
+
+QPointF ScrollHelper::speedForNextInterval(const QPointF& initSpeed, long timeInterval, const QPointF& decel)
+{
+    QPointF speed = initSpeed;
+    if (speed.y() < 0) {
+        speed.ry() += (decel.y() * timeInterval);
+        speed.ry() = qMin(qreal(0.0), speed.y());
+    }
+    else if (speed.y() > 0) {
+        speed.ry() -= (decel.y() * timeInterval);
+        speed.ry() = qMax(qreal(0.0), speed.y());
+    }
+    if (speed.x() < 0) {
+        speed.rx() += (decel.x() * timeInterval);
+        speed.rx() = qMin(qreal(0.0), speed.x());
+    }
+    else if (speed.x() > 0) {
+        speed.rx() -= (decel.x() * timeInterval);
+        speed.rx() = qMax(qreal(0.0), speed.x());
+    }
+    return speed;
+}
+
+
+void ScrollHelper::scroll(QPointF& delta)
+{
+    if ( delta.x() == 0 && delta.y() == 0)  return;
+
+    if (m_scrollState == ScrollHelper::IdleState) {
+        //m_lockToX = (delta.y() == 0);
+        //m_lockToY = (delta.x() == 0);
+
+        doScroll(delta);
+        m_scrollDelta = QPointF();
+        m_scrollTimer->setSingleShot(true);
+        m_scrollTimer->start(SCROLL_TIMEOUT, this);
+    }
+    else {
+        if (m_lockToX) {
+            delta.ry() = 0.0;
+        }
+        if (m_lockToY) {
+            delta.rx() = 0.0;
+        }
+        m_scrollDelta += delta;
+    }
+    m_scrollMode = ScrollHelper::PanScrollMode;
+    m_scrollState = ScrollHelper::ActiveState;
+}
+
+bool ScrollHelper::isScrolling()
+{
+    return m_scrollState == ScrollHelper::ActiveState;
+}
+
+void ScrollHelper::doScroll(QPointF& delta)
+{
+    QPointF scrollPos = getScrollPos();
+    QPointF targetPos;
+    targetPos = scrollPos + delta;
+    setScrollPos(targetPos);
+}
+
+QPointF ScrollHelper::getScrollPos()
+{
+    return m_scrolledWidget->property("widgetScrollPosition").toPointF();
+}
+
+void  ScrollHelper::setScrollPos(QPointF& pos)
+{
+     m_scrolledWidget->setProperty("widgetScrollPosition", pos.toPoint());
+}
+
+QPointF ScrollHelper::getMaxScrollPos()
+{
+    return m_scrolledWidget->property("maxScrollPosition").toPointF();
+}
+
+
+QSizeF ScrollHelper::viewportSize()
+{
+    return m_viewportSize;
+}
+
+void ScrollHelper::setViewportSize(const QSizeF& size)
+{
+    m_viewportSize = size;
+}
+
+void ScrollHelper::stopScrollNoSignal()
+{
+    m_scrollTimer->stop();
+    reset();    
+}
+
+void ScrollHelper::stopScroll()
+{     
+    stopScrollNoSignal();
+    emit scrollFinished();
+}
+
+
+void ScrollHelper::reset()
+{
+    m_scrollState = ScrollHelper::IdleState;
+    m_scrollMode = ScrollHelper::ReadyMode;
+    m_scrollDelta = QPointF();
+    m_lockToY = false;
+    m_lockToX = false;
+}
+
+void ScrollHelper::panFromOvershoot()
+{
+    stopScroll();
+    QPointF scrollPos = getScrollPos();
+    QPointF targetScrollPos = scrollPos;
+    QEasingCurve::Type easingCurveType = QEasingCurve::OutCubic;
+    if (clampScrollPosition(targetScrollPos)) {
+        easingCurveType = QEasingCurve::OutBack;
+        m_scrollMode = ScrollHelper::PanScrollMode;
+        startScrollAnimation(scrollPos, targetScrollPos, 300, easingCurveType);
+    }
+}
+
+bool ScrollHelper::clampScrollPosition(QPointF& scrollPos)
+{
+    QPointF maxScrollPos = getMaxScrollPos();
+    QPointF origPos = scrollPos;
+    scrollPos.ry() = qBound(qreal(0.0), origPos.y(), maxScrollPos.y());
+    scrollPos.rx() = qBound(qreal(0.0), origPos.x(), maxScrollPos.x());
+    bool clampY = (scrollPos.y() != origPos.y()) &&
+                  (scrollPos.y() == qreal(0.0) || scrollPos.y() == maxScrollPos.y());
+
+    bool clampX = (scrollPos.x() != origPos.x()) &&
+                   (scrollPos.x() == qreal(0.0) || scrollPos.x() == maxScrollPos.x());
+    return (clampX || clampY);
+}
+
+
+void ScrollHelper::startScrollAnimation(QPointF& scrollPos, QPointF& targetPos,
+                                        int duration, QEasingCurve::Type& easingCurveType)
+{
+    if (scrollPos != targetPos) {
+        m_scrollAnimation->setStartValue(scrollPos);
+        m_scrollAnimation->setEndValue(targetPos);
+        m_scrollAnimation->setDuration(duration);
+        m_scrollAnimation->setEasingCurve(easingCurveType);
+        m_scrollAnimation->start();
+        m_scrollState = ScrollHelper::ActiveState;
+    }
+}
+
+void ScrollHelper::scrollAnimationStateChanged(QAbstractAnimation::State newState,
+                                               QAbstractAnimation::State)
+{
+    switch (newState) {
+        case QAbstractAnimation::Stopped:
+            reset();
+            break;
+        case QAbstractAnimation::Running:
+            break;
+        default:
+            break;
+    }
+}
+
+
+int ScrollHelper::calcScrollDuration(const QPointF& speed, const QPointF& delta)
+{
+    QPointF v1 = speed;
+    int flickDuration = sqrt(delta.x() * delta.x() + delta.y() * delta.y()) /
+                        sqrt(v1.x() * v1.x() + v1.y() * v1.y());
+    flickDuration = qBound(m_minFlickDuration, flickDuration, m_maxFlickDuration);
+    return flickDuration;
+}
+
+
+int ScrollHelper::calcScrollDuration(qreal motionFactor)
+{
+    int duration = m_midFlickDuration;
+    if (motionFactor == m_maxFlickInViewportUnits) {
+         duration = m_maxFlickDuration;
+    }
+    else if (motionFactor == m_minFlickInViewportUnits) {
+        duration = m_minFlickDuration;
+    }
+    return duration;
+}
+
+void ScrollHelper::kineticScroll(QPointF& speed)
+{
+    if (m_lockToY) {
+        speed.rx() = 0.0;
+    }
+    if (m_lockToX) {
+        speed.ry() = 0.0;
+    }
+    
+    //doScroll(m_scrollDelta);
+    //stopScroll();
+    
+    QEasingCurve::Type easingCurveType = QEasingCurve::InOutCubic;
+    
+    QPointF scrollPos = getScrollPos() + m_scrollDelta;
+    int flickDuration = 0;
+    QPointF targetPos = calcTargetScrollPosAndDuration(speed, scrollPos, flickDuration);
+    QPointF delta = targetPos - scrollPos;
+
+    m_curEasingCurve = m_easingCurve;
+    if (clampScrollPosition(targetPos)) {
+        easingCurveType = QEasingCurve::OutBack;
+        m_curEasingCurve = m_easingCurveOvershoot;
+    }
+    m_scrollMode = ScrollHelper::KineticScrollMode;
+    qstmDebug() << "ScrollHelper::kineticScroll. initial speed: " << speed <<
+            ", delta: " << delta <<
+            ", flickDuration: " << flickDuration <<
+            ", m_scrollDelta: " << m_scrollDelta << "\n";
+    
+    m_targetScrollPos = targetPos;
+    m_kineticSpeed = speed;
+    m_curScrollPos = scrollPos;
+    m_startScrollPos = scrollPos;
+    m_scrollTotalDuration = flickDuration;
+    m_scrollDuration = 0; 
+    m_lockToX = false;
+    m_lockToY = false;
+    //m_decelVec = calcTargetScrollPosAndDuration(speed, delta, flickDuration);
+
+    //startScrollAnimation(scrollPos, targetPos, flickDuration, easingCurveType);
+}
+
+QPointF ScrollHelper::calcDeceleration(const QPointF& initSpeed, const QPointF& distance, long time)
+{
+    return 2 * (initSpeed * time - distance) / (time * time);
+}
+
+QPointF ScrollHelper::calcTargetScrollPosAndDuration(const QPointF& speed, const QPointF& scrollPos, int& duration)
+{
+    QSizeF  vpSize = viewportSize();
+    QPointF targetPos = scrollPos;
+    if (vpSize.isValid()) {
+        qreal kX = 0.0;
+        qreal kY = 0.0;
+        if (speed.x() != 0.0) {
+            kX = calcMotionFactor(speed.x());
+        }
+
+        if (speed.y() != 0.0) {
+            kY = calcMotionFactor(speed.y());
+        }
+
+        targetPos.rx() -= kX * vpSize.width();
+        targetPos.ry() -= kY * vpSize.height();
+        duration = calcScrollDuration(qMax(qAbs(kX), qAbs(kY)));
+    }
+    return targetPos;
+}
+
+
+qreal ScrollHelper::calcMotionFactor(const qreal speed)
+{
+    qreal k = 0.0;
+    qreal absSpeed = qAbs(speed);
+    if (absSpeed >= m_maxFlickSpeed) {
+        k = m_maxFlickInViewportUnits;
+    }
+    else if (absSpeed < m_maxFlickSpeed && absSpeed >= m_midFlickSpeed) {
+        k = m_midFlickInViewportUnits;
+    }
+    else {
+        k = m_minFlickInViewportUnits;
+    }
+    if (speed < 0) {
+        k = -k;
+    }
+    return k;
+}
+
+
+}