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.

The QGraphicsAnchorLayout

In the last post I mentioned that the new QGraphicsLayout, to be released in Qt 4.6, is already available in the public Qt git repository.

Now I’d like to give some overview of how it works and why we’re so excited about it.

To do that I’ll talk about the layouts that already existed in Qt, the Anchor Layout basic usage and finally, the situations that are better handled by the latter. From now on assume I’m talking about the QGraphicsView framework and where you read “widget” or “layout”, understand that I refer to QGraphicsWidgets and QGraphicsLayouts.

Existing QGraphicsLayouts

As of Qt 4.5, the exiting layouts available were the Linear and the Grid layouts. With those, as their names imply, users could have their widgets arranged side-by-side or in a spreadsheet-like fashion, with support for merged cells included.

It was not possible however to lay items out of the screen or overlap them, total or partially. Besides that, some cases would require several layouts to be nested in order to achieve the desired result.

The Anchor Layout concept

Previous layouts work like this: the linear layout would require you to add items to the layout in a given sequence and then those would be arranged side by side (or one on top of the other); in the grid layout the concept is similar, but items are now associated to X,Y indexes to represent the row and column where they should be inserted.

The Anchor Layout breaks with this paradigm in the sense there is no pre-defined concept of how should the items be arranged. The final position of each item is totally dependent on how you set the anchors. In other words, the anchor layout is a very, very flexible layout.

While most layouts are flexible to some extent and allow you to configure parameters like spacing between items or margins, the Anchor Layout brings flexibility to a new level. It was also our goal to make the setup process something easy. We didn’t want to expose hundreds of abstract parameters to the user, instead, we wanted to provide a high-level API and concept. Besides making the coding more rewarding, that would make it easier to translate into C++ code, the UI designers ideas and way of thinking. That’s what the anchors are about.

In this layout, each item has six anchorage points, which are Left, Right, Top, Bottom and the Horizontal and Vertical centers. The user is then responsible for connecting the anchorage points of different items to the others, and to the anchorage points of the layout itself. These connections are made through the use of the so called Anchors, that have well defined sizes.

Then, based on:

– the anchors created,
– the size hints of each item, and
– the size of the layout itself

the layout engine calculates the appropriate geometry of each widget.

Real world Anchor Layout use cases

Here at openBossa we have been involved on the development of rich-UI applications for long, and it was with that experience in mind that we started contributing with the guys from Qt on the development of the QGraphicsAnchorLayout.

Bellow you will find a few use cases of ours that were not handled by existing layouts and work fine with anchors.

Transition between screens

We often want to make a transition between two screens of the same application. Imagine the user performs an action that should hide the current screen and show another one.

In a rich UI application we probably want to animate such transition, one of the alternatives is to apply a fade-out or blur to the current screen until the new screen is seen. To achieve that, we want both screens to occupy the same position on the screen, the old one being in front of the new one (higher Z value). Then it’s just a matter of changing the opacity of the screen on the front and voilà, we have the effect.

So, how do we use a standard layout to make one widget (next screen) occupy the same position as another one (current screen)? We couldn’t do that with existing layout. With anchor is just a matter of anchoring both screens edges to the layout edges (right of item to right of layout, left to left, and so forth).

Usage of AnchorLayout to set two swallows on the same place

Usage of AnchorLayout to set two swallows on the same place.

Off-screen items

Another common trick used in UI animations is to have an off-screen item get “inside”  the screen or vice-versa. For instance, imagine a toolbar on the top of the screen that shows or hides itself in an animated way, like “walking into” or “out” of the screen. The idea here is to have two states (QStates maybe?) where the toolbar widget is on-screen in one of them, and off-screen in the other.

Again, with standard layouts we could not create this “off-screen” state. With anchors it’s just a matter of anchoring for instance, the bottom of the toolbar to the top of the layout, thus, the toolbar will sit right above the layout, ready to enter the screen (in an animated way) when necessary.

Usage of AnchorLayout to set a toolbar on or off-screen.

Usage of AnchorLayout to set a toolbar on or off-screen.

Organic setup

There are also those cases where we have several items arranged on the screen in a very organic way as drawn by the UI designers. Often it’s not practical (or possible) to define that arrangement in terms of several nested layouts. However, with a single Anchor Layout, all items can be arranged precisely.

Try it

There are other situations where such a flexible layout is helpful, however I hope the cases above exemplify the kind of problem we are trying to solve. Please, give it a try, let us know of your impressions. We really appreciate all kinds of feedback, either on usage, performance, API, features, and so forth. All of these will be taken into account so you can have a nice new tool ready for use in Qt 4.6.

Enjoy 🙂