Kinetic scrolling with Qt – the what and the how

Last month I wrote a kinetic scroller component after struggling to find a ready-to-use and reasonably licensed one. Developers that wanted to create Qt apps on Symbian didn’t have much choice when it came to this type of scrolling. With the exception of Ariya’s kinetic model, all the available options that I had found either were not usable with Qt 4.6.3 on Symbian or were GPL.

In this article I’ve tried to explain how kinetic scrolling works and have also linked to a working implementation.

Introduction

Kinetic scrolling is the combination of regular, drag-finger-on-screen scrolling with an additional movement after the finger is lifted off the screen. Based on how fast the finger was dragged on the screen, the duration, speed and deceleration of the additional movement can vary. This video shows kinetic scrolling in action on a Nokia Symbian touch screen phone. A notable difference from using a scrollbar is that when tapping and dragging, the list moves in the same direction that the user is dragging in, which is the opposite of how a scrollbar works. For a scrollbar, when one drags down the list moves up, for kinetic scrolling when one drags down, the list also moves down – it feels more natural this way.

Advanced kinetic scrolling implementations also feature „bounce” and „overshoot”: in the first case the scrollable elements bounce off the scroll area’s edge once the scroll bar reaches the end, while in the second case one can scroll past the end of the scroll area and it will exhibit drag resistance and snap back into place when released.

Recent Symbian OS touch screen phones feature kinetic scrolling prominently in the user interface, but surprisingly Qt apps do not have it enabled by default. A fully featured implementation is still in the works for Qt version 4.8, with the possibility of a back-port to versions 4.7 and 4.6. In the mean time, a proof of concept implementation is available in the Qt labs under the name Flickable. The ideas in the latter example were used as a starting point for the simple kinetic scroller described in this article – QsKineticScroller. It can be used for vertical scrolling on any descendant of QAbstractScrollArea, including QScrollArea, QListView, QListWidget and QTreeView.

Algorithm

Note: the algorithm is designed for touch screen use with fingers, but Qt works with mouse cursors and mouse events. The algorithm is presented with Qt’s terminology, with a list playing the role of scroll area.

Click & drag scrolling

Since kinetic scrolling can be viewed as the sum of two features, it can be implemented in two steps.

The first step is click & drag scrolling. It can be achieved by installing an event filter and intercepting mouse press, move and release events. When a press event is received the scrolling starts, when a move event is received the list is scrolled, and finally when a release event is received the scrolling stops. To avoid accidental clicks, all the events are blocked inside the filter function.

Consuming the mouse events is a necessary step that leads to an unpleasant problem: regular clicks are no longer registered by the target list. This issue can be avoided by making the algorithm guess when the user is clicking and dragging as opposed to when they’re just clicking to select an item in the list. In the QsKineticScroller implementation a press & release event sequence is considered a click when it has less than five move events in between. Personal experiments have shown that a finger tap is much less precise than a pointer click, with one to four move events received between the time the finger is pressed on the screen and then lifted.

By the time the scroller has figured out that the user wanted to tap the screen, the events have already been consumed. To get around this last obstacle, the scroller records the screen position of the last press event and simulates a mouse click at that position.

Finally, by tracking mouse move events and updating the scrollbar position, the scroller makes the item list follow the user’s finger. Implementing this first part of the algorithm allows Symbian Qt application users to scroll much easier than with scrollbars. The second step makes scrolling more visually interesting and easier to do, especially on longer lists.

Kinetic scrolling

For step two, the scroller continues to scroll the list automatically after the user has lifted their finger off the screen, gradually slows down and then stops. To display a pleasing effect, the scroller must decide how fast to scroll, how far to scroll and how fast to slow down.

A good starting point is „how fast to scroll”.  In physics velocity represents the direction in which and magnitude by which an object changes its position. Speed is another word for magnitude in this context. The „how fast to scroll” question can be answered by recording the cursor’s drag velocity on the screen. A simple but imprecise way to do this is to poll the cursor position at specific time intervals; the difference in positions represents the speed (measured in pixels / timer interval) and the mathematical sign of the difference represents the direction. This algorithm will give a good enough idea on whether the cursors is moving fast or slow and it is popular enough, since it can be found in other implementations such as Sacha Barber’s Scrollable canvas.

Next up is „how far to scroll”. How far is actually connected to how fast to slow down because the list is scrolled with a certain velocity and then it decelerates until it stops. Since the velocity has previously been established, the only thing left is to calculate the deceleration based on friction. In physics, kinetic friction is the resistance encountered when one body is moved in contact with another. Of course, there can be no friction between pixels, but kinetic scrolling is a simulation and one can pretend that the list items are moving over the list container and that this movement generates friction.
In reality friction is calculated based on the nature of the materials, mass, gravitational force and so on. In the simulation a numeric value is used to alter the speed of scrolling. QsKineticScroller reduces the speed by a value of 1 at certain time intervals – a very simplified model indeed, but it works.

Having determined the deceleration, „how far” the list scrolls kinetically is simply a function of the time that it needs to reach a speed of zero.

Implementation

Note: comments have been removed and some of the code has been truncated and replaced with a [...] marker.

Step-by-step source code description

QsKineticScroller is implemented as a stand-alone class and most of the implementation is hidden behind a  d-pointer. The event filter function makes QObject inheritance necessary, but with a bit of work the event machinery can be moved inside the d-pointer to make the class implementation truly opaque.

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

Moving on to the cpp file, a few variables of interest can be seen at the top. The class user can experiment with these variables to influence the scrolling behavior. Indeed, the default values have also been chosen based on experimentation. As an example, changing the timer interval will affect the scrolling speed and smoothness, while changing the friction will influence the deceleration.

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

The private implementation is aimed at hiding unneeded information from the compiler and component users.
The isMoving and isPressed variables are the simplest way to keep track of what state the scroller is in. e.g: not moving, scrolling by finger and so on. Some implementations assign an explicit state and one can go as far as using the Qt state machine implementation. The rest of the variables are described at the point of use.

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

When installing the event filter, the important thing to notice is that it has to be installed for both the scroll area and its viewport. A scroll area is not a single item, and failing to install the filter on the viewport will result in not getting any mouse events at all.

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

The largest part of the implementation lies inside the event filter. It is described in multiple blocks.

The first job of the filter is to make sure that it only works on mouse events, since the scroll area will also receive paint events, resize events and so on. When a mouse press is registered, the press point is stored in case it is needed later for a simulated click and the scroll bar position is stored so that it can be used when calculating by how much to scroll the list.

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

This code block does the click versus click & drag differentiation. If it weren’t for it, the scroller would ignore legitimate clicks. Once the scroller has established that the user is indeed dragging on the screen, it starts a timer that calculates the drag speed. lastMouseYPos will be used later in the speed 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.start(gTimerInterval);
      }
   }
   [...]

When the user lifts their finger off the screen a mouse release event shall be received. This is where the click versus drag differentiation makes a difference: d->isMoving will be false for clicks, but true for drags. The simulated click will be swallowed by the next filter call unless the filter is told to ignore 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 mentioned in the algorithm description, both speed calculation and deceleration are done at certain time intervals. This implementation uses a single timer for both.
The kinetic scrolling happens in the else branch: since the speed measurement is imprecise it is restricted to a more reasonable value at first. Then it is adjusted by the friction value until it reaches a minimum boundary, which means that kinetic scrolling should stop.

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()->value();
      d->scrollArea->verticalScrollBar()->setValue(scrollBarYPos - d->velocity);
   }
   else
      d->stopMotion();
}

Where to get it, how to use it

The source code can be downloaded from my BitBucket repo, together with all the other Qt classes.

It’s licensed under a BSD license, so use it however you like to:

  • Unzip the downloaded file.
  • Add the unzipped cpp and h files to a Qt pro file.
  • Create a scroller object instance. It’s a good idea to make the scroller a child of the dialog or window that also contains the target scroll area.
  • Call the enableKineticScrollFor function with the target scroll area as a parameter.
  • If the scroll area is a list view or list widget, it must have the scroll mode set to ScrollPerPixel.

That’s it, the scroller will take care of everything else.

What’s wrong with C++

…from the point of view of someone that actually uses it? The bits from my last post haven’t even dried up yet, and another fascinating rant about C++ has popped up, this time from Zed Shaw.

What’s interesting about these rants is that the ranter isn’t really using C++. They’re C hackers, or they program mostly in Python, or they used C++ at the beginning of the 90s. So I thought it would be interesting to write about my experience with C++.

First, a little background info…

I started learning C++ about 10 years ago, first the C part and then the ++ part. Nothing serious, just toying around with small projects to learn and have fun. I didn’t use it professionally until about four and a half years ago, when I started working on a large scale C++ project. Since then I’ve used it almost daily.

Two and a half years ago I came across Qt almost by accident: the large scale project that I was working on was being ported to Linux and the port was enabled by Qt. Since then, I’ve used Qt almost daily too.

Right now I’m working on a  Qt C++ project that’s about 25KLOC in size and growing. It’s a multi-platform, threaded client-server and it’s a line of bussiness app if you’d believe it. Working on it is fun, and I’m glad that I picked Qt and C++.

Okay, enough with the history, what about C++ and its warts?

Issues that bother me in practice

  • Compile times and the include dependency specification: this is a problem that every C++ programmer will have sooner or later. It can be delayed and mitigated with a good physical design, precompiled headers and distributed compilers/linkers, but it can’t be solved without throwing away compatibility to existing code completely.
  • The small standard library: not a big issue in itself, since you can find other libraries for anything you might want to do, but it’s unpleasant to install all those libraries and make sure that they work together nicely. Using more libraries instead of a large standard library also means that you have to wait on each library vendor to port the library to different OSes/compilers, upgrade it and so on.
  • lack of reflection: I like Qt’s moc and I miss it in standard C++. I also wish it had more features.
  • lack of type inference: even with typedefs I’m typing more than I have to. C++0x will thankfully fix this.
  • localization & internationalization support in the standard library: the implementation is complicated and difficult to use. After browsing through C++ IOstreams and locales I’ve decided to just use Qt and not worry about it.
  • template error messages: they’re long and hard to understand. I usually parse a template error message in about 10-20 seconds depending on the difficulty. The worst offenders are metaprogramming-heavy libraries such as Boost Spirit. In that case, I try to analyze the code first and see what might be wrong. STLFilt can help.
  • functional programming support: this will get better in C++0x, but I can’t use C++0x now, at least not on all platforms and with the other libraries I’m using. Functors and free functions are cumbersome to use with algorithms.
  • tools and parsing: C++ has access to solid, mature tools, but those tools are much harder to develop than for other languages and generally don’t have as many features.

Issues that don’t bother me at all, but are regularly brought up in rants

  • memory management: never a problem for me. The last time I’ve had a memory leak was about two years ago and it got caught at code review. C++ has good support for memory management and good tools that can be used for runtime debugging. It might as well have GC, I wouldn’t be able to tell the  difference.
  • memory corruption: memory corruption can be both exciting and scary. Exciting because you have to go down to the memory address level and do some sleuthing, scary because it can take days or weeks to find some bugs. I usually don’t sweat these bugs, they were very rare in the code I’ve worked on, and when they occurred they were easily found. e.g: the last one I’ve solved in maybe 30 seconds, the tools have gotten very good. The worst I’ve encountered was a problem with an embedded STL in a large project that took a week to figure out and only because of my lack of experience at that time. Some people love debugging.
  • multi-threading: it is hard in most languages. The trick is using higher-level libraries such as QtConcurrent, Intel’s TBB or OpenMP. Sure, if you’re doing PThreads it’s going to be painful.
  • string formatting and string support: I generally don’t use iostreams or std::string, but when I did I found them acceptable. Unicode support was the biggest issue, but “there’s a applib for that”. Now I use Qt’s strings and streams; they’re as easy to use as Python as far as I’m concerned. Don’t want Qt? Use boost::format or any other of the nice formatting libraries.
  • templates in general: I use them when needed to break dependencies, write generic code, etc. I try to keep template code manageable, without going into the metaprogramming stuff; they are one of my favourite C++ features.
  • exception rules: I don’t see any problem with exceptions in C++ as long as you’re using RAII. Other languages (e.g.: Python) use them extensively for error handling.
  • STL & Boost: I’ve used both for a lot of time and I find them very useful. Contrary to popular belief, most templates don’t use metaprogramming techniques. You don’t have to install Boost, it’s mostly headers. A large part of Boost became TR1 so it should even ship with your compiler now.
  • const: This one came from Zed, I don’t think I’ve seen that many people complain about const. Personally, I constify everything that I can, even const type* const.
  • everybody learns a different 10% of C++: strange, because I use all of it and always have. It’s not that hard either, I’m not a genius… Sane projects have coding guidelines and architectural documents. If your teammate can waltz right in with a template metaprogram or is generally macro-happy and it doesn’t raise eyebrows at code review, you have bigger problems than using C++. I’ve had to fix two C++ projects so far; my only job was to do what it takes to make the programs work and ship them to the customer. Most problems were memory leaks, superfluous use of casts, functions that didn’t compile on all platforms and so on. Fixable. What I couldn’t fix – and had to patch up as best as possible – were issues with the program logic and architecture. Those were not caused by C++.

Ok, but what about…

You probably have your favourite C++ misfeatures.  Odds are that they don’t matter than much in practice, or workarounds are available. Maybe I just got used to them and consider them a C++ tax.

C++ is thriving in 2010

Another Linus Torvalds C++ post is making the rounds on the interwebs. Open any site like reddit or Hacker News and you’re bound to find someone cursing at C++.

Despite them – in the real world – more people are using C++ and the language itself is getting better:

  • Compiler and tool support is better than ever.
  • The C++0x FC draft is out, and the big C++ compilers already implement a significant part of it. It brings a larger standard library, better performance, more functional programming, easier syntax for some operations and improves the type system.
  • With Qt, C++ has access to the equivalent of the .NET libraries or the Java class library. Free and open source.
  • It’s available on even more platforms: Symbian and Maemo/MeeGo, and even Android or iPhone.

C++’ers gonna C++ and haters gonna hate… :)

More Qt on S60: building an app

I’ve been working on a Qt Symbian S60 app to familiarize myself with Symbian development. After struggling with the build system for about an hour, a passage from Dr. Kenneth Kamler‘s book – Surviving the extremes – popped into my mind:

While crossing an aluminium ladder placed horizontally to bridge a crevasse in the ice, Passang had slipped and fallen 80 feet, wedging himself headfirst between the narrowing walls of ice at the bottom.

Working quickly, a combined team of Spanish and Nepalese climbers managed to get a rope around his waist and hoist him back up to the surface. But by then he had been refrigerated upside down for nearly half an hour.

Fascinating book, I cannot recommend it highly enough. To prevent anyone else from slipping on a makefile and wedging themselves between a library and a .sisx file, I’m going to make the following recommendation about building Qt apps for Symbian: drop all your sources into one folder, build, deploy and run. At this point it seems to be very difficult to build and deploy a SUBDIRS project:

  • The biggest problem is actually an older issue with multi-library projects in Qt: PRE_TARGETDEPS. LIBS works fine, but you can’t change the destination diractories for the target files nor the lib search paths. This means that your PRE_TARGETDEPS should point into the SDK’s build directory -> compiler directory -> build configuration. There are too many path combinations!
  • Even if you somehow managed to make library dependencies work, you can’t create a sis package for SUBDIRS projects.

Not being able to organize source code into libraries will not be such a big hindrance for Symbian-only programs, but it does make it more difficult to manage a cross-platform solution, which is what I was after.