Kinetic scrolling with Qt – the what and the how


Heres an fascinating article from Code Improved that explains how kinetic scrolling works and can also be linked to a working implementation. Supply code shall be revealed quickly as properly.

A notable distinction from utilizing a scrollbar is that when tapping and dragging, the record strikes in the identical path that the consumer is dragging in, which is the other of how a scrollbar works. For a scrollbar, when one drags down the record strikes up, for kinetic scrolling when one drags down, the record additionally strikes down – it feels extra pure this manner.
Superior kinetic scrolling implementations additionally function „bounce” and „overshoot”: within the first case the scrollable parts bounce off the scroll space’s edge as soon as the scroll bar reaches the top, whereas within the second case one can scroll previous the top of the scroll space and it’ll exhibit drag resistance and snap again into place when launched.
Current Symbian OS contact display telephones function kinetic scrolling prominently within the consumer interface, however surprisingly Qt apps wouldn’t have it enabled by default. A totally featured implementation remains to be within the works for Qt model 4.8, with the potential for a back-port to variations 4.7 and 4.6. In the interim, a proof of idea implementation is accessible within the Qt labs beneath the identify Flickable. The concepts within the latter instance had been used as a place to begin for the straightforward kinetic scroller described on this article – QsKineticScroller. It may be used for vertical scrolling on any descendant of QAbstractScrollArea, together with QScrollArea, QListView, QListWidget and QTreeView.

Algorithm

Word: the algorithm is designed for contact display use with fingers, however Qt works with mouse cursors and mouse occasions. The algorithm is introduced with Qt’s terminology, with a listing taking part in the function of scroll space.

Click on & drag scrolling

Since kinetic scrolling could be seen because the sum of two options, it may be carried out in two steps.
Step one is click on & drag scrolling. It may be achieved by putting in an occasion filter and intercepting mouse press, transfer and launch occasions. When a press occasion is obtained the scrolling begins, when a transfer occasion is obtained the record is scrolled, and eventually when a launch occasion is obtained the scrolling stops. To keep away from unintentional clicks, all of the occasions are blocked contained in the filter perform.
Consuming the mouse occasions is a mandatory step that results in an disagreeable downside: common clicks are not registered by the goal record. This situation could be prevented by making the algorithm guess when the consumer is clicking and dragging versus once they’re simply clicking to pick out an merchandise within the record. Within the QsKineticScroller implementation a press & launch occasion sequence is taken into account a click on when it has lower than 5 transfer occasions in between. Private experiments have proven {that a} finger faucet is far much less exact than a pointer click on, with one to 4 transfer occasions obtained between the time the finger is pressed on the display after which lifted.
By the point the scroller has found out that the consumer needed to faucet the display, the occasions have already been consumed. To get round this final impediment, the scroller information the display place of the final press occasion and simulates a mouse click on at that place.
Lastly, by monitoring mouse transfer occasions and updating the scrollbar place, the scroller makes the merchandise record comply with the consumer’s finger. Implementing this primary a part of the algorithm permits Symbian Qt software customers to scroll a lot simpler than with scrollbars. The second step makes scrolling extra visually fascinating and simpler to do, particularly on longer lists.

Kinetic scrolling

For step two, the scroller continues to scroll the record robotically after the consumer has lifted their finger off the display, regularly slows down after which stops. To show a delightful impact, the scroller should determine how briskly to scroll, how far to scroll and how briskly to decelerate.
place to begin is „how briskly to scroll”.  In physics velocity represents the path wherein and magnitude by which an object modifications its place. Velocity is one other phrase for magnitude on this context. The „how briskly to scroll” query could be answered by recording the cursor’s drag velocity on the display. A easy however imprecise means to do that is to ballot the cursor place at particular time intervals; the distinction in positions represents the velocity (measured in pixels / timer interval) and the mathematical signal of the distinction represents the path. This algorithm will give a ok thought on whether or not the cursors is shifting quick or sluggish and it’s in style sufficient, since it may be present in different implementations akin to Sacha Barber’s Scrollable canvas.
Subsequent up is „how far to scroll”. How far is definitely related to how briskly to decelerate as a result of the record is scrolled with a sure velocity after which it decelerates till it stops. Because the velocity has beforehand been established, the one factor left is to calculate the deceleration based mostly on friction. In physics, kinetic friction is the resistance encountered when one physique is moved involved with one other. In fact, there could be no friction between pixels, however kinetic scrolling is a simulation and one can faux that the record gadgets are shifting over the record container and that this motion generates friction.
In actuality friction is calculated based mostly on the character of the supplies, mass, gravitational pressure and so forth. Within the simulation a numeric worth is used to change the velocity of scrolling. QsKineticScroller reduces the velocity by a worth of 1 at sure time intervals – a really simplified mannequin certainly, however it works.
Having decided the deceleration, „how far” the record scrolls kinetically is solely a perform of the time that it wants to succeed in a velocity of zero.

Implementation

Word: feedback have been eliminated and among the code has been truncated and changed with a […] marker.

Step-by-step supply code description

QsKineticScroller is carried out as a stand-alone class and a lot of the implementation is hidden behind a  d-pointer. The occasion filter perform makes QObject inheritance mandatory, however with a bit of labor the occasion equipment could be moved contained in the d-pointer to make the category implementation actually opaque.

class QsKineticScroller: public QObject
{
[...]
protected:
   bool eventFilter(QObject* object, QEvent* occasion);
[...]
personal:
   QScopedPointer<QsKineticScrollerImpl> d;
};

Shifting on to the cpp file, just a few variables of curiosity could be seen on the prime. The category consumer can experiment with these variables to affect the scrolling habits. Certainly, the default values have additionally been chosen based mostly on experimentation. For example, altering the timer interval will have an effect on the scrolling velocity and smoothness, whereas altering the friction will affect the deceleration.

static const int gMaxIgnoredMouseMoves = 4;
static const int gTimerInterval = 30;
static const int gMaxDecelerationSpeed = 30;
static const int gFriction = 1;

The personal implementation is aimed toward hiding unneeded info from the compiler and element customers.
The isMoving and isPressed variables are the best technique to preserve monitor of what state the scroller is in. e.g: not shifting, scrolling by finger and so forth. Some implementations assign an express state and one can go so far as utilizing the Qt state machine implementation. The remainder of the variables are described on the level of use.

class QsKineticScrollerImpl
{
[...]
   bool isPressed;
   bool isMoving;
   QPoint lastPressPoint;
   int lastMouseYPos;
   int lastScrollBarPosition;
   int velocity;
   int ignoredMouseMoves;
   int ignoredMouseActions;
   QTimer kineticTimer;
};

When putting in the occasion filter, the essential factor to note is that it needs to be put in for each the scroll space and its viewport. A scroll space will not be a single merchandise, and failing to put in the filter on the viewport will lead to not getting any mouse occasions in any respect.

void QsKineticScroller::enableKineticScrollFor(QAbstractScrollArea* scrollArea)
{
[...]
   scrollArea->installEventFilter(this);
   scrollArea->viewport()->installEventFilter(this);
   d->scrollArea = scrollArea;
}

The biggest a part of the implementation lies contained in the occasion filter. It’s described in a number of blocks.
The primary job of the filter is to be sure that it solely works on mouse occasions, for the reason that scroll space may even obtain paint occasions, resize occasions and so forth. When a mouse press is registered, the press level is saved in case it’s wanted later for a simulated click on and the scroll bar place is saved in order that it may be used when calculating by how a lot to scroll the record.

bool QsKineticScroller::eventFilter(QObject* object, QEvent* occasion)
{
   const QEvent::Kind eventType = event->kind();
   const bool isMouseAction = QEvent::MouseButtonPress == eventType
      || QEvent::MouseButtonRelease == eventType;
   const bool isMouseEvent = isMouseAction || QEvent::MouseMove == eventType;
   if( !isMouseEvent || !d->scrollArea )
      return false;
   [...]
   change( eventType )
   {
      case QEvent::MouseButtonPress:
      {
         d->isPressed = true;
         d->lastPressPoint = mouseEvent->pos();
         d->lastScrollBarPosition = d->scrollArea->verticalScrollBar()->worth();
   [...]

This code block does the press versus click on & drag differentiation. If it weren’t for it, the scroller would ignore official clicks. As soon as the scroller has established that the consumer is certainly dragging on the display, it begins a timer that calculates the drag velocity. lastMouseYPos shall be used later within the velocity calculation.

case QEvent::MouseMove:
{
   if( !d->isMoving )
   {
      if( d->ignoredMouseMoves < gMaxIgnoredMouseMoves )
         ++d->ignoredMouseMoves;
      else
      {
         d->ignoredMouseMoves = 0;
         d->isMoving = true;
         d->lastMouseYPos = mouseEvent->pos().y();
         if( !d->kineticTimer.isActive() )
         d->kineticTimer.begin(gTimerInterval);
      }
   }
   [...]

When the consumer lifts their finger off the display a mouse launch occasion shall be obtained. That is the place the press versus drag differentiation makes a distinction: d->isMoving shall be false for clicks, however true for drags. The simulated click on shall be swallowed by the subsequent filter name until the filter is instructed to disregard it.

case QEvent::MouseButtonRelease:
{
[...]
   if( !d->isMoving )
   {
      QMouseEvent* mousePress = new QMouseEvent(QEvent::MouseButtonPress,
      d->lastPressPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
      QMouseEvent* mouseRelease = new QMouseEvent(QEvent::MouseButtonRelease,
      d->lastPressPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
      d->ignoredMouseActions = 2;
      QApplication::postEvent(object, mousePress);
      QApplication::postEvent(object, mouseRelease);
      [...]
}

As talked about within the algorithm description, each velocity calculation and deceleration are executed at sure time intervals. This implementation makes use of a single timer for each.
The kinetic scrolling occurs within the else department: for the reason that velocity measurement is imprecise it’s restricted to a extra cheap worth at first. Then it’s adjusted by the friction worth till it reaches a minimal boundary, which implies that kinetic scrolling ought to cease.

void QsKineticScroller::onKineticTimerElapsed()
{
   if( d->isPressed && d->isMoving )
   {
      const int cursorYPos = d->scrollArea->mapFromGlobal(QCursor::pos()).y();
      d->velocity= cursorYPos - d->lastMouseYPos;
      d->lastMouseYPos = cursorYPos;
   }
   else if( !d->isPressed && d->isMoving )
   {
      d->velocity = qBound(-gMaxDecelerationSpeed, d->velocity,    gMaxDecelerationSpeed);
      if( d->velocity> 0 )
         d->velocity -= gFriction;
      else if( d->velocity < 0 )
         d->velocity += gFriction;
      if( qAbs(d->velocity) < qAbs(gFriction) )
         d->stopMotion();
      const int scrollBarYPos = d->scrollArea->verticalScrollBar()->worth();
      d->scrollArea->verticalScrollBar()->setValue(scrollBarYPos - d->velocity);
   }
   else
      d->stopMotion();
}

The place to get it, the way to use it

The supply code could be downloaded from my BitBucket repo, along with all the opposite Qt courses. I’ll publish it as a separate obtain as quickly as I can.
It’s licensed beneath a BSD license, so use it nonetheless you prefer to:

  • Unzip the downloaded file.
  • Add the unzipped cpp and h recordsdata to a Qt professional file.
  • Create a scroller object occasion. It’s a good suggestion to make the scroller a toddler of the dialog or window that additionally comprises the goal scroll space.
  • Name the enableKineticScrollFor perform with the goal scroll space as a parameter.
  • If the scroll space is a listing view or record widget, it will need to have the scroll mode set to ScrollPerPixel.

That’s it, the scroller will care for all the things else.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles