But what about swipe gestures for arbitrary widgets, which do not have a default implementation for scrolling, e.g. for a tab widget? Let’s consider the following use case:

Having a tab widget, we would like to change the current tab on horizontally swiping left or right. The swipe should also be animated, so that the swipe will look like if the current tab widget is scrolled out of sight, until it is replaced by the left or right neighbor tab widget. Additionally a tab widget, which is another scroll area, should get kinetically scrolled for vertical swipes.

Can we do this with QScroller? Principially, we can, but the scroller just scrolls any given QObject by sending scroll resp. gesture events to the object. Also changing the current tab upon a left or right swipe requires to subclass the tab and to specify its reaction upon receiving a scroll event from the scroller by reimplementing the event() method to catch any QScrollEvent and react accordingly.

Is that easy? No, it is not, because one needs to understand the implementation of QScroller to see which events eventually are generated in which case and at which point in time and how one should react on them. I was reading the implementation of QAbstractScrollArea and QScrollArea and did not get much further. Reading the QScroller examples wasn’t helpful either because they just show how to scroll a scroll area. What is more, it is not apparent how multiple scrollers for the horizontal and vertical swipes, which have different receiving widgets, would interact.

So, I decided to make my own dedicated kinetic scroller, that is flexible to scroll and also is able to detect swipes to let arbitrary widgets react on them. It basically is an event filter for mouse events:

on MousePressEvent:

remember actual mouse position

consume the event, so that a mouse click is not generated before we know whether or not it is a swipe

on MouseMoveEvent:

calculate the relative mouse position to the remembered position

if the relative position exceeds a threshold, determine if its a left, right, down or up swipe

on MouseReleaseEvent:

if there was a swipe detected send an according signal

else send a mouse click event

if the speed of the mouse movement (in pixels/s) exceeds a threshold, trigger a repeating timer, that drives the kinetic scrolling

let fps be the frame rate of the kinetic scrolling

then the trigger period of the timer is 1/fps

for each timeout of the timer:

send kinetic scroll signals that continue the mouse motion with the last recognized speed (in pixels/s)

damp the speed with a damping coefficient

if the speed is less than 0.5px/s*fps stop the kinetic motion and the timer

The above scheme is easy enough to understand and implement, so I spare the details on that. The difficult part is about which object to install the above event filter on:

Supposed we want to make a QWidget react on a swipe then we create an instance of the above event filter, install the event filter on the widget and connect the swipe signal of the filter with a slot of the widget. Does that work? No, it does not work, because Qt propagates mouse events directly to the widget that has been clicked at. This means that the top-level widget’s slot does not receive the mouse events it’s ought to filter, because its children are receiving them instead. A solution would be to install filters on all visible widgets, but then each widget needs to be sub-classed, an infeasible solution.

The solution is to install an event filter on the qApp object, since all events are passed through this object. Then the filter must determine if the event is meant to be delivered to the particular widget in question. In that case the widget receives the swipe events.