diff -r 6aeb7a756187 -r 3c88a81ff781 ginebra2/ScrollHelper.cpp --- /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 +#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; +} + + +}