Blog

Fullscreen mode in QtQuick

In theory creating a fullscreen application in QtQuick is straightforward - you just have to call the showFullScreen() method of QQuickView instead of show():

QQuickView view;

view.showFullScreen();

The application will work fine until you press Alt+Tab to switch to another window or Windows+M to minimize all windows. It may turn out that when you try to switch back to your QtQuick application, the window won't appear, and the screen will seem frozen.

Apparently I wasn't the only one who observed such problem - I found a few unanswered questions in various Qt forums, so I decided to dig deeper into it. After some experimentation I observed that adding an animated item (such as a spinning BusyIndicator) solves the problem. But why does it make a difference? An animated item causes the window to be repainted every frame (typically 60 times per second). However, if your window only contains static elements, Qt will cleverly avoid repainting it until something changes. This optimization works fine in windowed mode, but it causes problem in fullscreen mode.

Perhaps there is another way to do that, but the most obvious solution that came to my mind was to force an update whenever the window is activated or deactivated:

QObject::connect( &view, &QWindow::activeChanged, &view, &QQuickWindow::update );

I tested this on both Windows XP and Windows 8 and it seems to fix the problem in all situations.

I'm not sure if this is a bug in Qt or simply a glitch caused by Windows itself; it may have something to do with the fact that OpenGL (which is used by QtQuick under the hood) works a bit differently in fullscreen mode, bypassing the windows composition mechanism and rendering directly to the screen, but maybe I'm wrong. It doesn't really matter as long as there is a workaround.

Another small thing that you must remember about when using QtQuick in fullscreen mode is that the contents of your window should resize to the actual screen resolution. You will almost certainly want to add the following line:

view.setResizeMode( QQuickView::SizeRootObjectToView );

This can be combined with the scalable UI mechanism that I described recently. Just design your UI for some predefined resolution (I use 1600x900 because it fits nicely in windowed mode when debugging) and then set the multiplier in the u.dp() method so that it scales everything up or down according to the actual screen resolution (the Screen QML Type will come in handy).

Filed under: Blog

Scalable UI in QtQuick

One of the first problems that anyone developing QtQuick applications encounters is the scalability of the user interface. On Windows, when you change the size of text in the Display settings in the Control Panel, the fonts become larger or smaller, but the window and controls don't. When you run the same application on Mac OS X, the font sizes are completely different than on Windows. The situation is even more complicated on mobile devices, which have a wide variety on both screen resolutions and screen sizes - you can find both a mobile with 5" display and 1920x1080 resolution and a tablet with 10" display and 1280x800 resolution.

Web developers, who face similar problems, solve them by using relative font sizes (such as 0.8em or 1.5em) and logical pixels which are scaled to physical device pixels (according to the <meta name="viewport"> tag). Exactly the same approach can be used in QtQuick applications, but it requires some additional work. After some research and experimentation, I created the following simple component which I called Units.qml:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

QtObject {
    function dp( x ) {
        return Math.round( x * Settings.dpiScaleFactor );
    }

    function em( x ) {
        return Math.round( x * TextSingleton.font.pixelSize );
    }
}

As you can see, the Units component provides two simple functions:

  • dp() - converts logical pixels to device (physical) pixels. Generally, all absolute sizes, positions, margins and other distances should always be specified using this function.
  • em() - converts relative font size to pixels. This function should be used for specifying all font sizes, but it can also be useful for some sizes and distances (e.g. the height of a button can be specified as a multiple of the font size, provided that appropriate layouts are used).

This is an example of how this component can be used:

import QtQuick 2.2
import QtQuick.Controls 1.2

Item {
    id: root

    implicitWidth: u.dp( 640 )
    implicitHeight: u.dp( 480 )

    Label {
        id: helloLabel

        x: u.dp( 20 )
        y: u.dp( 50 )

        text: "hello"
        font.pixelSize: u.em( 1.5 )
    }

    Units {
        id: u
    }
}

How does it work? The Settings object is an internal singleton of the QtQuick Controls module which provides various information. The dpiScaleFactor property basically uses the logicalDotsPerInchX property of the primary screen to calculate the scale factor. On Windows, this corresponds to the size of text in the Display settings in the Control Panel, and typical values are 1 (small) or 1.25 (normal). On Mac OS X, the dpiScaleFactor is always 1, however on Retina displays this actually corresponds to 2 physical pixels, because the OS performs additional scaling. On mobile devices you may need to use a different scale factor to ensure that the window fits the entire screen regardless of it's dimensions and DPI resolution.

Note that the dp() function uses Math.round() to ensure that the result is an integer value. Although QtQuick elements can be positioned at non-integer coordinates, this can result in ugly artifacts, so it's better to round everything.

The TextSingleton, as the name implies, is another internal singleton of the QtQuick Controls module which is simply an instance of Text component. It is used to access the pixel size of the default font. The result is also rounded in case it's used to position elements.

The Units component could also be made a singleton; in that case it wouldn't be necessary to put an instance of the Units object into every component that uses it. On the other hand, writing "u.dp()" is easier and more readable than "Units.dp()". Besides, using the singleton messes up the design mode in Qt Creator. Of course, when designing the UI, you still have to add the function calls manually and you cannot simply re-position items by dragging them in the design mode, but at least the Qt Creator displays everything correctly when the Units object is explicitly placed in the component.

Filed under: Blog

Dial and StyleItem

As I promised, I'm starting a new series of articles dedicated to QtQuick. When I started playing with QtQuick and the QtQuick Controls module, I noticed that it implements QtQuick equivalents of most widgets, and Dial is one of the few that are missing. Perhaps QDial is not a very frequently used widget, but in certain kinds of QtQuick applications, especially for mobile devices, a dial control can come in handy:

dial

A bit of googling quickly revealed that a Dial control exists as an experimental part of the QtQuick Controls module (you can find it here), but for whatever reason it never made it into the official package.

Here's my final version of Dial.qml after some tweaking:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Control {
    id: root

    width: 100
    height: 100

    property alias minimumValue: range.minimumValue
    property alias maximumValue: range.maximumValue
    property alias value: range.value
    property alias stepSize: range.stepSize
    property alias pressed: mouseArea.pressed

    property bool notchesVisible: false
    property real notchSize: 1
    property bool activeFocusOnPress: false

    activeFocusOnTab: true

    Accessible.role: Accessible.Dial
    Accessible.name: range.value

    Keys.onLeftPressed: { range.value -= range.stepSize; }

    Keys.onRightPressed: { range.value += range.stepSize; }

    RangeModel {
        id: range
        minimumValue: 0
        maximumValue: 1
        stepSize: 0
        value: 0
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        hoverEnabled: true

        onPositionChanged: {
            if ( mouseArea.pressed )
                range.value = mouseArea.valueFromPoint( mouseArea.mouseX, mouseArea.mouseY );
        }

        onPressed: {
            range.value = mouseArea.valueFromPoint( mouseArea.mouseX, mouseArea.mouseY );
            if ( root.activeFocusOnPress )
                root.focus = true;
        }

        onWheel: { range.value += wheel.angleDelta.y * range.stepSize / 120.0; }

        function valueFromPoint( x, y ) {
            var yy = root.height / 2 - y;
            var xx = x - root.width / 2;

            var angle = ( xx || yy ) ? Math.atan2( yy, xx ) : 0;

            if ( angle < -Math.PI / 2 )
                angle += 2 * Math.PI;

            var dist = 0;
            var minv = range.minimumValue;
            var maxv = range.maximumValue;

            if ( minimumValue < 0 ) {
                dist = -range.minimumValue;
                minv = 0;
                maxv = range.maximumValue + dist;
            }

            var v = ( minv + ( maxv - minv ) * ( Math.PI * 4 / 3 - angle ) / ( Math.PI * 10 / 6 ) );

            if ( dist > 0 )
                v -= dist;

            return Math.max( range.minimumValue, Math.min( range.maximumValue, v ) );
        }
    }

    style: Qt.createComponent( "DialStyle.qml" )
}

Note: for copyright purposes, the code snippets are licensed under the BSD-style open source license, just like the original code.

The code is very straightforward. The internal RangeModel object is used for handling minimum, maximum and step values (the Slider uses it too). The valueFromPoint() function converts the position of the mouse to the angle and then to the actual value. I renamed "tickmarksEnabled" to "notchesVisible" and added "notchSize" for compatibility with QDial. I also removed "wrapping", because the underlying StyleItem doesn't support it anyway. Finally, I added the missing mouse wheel and keyboard support.

What about drawing the control? The original Dial contains a StyleItem, another internal component of QtQuick Controls, which uses the application's default QStyle to paint items so that they look just like widgets. Now, a little digression. Qt5 has two separate UI modules - QtGui for handling windows and QtWidgets which implements the widgets. The former can be used without the latter, for example if your applications uses OpenGL or QtQuick or a custom painting engine and doesn't need widgets. By excluding widgets, you can reduce dependencies by a few megabytes.

The QtQuick Controls can work perfectly fine without QtWidgets. However, in that case they are drawn using built-in bitmap images, and they look the same on all platforms. On the other hand, when QtWidgets are enabled, the QtQuick Controls use the StyleItem components, which emulate the "native" look of widgets on the given platform. To be more specific, the style used by QtQuick Controls depends on whether you are using QGuiApplication or QApplication (the former is part of QtGui; the latter is part of QtWidgets). This information is deeply buried in the Qt documentation, so it can be quite confusing. If you dig into the Qt sources, you will find that the QtQuick.Controls.Styles module actually contains two subdirectories: "Base" with platform-independent styles and "Desktop" with ones that use StyleItem. The internal QQuickControlSettings class is used to select the right styles at run-time, and you can even use the "QT_QUICK_CONTROLS_STYLE" environment variable to use an entirely different set of styles.

Back to the main topic, embedding a StyleItem directly in the Dial is not a good idea, because it only works with the QtWidgets module and it's not possible to implement a custom or platform-independent style. That's why I changed the code to use the "style" property of the "Control" component, decoupling the style from the control, just like other QtQuick Controls do. The default implementation of the style is based on the original code, so it uses the StyleItem to do the job:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Style {
    property Component panel: StyleItem {
        id: styleItem

        property real visualPos: control.maximumValue - control.value

        property real granularity: {
            if ( ( control.maximumValue - control.minimumValue ) < 10 )
                return 100;
            else if ( ( control.maximumValue - control.minimumValue ) > 1000 )
                return 1 / Math.ceil( ( control.maximumValue - control.minimumValue ) / 1000 );
            else
                return Math.floor( 1000 / ( control.maximumValue - control.minimumValue ) );
        }

        elementType: "dial"
        activeControl: control.notchesVisible ? "ticks" : ""
        hasFocus: control.focus
        enabled: control.enabled
        sunken: control.pressed
        maximum: control.maximumValue * styleItem.granularity
        minimum: control.minimumValue * styleItem.granularity
        value: styleItem.visualPos * styleItem.granularity
        step: Math.ceil( control.notchSize * styleItem.granularity )

        Behavior on visualPos {
            enabled: !control.pressed
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutSine
            }
        }
    }
}

This code simply passes information from the control to the StyleItem. The only interesting part is the "granularity" property. The original code uses a hard-coded value of 100 instead. By increasing the number of steps internally, it's possible to have a nice looking, smooth animation when the Dial rotates. However, the hard-coded value of 100 doesn't always work, because the internal implementation of QStyle only paints the notches correctly if the total number of steps doesn't exceed 1000. That's why my code uses a dynamic granularity instead which works correctly in most cases.

A very simple, platform-independent implementation of the style could use a rotating image instead:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Style {
    property Component panel: Image {
        id: image

        property real visualPos: control.value

        source: control.pressed ? "dial-on.png" : "dial.png"
        rotation: 180 * 7 / 6 + ( image.visualPos - control.minimumValue ) * ( 180 * 10 / 6 )
            / ( control.maximumValue - control.minimumValue )

        Behavior on visualPos {
            enabled: !control.pressed
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutSine
            }
        }
    }
}

In order to create the notches, you can place a small Rectangle in a Repeater and use appropriate rotation.

Filed under: Blog

Hello, QtQuick!

It's been a while... As usual, this means that there's been a lot going on and I simply didn't have time to write. However, I'm planning to start a new series of articles dedicated to QtQuick. It's been over 2 months since I started my adventure with QtQuick, so I have some initial observations of its strong and weak points that I would like to share.

I think that the biggest problem with QtQuick is that it's hugely undervalued. Unfortunately, there are some political reasons behind this. Major software companies try to tie developers to their platforms and platform-specific frameworks. It's no big surprise that Nokia had to get rid of Qt when it was purchased by Microsoft, even though in theory a good cross-platform framework should be a goldmine for the company who owns it. The result is that many people unduly consider Qt dead.

The reality is that QtQuick has a lot of potential. It's more powerful than HTML5 when it comes to rich, platform-independent UI, and it's supported by powerful back-end Qt modules. It's free even for commercial use, and it's quite mature and still actively developed. It also supports both mobile and desktop platforms. Of course, there are also disadvantages. First, in order to use QtQuick you need to learn QML, which is not as easy as it seems, especially when attempting to create complex applications. Second, QtQuick (and Qt in general) is a toolkit, not a framework, so you have to lay down a lot of foundation before you can start being productive developing the actual application. Finally, the documentation is quite poor, and there is not too much open source code using QtQuick that you can peak into, so it's often difficult to find any useful information about a problem you're trying to solve. Because of that, learning QtQuick requires a bit of trial and error approach, but from what I can tell so far, it's worth the effort.

Apart from the fact that QtQuick has many ready to use UI components (including the Quick Controls), there are also additional benefits from writing the application partially in QML and partially in C++. When the UI is written entirely in QML, it can be designed by people with graphical skills, who don't necessarily need to know low level programming languages like C++. It's similar to how HTML/CSS templates are created independently from PHP (or whatever) code in web applications. Also the UI of the application can be fully tested without writing a single line of C++ code, which is great for rapid design and prototyping. To make it possible, simple stubs (mock objects) for application logic can be written in QML. When using a simple MVP or MVC pattern it's also possible to easily switch views - for example between the desktop and mobile versions of the application. I will write more about it in one of the next articles.

Filed under: Blog

Life goes on

Mister Tins is now officially in sale, which means it's officially dead, and that's not a big surprise to me. I guess it's time for a brief post-mortem summary of what went right and what went wrong. Well, the good thing is that I finished it, and it was fun, not to mention that my long time dream to make a game has come true :). I still think it was quite a good idea, and a bit underrated, but looking back I think there are at least three reasons why this whole project was doomed from the beginning:

  • Modern games are driven by artists, not by developers. It's just no longer "let's do the hard work and write the whole code and then we'll just throw in a few textures and the game will be ready". On the contrary, you take an existing engine like Unity, and then all the hard work is graphics, models, animations; the programming comes down to scripting a few events. I'm oversimplifying, but you get the point.
  • Modern games take a lot of work. Even a simple indie game requires a team of at least a few dedicated persons and often takes years, especially when people have another full time job. I spent six months, but in reality only during the first few weeks I spent significant time on this project. It's good that I started with something simple enough that I was able to finish it, but it can't compete with all the professional games in the market.
  • Another thing, which is perhaps not a strong requirement, but certainly helps, is when you can participate in various events and shows, contact other game developers, potential players and especially the press, long before your game is finished. Obviously, it's easier when you live in a large city in USA or Canada, not in some shithole in Poland…

In conclusion, if I'm ever going to get involved in another game, I'd certainly be joining an existing, dedicated team of artists, who need a coding monkey, and I'd teach myself Unity. Anyway, it's not going to happen very soon :). The good news is that now I finally have some time to release a new version of Saladin and WebIssues, work on redesigning my book, and simply read and play some games.

Also in my personal life there have been some changes recently; for example, I'm getting a divorce. I could make a similar list of things that went wrong, but I will spare you the details. Let's just say that one day you think you know someone, and then it turns out that your goals and values are so different that you simply can't go on any further. At first I was really upset because I hoped our son could have a "normal" family, especially that I din't have one, but the truth is that now I spend much more time with him than before we separated, and I'm definitely going to do everything I can to make it up for him. So I went through the whole denial-anger-regret stages and in the end I think it's for the better for all of us.

Luckily, there have been some positive changes too. I renewed an old friendship and made a new one, and that's something you can't overvalue. It also may have bigger consequences, because I think that I finally found a team of dedicated people and we have a chance to work on a very interesting and promising project - maybe it's not a game, but it's not one of those boring warehouse/financial/enterprise kind of applications either. So, life goes on…

Filed under: Blog
Syndicate content