Mapping mouse position to angles (QtQuick example)

Hi there! This post is to share with you something I hacked these days.

The use case

Image you are writing a widget or a piece of an application interface and you want to rotate something based on a mouse click. For instance, you may have a widget that looks like a rotating knob, or a round analogue gauge.

For this post, let’s use a car dashboard speedometer. What I want here is to click somewhere on the gauge and have the needle rotated towards the current mouse position. Check this video of how it should work.

The problem

In Qt, rotating a widget is easy in the context of QtQuick or QGraphicsView, just define the “rotation” property to the desired angle and you are all set. The problem here is to find out what the desired angle is.

Here I want to find out which is the angle formed between a given mouse position and centre of the gauge, or the origin of the needle rotation. Getting the mouse position is easy in QtQuick, simply watch the “mouseX” and “mouseY” properties of a MouseArea. But what about converting that to an “angle” ?

In mathematical terms what I want is to convert the mouse position from linear coordinates (X,Y pair) to circular coordinates (angle, radius pair) with the centre of the gauge being the origin of my plane. But designers using QtQuick really don’t want to deal with that.

The solution

I made a Qt class meant to be used as a “model” or “helper” for both C++ and QtQuick applications. This class hides the trigonometry required for that conversion by exporting a set of properties, namely:

  • originX
  • originY
  • x
  • y
  • angle
  • radius

So how would we use it in our example?

  1. Set “originX” and “originY” to the centre of the gauge. That’s the origin of the needle, right?
  2. Set “x” and “y” to the current mouse position. In QML that would be “x: mouseArea.mouseX” and “y: mouseArea.mouseY”.
  3. Read the value of “angle“. In QML that would be something like  “needleRotation.angle: angleModel.angle”

Then QtQuick data binding system will handle everything else. Every time the mouse moves, the properties “x” and “y” will be updated, the model will then recalculate the values of “angle” and “radius” causing the needle to update itself.

Below you can see a diagram of that behaviour. Note that the opposite also works, that is, once I explicitly set an “angle” and “radius” in the model, the values of “x” and “y” are automatically updated.

Relationship between AngleModel and different coordinates

Relationship between AngleModel and different coordinates

The Source

To grab the code go to Qt Components Gitorious and check:

  • examples/clickable-dial
  • examples/angle-model

Hope you enjoy it. Comments are always welcome.

Arbitrary and/or animated opacity masks to QGraphicsItems with QGraphicsEffect

Last week I dropped by this neat image slider and was wondering how could that be done in Qt (C++ or Quick). And basically what we need is an animated opacity mask, or “clipper”.

While QGraphicsView and QML already provide rectangular clipping, it does not support clipping to arbitrary forms and opacity, something we would need in order to implement the “moving bars” transition used in the image slider I mentioned above.

I expected QGraphicsEffect to be suitable for that usecase but I had no experience with it, so I decided to try and make a proof of concept effect class that would clip an arbitrary QGraphicsItem to the shape of a bouncing circle.

A more useful class would be one that received as parameter a mask image instead of the “hard-coded” bouncing circle, but I’ll let that as an exercise to those reading :-)

Code is attached, I complied against Qt 4.6 and it works fine. Just run:

# qmake
# make
# ./effect

l class would be one that received as parameter a mask image instead of the “hard-coded” bouncing circle, but I’ll let that as an exercise to those read.

Download and try it here.

UPDATE:  If your default graphics system is not raster, ie. if you get errors like:

QPainter::setCompositionMode: Blend modes not supported on device

Try running the example as follows:
# ./effect -graphicssystem raster

QPainter::setCompositionMode: Blend modes not supported on device