The QML State Machine

The Qt 5.4 release is coming closer and it brings a whole lot of nice things: high DPI support, Qt WebChannel, and much more. One of these very cool, yet maybe slightly inconspicuous, new features is the QML State Machine. It brings a fully featured state machine to the QML world, which is a finite automaton consisting of states, transitions that define on which event to transit from one state to another, and event handlers that are called when a stated is left or entered.

Despite its simplicity, a state machines features a very powerful tool to ease the creation and maintenance of complex QtQuick UIs. In a former post I very briefly talked about the rewrite of Rocs’ visual graph editor in QtQuick. A major problem of this rewrite was that it became (even with only basic functionality) a very complex beast to handle. To give a little bit of context first, the visual graph editor currently consists of four mutual exclusive actions that can be performed at a scene: select some and/or move the selected nodes, connect two nodes by an edge, create a new node, delete scene elements by clicking at them.

Now, I played around with rewriting this functionality by using state machines, mostly to figure out how effective state machines are for this use case. For my experiment I ported the first two of these actions, whereas activating an action simply starts a corresponding state machine. For the select-move action the state machine code looks something like this (compared to the original code that lives in the editorStateMachine branch in the Rocs repository for now, functionality is simplified a little bit to improve readability):

import QtQml.StateMachine 1.0 as DSM
...
DSM.StateMachine {
    id: dsmSelectMove
    initialState: smStateIdle
    running: true
    DSM.State {
        id: smStateIdle
        DSM.SignalTransition {
            targetState: smStateMoving
            signal: scene.onMousePressed
            guard: scene.mousePressPerformedAtNode
        }
        DSM.SignalTransition {
            targetState: smStateSelecting
            signal: scene.onMousePressed
            guard: !scene.mousePressPerformedAtNode
        }
        DSM.SignalTransition {
            signal: scene.onMouseClicked
            onTriggered: {
                scene.clearSelection()
            }
        }
    }
    DSM.State {
        id: smStateSelecting
        DSM.SignalTransition {
            signal: scene.onMousePositionChanged
            onTriggered: {
                selectionRect.visible = true
                selectionRect.to = scene.currentMousePosition
            }
        }
        DSM.SignalTransition {
            targetState: smStateIdle
            signal: sceneAction.onMouseReleased
        }
        onEntered: {
            scene.clearSelection()
            selectionRect.from = scene.lastMousePressedPosition
        }
        onExited: {
            selectionRect.visible = false
        }
    }
    DSM.State {
        id: smStateMoving
        DSM.SignalTransition {
            targetState: smStateIdle
            signal: scene.onMouseReleased
        }
        onEntered: {
            scene.startMoveSelectedNodes()
        }
        onExited: {
            scene.applyMoveSelectedNodes()
        }
    }
}

The code itself probably can live without further comments, except that scene is the component containing all elements and that the scene re-transmits all mouse events which happen there. Furthermore, the selectionRect element is a rectangle that is displayed whenever the mouse is pressed at the scene and moved in some direction (hence, a selection rectangle 🙂 ). If you know some QtQuick you probably can estimate how this functionality is implemented in a typical way: you have to create a lot of bindings, properties that store state information, and maybe actually some QtQuick states — with the result of having the logic distributed over the whole QML file.

This problem mostly vanishes when using state machines, and furthermore, by using state transitions one can drastically improve readability. Try it yourself! And thanks to the devs who implemented this cool tool!