Dec 6, 2010

A cute quick colorpicker


Declarative user interface programming is slowly becoming a mainstream, being backed by technologies like WPF, JavaFX, Flex and others.

And now Qt is catching up with Qt Quick and QML.

While the basic ideas behind it are pretty similar to what we've already seen in those other technologies, there are several things which potentially can make it stand out, not the last one being the fact that Qt is open-source and multi-platform and that Qt Quick is naturally built on top of the solid, tried and true Qt Framework.

There is hardly a better way of getting the impression of some technology than just trying to make something functional with it, struggling your way through the documentation/examples and counting the number of hoops to jump through in the process.

So let's try and make a simple QML colorpicker control from scratch, just for the hell of it.

The first step is, obviously, downloading and installing Qt SDK (at the time of writing it was Qt Creator 2.0.1 with Qt Libraries 4.7.0). Run it and create a new QML project, named "colorpicker" (File-> New File or Project... -> Qt Quick Project -> Qt Quick UI -> Choose...).

After we're done, the final code can be found here.

Act 1

Scene 0

Enter Rectangle, Gradient.

It all starts with a simple square, filled with a white-red gradient (so it does not appear completely dull).

QML script essentially descibes a hierarchy of objects, with a single root element. Every object can have nested children objects, as well as a list of property values.

That's pretty common way of specifying what the UI's logical structure is (e.g. XAML does the same thing via XML with its tags and attributes). The "is" part here is essential, because that's where "declarative" comes from (as opposed to imperative code, which describes what something "does").

We make a Rectangle as a root element, tell that its "width" and "height" properties should be 120 pixels, and it's "gradient" property (which is by default a vertical gradient, from top to bottom) is itself a Gradient object, going from "white" to "red" color (those colors are in turn described inside GradientStop objects, its children):
import Qt 4.7
Rectangle {
    width: 120; height: 120
    gradient: Gradient {
        GradientStop { position: 0.0; color: "white" }
        GradientStop { position: 1.0; color: "red" }
    }
}
Pressing Ctrl+R will open the QML Viewer window, which would show the result.

Scene 1

Enter Rotation, Anchors, Blending, Color Notation.

Now, let's try to mimic the saturation/brightness picking area appearance. For that we take our red/white square, rotate it by 90 degrees, and blend a black square on top, with a gradient alpha.

Here we use an alternate color representation, as a string in a form "#RRGGBB" or "#AARRGGBB" (where, "AA" stands for "transparency", or "alpha").

We'll stuff both rectangles as children of a common parent object, an Item, which is a base visual object type in QML (all other types, including Rectangle, derive from it). It handles basic positioning and size, and also is often used as a lookless grouping container for other objects.

We'd explicitly specify the size only for this top-level object, and for the children rectangles use the "anchors.fill" property, which means "use the same size as some other object", the other object in this case being the "parent" object (in fact, here we see the first example of the property binding in action. Besides, the "." in the property name means that property is an attached property... but let's ignore these gory details for now):
import Qt 4.7
Item {
    width: 120; height: 120
    Rectangle {
        anchors.fill: parent;
        rotation: -90
        gradient: Gradient {
            GradientStop {
                position: 0.0; color: "#FFFFFFFF"
            }
            GradientStop {
                position: 1.0; color: "#FFFF0000"
            }
        }
    }
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop {
                position: 1.0; color: "#FF000000"
            }
            GradientStop {
                position: 0.0; color: "#00000000"
            }
        }
    }
}

Interlude

A bevel made with rectangles.

Continuing the rectangles topic, let's make a fancy "bevel", which could serve, for example, as a border for an edit box control.

Here we have a more intense usage of anchor-properties, margins in particular. Also, borders width and color for Rectangles, as well as corner radius.

The clip: true property is used to cut away right and bottom parts of the dark ("shadow") rectangle. Note, that the outermost rectangle here is used just as a background for our "bevel":
import Qt 4.7
Rectangle {
    color: "#3C3C3C"
    width: 80; height: 35
    Rectangle {
        x: 20; y: 10
        width : 40; height : 15; radius: 2
        border.width: 1; border.color: "#FF101010"
        color: "transparent"
        anchors.leftMargin: 1; anchors.topMargin: 3
        clip: true
        Rectangle {
            anchors.fill: parent; radius: 2
            anchors.leftMargin: -1; anchors.topMargin: -1
            border.width: 1; border.color: "#FF525255"
            color: "transparent"
        }
    }
}

Scene 2

Enter Custom Properties, Property Binding, Expressions.

I guess at this point everyone should be fed up with rectangles, so let's try something else.

Circles, for example. Let's try and make a visual representation for the cursor in the saturation/brightness area of the colorpicker.

So, we'll make our circles with... correct, with Rectangles.

The QML code here is pretty similar to the "bevel" example, with a new twist: custom properties.

We add our own new property to the root Item, and call it "r" (as in "radius"):
    property int r : 8
This will behave as a regular, existing property of the object (e.g. the "width" property) would. In this case, "8" will be a default value for the radius of our cursor.

Next, let's use this new custom property to turn the child rectangle into a proper circle:
       radius: parent.r
which means "keep the radius of this rectangle the same as the value of parents r property is".

What just happened here is called property binding (AKA "data binding") and is another cornerstone in declarative UI programming.

Property binding is a mechanism which keeps the values of certain properties in sync, i.e. when one property changes its value, then the corresponding bound property will update it's value automatically.

Furthermore, this value can be transformed ("converted") on the way. In QML this is done in a simple and elegant way, by using expressions, written in JavaScript language:
width: parent.r*2; height: parent.r*2
Here it means "keep the width and height of the object equal to be a double of the parent's r property". Expressions can refer more than one property from more than one object, in which case all of them get automatically "bound".

I must admit, WPF's converters and multiple bindings look quite a bit more awkward comparing to this... anyway, here's our cursor's code:
import Qt 4.7
Item {
    property int r : 8
    x: 50; y: 50; width: 100; height: 100;
    Rectangle {
        x: -parent.r; y: -parent.r
        width: parent.r*2; height: parent.r*2
        radius: parent.r
        border.color: "black"; border.width: 2
        color: "transparent"
        Rectangle {
            anchors.fill: parent; anchors.margins: 2;
            border.color: "gray"; border.width: 2
            radius: width/2
            color: "transparent"
        }
    }
}

Scene 3

Enter IDs, Grid, Repeater, Data Models.

What about some more rectangles?.. I knew you looked forward.

This time, though, let's organize them in a checkerboard pattern.

But first, let's mention that every object in QML object tree can have it's own "identifier", specified by the id property. This identifier can be used in property binding, so properties of one object get bound to properties of another object, referenced by its identifier. Here, we'll name the root object as "root":
    id: root
Next, let's combine a Grid element, which lays out its children in a grid with the given number of rows/columns, together with a Repeater element, which creates many similar objects based on two main parameters: visual template (specified as a child of the Repeater object) and data model.

Data model, generally, is a list of data items, each of them serving as an input context to the corresponding separate object created by the Repeater. In simpler cases (such as ours) it can be just a number, specifying how many copies of the object to create. The visual template can reference the property called index, which corresponds to the order of the item in the array.

We paint all the even elements gray and all the odd elements white, assuming, for the sake of simplicity, that the number of rows in the grid is always odd:
import Qt 4.7
Grid {
    id: root
    property int cellSide: 10
    width: 110; height: 110
    rows: height/cellSide; columns: width/cellSide
    Repeater {
        model: root.columns*root.rows
        Rectangle {
            width: root.cellSide; height: root.cellSide
            color: (index%2 == 0) ? "gray" : "white"
        }
    }
}

Act 2

Scene 0

Enter MouseArea, Signals, Inline JavaScript Code

Let's add some interaction now and make a circle, draggable with the mouse. For that we'll use a MouseArea element, which handles basic mouse tracking. One of the ways it does it is by receiving signals whenever certain changes in mouse status happen.

In this case particular signals of interest are onPositionChanged and onPressed, as we want to change cursor position in both the case when user clicks with the mouse and when the mouse cursor is dragged. The code, is going to be identical in both cases, so it looks like a good idea to put it into a separate JavaScript function (which we'll call "handleMouse"). The mentioned signals pass a MouseEvent object (named "mouse") with parameters describing the mouse status change ("x" and."y" corresponding to the current mouse position in particular).

Our helper JavaScript function can reside right in the scope of the MouseArea object (which makes this a kind of "custom method" for this object, somewhat similarly tho the custom properties case we've looked into earlier).

Additionally, the standard JavaSript Math module is used to clamp the cursor position to the area of interest:
import Qt 4.7
Item {
    width: 120; height: 120
    Item {
        id: pickerCursor
        Rectangle {
            x: -10; y: -10
            width: 20; height: 20; radius: 10
            border.color: "black"; border.width: 2
        }
    }
    MouseArea {
        anchors.fill: parent
        function handleMouse(mouse) {
            if (mouse.buttons & Qt.LeftButton) {
                pickerCursor.x = Math.max(0, 
                    Math.min(width,  mouse.x));
                pickerCursor.y = Math.max(0, 
                    Math.min(height, mouse.y));
            }
        }
        onPositionChanged: handleMouse(mouse)
        onPressed: handleMouse(mouse)
    }
}

Scene 1

Enter Components, Layout

Now, let's start putting things together.

Besides of the saturation/brightness picking area, we'd also like to have a couple of vertical sliders - one for hue and one for transparency. The only difference betwen them would be a background picture. I the case of hue slider it is a gradient of colors from the rim of the color wheel, and for the alpha slider it's a good old checkerboard with a black-white gradient on top. The vertical slider can be made in exactly the same way as the draggable cursor from the previous example, except it's limited to the vertical direction.

We don't really want to copypaste around the identical code for sliders, and that's where components come into play.

Components are reusable chunks of code (most often extracted into a separate file), and they serve as building blocks for Qt Quick application.

Let's create three separate files: ColorSlider.qml (the vertical slider component, used in hue and alpha sliders), SBPicker.qml (saturation/brightness picking box, with code from the earlier examples) and Checkerboard.qml (also familiar checkerboard rectangle) and put them inside the folder named, for example, "content". Our project starts to get some structure:



Note that component file names should start from a capital letter (CamelCasing is a common idiom here), and those names will be used to reference components from other code. A QML file can reference all the components, wich have files placed in the same folder. In our case, though, in order for the root file (colorpicker.qml) to use components from the "content" folder, we add the following line:
import "content"
This makes all the components from the "content" folder available inside colorpicker.qml.

Now that we can reference ColorSlider and SBPicker components, we'd like to arrange them horizontally. For that we use QML's Row element, which does exactly that:
// colorpicker.qml
import Qt 4.7
import "content"

Rectangle {
    width: 230; height: 200
    color: "#3C3C3C"
    Row {
        anchors.fill: parent
        spacing: 3

        // saturation/brightness picker box
        SBPicker {
            id: sbPicker
            hueColor : "green"
            width: parent.height; height: parent.height
        }

        // hue picking slider
        Item {
            width: 12; height: parent.height
            Rectangle {
                anchors.fill: parent
                gradient: Gradient {
                    GradientStop { position: 1.0;  color: "#FF0000" }
                    GradientStop { position: 0.85; color: "#FFFF00" }
                    GradientStop { position: 0.76; color: "#00FF00" }
                    GradientStop { position: 0.5;  color: "#00FFFF" }
                    GradientStop { position: 0.33; color: "#0000FF" }
                    GradientStop { position: 0.16; color: "#FF00FF" }
                    GradientStop { position: 0.0;  color: "#FF0000" }
                }
            }
            ColorSlider { id: hueSlider; anchors.fill: parent }
        }

        // alpha (transparency) picking slider
        Item {
            id: alphaPicker
            width: 12; height: parent.height
            Checkerboard { cellSide: 4 }
            //  alpha intensity gradient background
            Rectangle {
                anchors.fill: parent
                gradient: Gradient {
                    GradientStop { position: 0.0; color: "#FF000000" }
                    GradientStop { position: 1.0; color: "#00000000" }
                }
            }
            ColorSlider { id: alphaSlider; anchors.fill: parent }
        }

    }
}


Scene 2

Enter TextInput, Validators, Property Aliases

What we also want to have in our colorpicker is a textbox displaying the whole color in "#AARRGGBB" string format, as well as separate numeric boxes with H, S, V and R, G, B, A values.

Those numeric boxes, again, are all lookalike, so it's a perfect candidate for a new component.

The element, which in QML is used for a simple one-line text input, is called TextInput, and it has a few properties, specific to text editing - such as if text is selectable, what is the selection color, font properties etc.

One interesting property is validator, which has a function of allowing only input of a certain type from the user. DoubleValidator, in particular, only allows floating-point numbers in a specified range and with a specified number of digits after the decimal point.

So, the NumberBox component would consist of three parts: text input, caption text (drawn to the left of the text input), and a border around the text input.

External clients are not supposed to see the internal structure of a component, for them it's a black box with an interface. Property aliases is a convenient way of wiring certain properties of component's internal objects as a part of component's interface. For example, an internal Text component, which is used to display the NumberBox's caption, can have its "text" property "routed" to the component's interface without exposing the unnecessary details to client:
    property alias  caption: captionBox.text
The final number edit box component we place into a separate file: NumberBox.qml.

//  NumberBox.qml
import Qt 4.7

Row {
    property alias  caption: captionBox.text
    property alias  value: inputBox.text
    property alias  min: numValidator.bottom
    property alias  max: numValidator.top
    property alias  decimals: numValidator.decimals

    width: 80;
    height: 15
    spacing: 4
    anchors.margins: 2
    Text {
        id: captionBox
        width: 18; height: parent.height
        color: "#AAAAAA"
        font.pixelSize: 11; font.bold: true
        horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom
        anchors.bottomMargin: 3
    }
    PanelBorder {
        height: parent.height
        anchors.leftMargin: 4;
        anchors.left: captionBox.right; anchors.right: parent.right
        TextInput {
            id: inputBox
            anchors.leftMargin: 4; anchors.topMargin: 1; anchors.fill: parent
            color: "#AAAAAA"; selectionColor: "#FF7777AA"
            font.pixelSize: 11            
            maximumLength: 10
            focus: true
            selectByMouse: true
            validator: DoubleValidator {
                id: numValidator
                bottom: 0; top: 1; decimals: 2
                notation: DoubleValidator.StandardNotation
            }
        }
    }
}


Note that this in turn uses another component, the bevel we already know: PanelBorder.qml.

And then the QML file for the details part of the colorpicker looks like this:
import Qt 4.7
import "content"
Rectangle {
    width: 100; height: 200
    color: "#3C3C3C"
    Column {
        anchors.fill: parent
        anchors.leftMargin: 4; anchors.rightMargin: 3
        height: parent.height
        spacing: 4

        // current color/alpha display rectangle
        Rectangle {
            width: parent.width; height: 30
            Checkerboard { cellSide: 5 }
            Rectangle {
                width: parent.width; height: 30
                border.width: 1; border.color: "black"
                color: "#AAFFFF00"
            }
        }

        // "#AARRGGBB" color value box
        PanelBorder {
            id: colorEditBox
            height: 15; width: parent.width
            TextInput {
                anchors.fill: parent
                color: "#AAAAAA"
                selectionColor: "#FF7777AA"
                font.pixelSize: 11
                maximumLength: 9
                focus: true
                text: "#AAFFFF00"
                selectByMouse: true
            }
        }

        // H, S, B color values boxes
        Column {
            width: parent.width
            NumberBox { caption: "H:"; value: "0.1" }
            NumberBox { caption: "S:"; value: "0.2" }
            NumberBox { caption: "B:"; value: "0.3" }
        }

        // filler rectangle
        Rectangle {
            width: parent.width; height: 5
            color: "transparent"
        }

        // R, G, B color values boxes
        Column {
            width: parent.width
            NumberBox {
                caption: "R:"; value: "255"
                min: 0; max: 255
            }
            NumberBox {
                caption: "G:"; value: "255"
                min: 0; max: 255
            }
            NumberBox {
                caption: "B:"; value: "0"
                min: 0; max: 255
            }
        }

        // alpha value box
        NumberBox {
            caption: "A:"; value: "100"
            min: 0; max: 255
        }
    }
}


We've used another layout element here, Column, which is a vertical version of Row.

Act 3

Scene 0

Enter Qt Source Code, JavaScript Modules

We have all the controls in place, and what is left is wiring them together with property binding. For that we need to have some ways of converting between QML's color values, "#AARRGGBB" text representations, as well as between hue/saturation/brighness/alpha and red/green/blue/alpha.

The original expectation was that QML would already provide the methods needed. Unfortunately, it turned to be not as straightforward. One of the problems, for example, is that when converting color value to string, the "AA" part in "#AARRGGBB" is for some reason omitted. The documentation regarding the QML color type is rather scarce at the moment.

But we have the source code, which is a kind of documentation on its own, given that one has time and desire to understand the internal workings of Qt Framework. Personally I do find this kind of looking inside the black box to be quite a useful experience, especially since Qt has generally nice code and architecture.

The source code can be conveniently browsed in the Qt Creator itself, by opening the "[QtSDK Folder]/src/src.pro" project.

Looking into the code confirms that current support for the color manipulation is indeed rather basic (not going beyond what documentation suggests), and problem with the color-to-string conversion appears to be a bug, as code does indeed just ignore the alpha component when composing the color string. Which basically leaves us on our own with the color conversion.

What we can do, however, is to write the corresponding utility functions ourselves, in a separate JavaScript file. So let's create a file named ColorUtils.js and add it to the "components" folder:
//  ColorUtils.js
//  Color manipulation utilities

//  creates color value from hue, saturation, brightness, alpha
function hsba(h, s, b, a) {
    var lightness = (2 - s)*b;
    var satHSL = s*b/((lightness <= 1) ? lightness : 2 - lightness);
    lightness /= 2;
    return Qt.hsla(h, satHSL, lightness, a);
}

//  creates a full color string from color value and alpha[0..1], e.g. "#FF00FF00"
function fullColorString(clr, a) {
    return "#" + ((Math.ceil(a*255) + 256).toString(16).substr(1, 2) +
            clr.toString().substr(1, 6)).toUpperCase();
}

//  extracts integer color channel value [0..255] from color value
function getChannelStr(clr, channelIdx) {
    return parseInt(clr.toString().substr(channelIdx*2 + 1, 2), 16);
}
There are many things which are wrong with this code, but hopefully its main purpose - to demonstrate the idea - is fulfilled.

One thing to note is that it uses Qt.hsla method, which is a built-in QML function for creating color value from hue/saturation/alpha/lightness values. As we use HSB space, not HSL, there is an additional conversion performed.

Now, to be able to use these utilities in our colorpicker.qml file, we add the following line:
import "content/ColorUtils.js" as ColorUtils
and refer to the utility functions as, e.g. "ColorUtils.hsba(hue, saturation, brightness, alpha);".

After that, we finally wire all the text in the numberboxes to the actual color value, the color in the saturation/brightness picking area to correspond to the current hue slider position, and the color/alpha display rectangle to be of the current color, which gives us the final version of colorpicker.qml.

Grand Finale

I've been fascinated with the idea of declarative user interface programming, even playing with that in a game development framework long time ago (quite ironically, my "JML" markup language from 2003 was pretty similar to QML).

The paradigm does indeed seem to improve productivity when developing user interfaces, and experience with QML in particular was that it's fun to learn and use.

There've been a few gotchas, however.

For example, I could not figure out how to emulate two-way property binding, that's why our colorpicker does not react on input in the edit boxes. While there might be an obscure way of doing this, it's not supported out of the box.

Also, the layout engine is rather flaky - e.g. margins do not seem to work as expected when using Row and Column containers, and in few cases I had to use direct property binding to simulate the desired layout behaviour.

There is no complex brushes and vector graphics, as one would see in WPF, etc.

However, there is also a plenty of strong points, which competitors lack, and those points are extremely appealing.

Let's wait and see where it moves. I sincerely hope that Qt Quick/QML will take a strong position eventually.