How to Be a Programmer

Learning to program is a lot like learning a foreign language. To be any good in it, you need to read a lot of other people’s code and write a lot of new code. Getting fluent in programming takes time, but like any practice reading somebody else’s code becomes easier the more you do it, and the more you write the faster you are able to produce working functionality and less mistakes you will make while writing. Young developers joining the industry often start their careers by maintaining existing software and end up writing fairly little new code, which is a real shame as that means it takes them awfully long to get productive and confident enough to trust their own thinking.

Like writers, many programmers suffer from writer’s block. Only solution I know to procrastination is to just start producing something and forget for a moment any quality requirements you might mentally have that prohibit you from proceeding. Contrary to popular belief writing new code isn’t dangerous. Shipping new code that hasn’t been thoroughly reviewed and tested is dangerous. In publishing, on average fairly few pages ever written end up being published and pages that are lucky enough to get published often go trough multiple revisions before being accepted by the publisher. Publishers appoint editors to review even the most accomplished authors. In software projects code reviews are rarely practiced in the required depth, even though it is one of the best empirically-proven methods of improving the code quality and catching bugs.

Words

Words are powerful. Every word is an abstraction, for example when you say a word dog people think of an idea of a dog, not necessarily any particular dog. Like words in a book, each program is described using abstractions. Natural language writer often assumes that the reader is already familiar with the used vocabulary or can infer the meaning through the context, where as in programming the author needs to explicitly define the meaning behind each words. The program code does not give the author much artistic freedom for interpretations or ambiguities, the system crashes if it is unable to follow the plot or track the involved subjects correctly. Dynamically typed and interpreted programming languages are often closer to natural languages and offer more expressive freedom for the authors than computationally faster, statistically typed and compiled languages.

Defining an abstraction is like writing a poem. Each abstraction tries to capture one concept or behavior similarly as a poem tries capture the essence of one feeling or a thought. Like in poems, a good abstraction is as concise as possible without making compromises to the original idea. An abstraction that tries to capture multiple responsibilities is hard to follow and maintain, similarly as words with ambiguous and hidden meanings are easily misunderstood by the participants. Like in writing, the more familiar metaphors you find for representing your abstractions, the more readable and self-explanatory your code will be.

Right abstractions are highly dependent on the problem domain. Good writing skills do not get you very far without solid understanding of the subject area you should be writing about. Big software companies often underestimate the importance of domain knowledge and end up hiring and subcontracting developers who lack understanding of the functionality they have been hired to improve. In addition to experience, finding right abstractions for the job requires active thinking. Every new abstraction adds to the overall complexity of the program, so it’s important that you understand what abstractions are really critical for implementing the program.

The Plot

A program is a very special kind of book. Traditional books can be read linearly from cover to cover, whereas code execution jumps between lines and chapters circularly based on the user input and changes in the environment the program is being run. In a way program is a book that is being read over and over again in a varying order. Writing new software is even more challenging than writing a book, because most software is simultaneously co-written by multiple authors, making it harder to achieve good cohesion and to make sure the common story and all the side plots won’t end up skewed and misrepresented when the changes are applied. This makes writing software largely a communication game between the authors. When the communication fails, manual and automatic verification tests are needed for shielding the program correctness from human mistakes.

Variations in the program behavior are called side effects. Side effects are a major cause of headache and quality problems in software development. Getting a program swarming with side effects to behave correctly is really difficult job. It is like living with an alcoholic dad suffering from mental problems, you never know what kind of mess he gets the whole family in. You should try to keep the number of program states at minimum and make the states you absolutely can’t live without explicit so they are easier to track and validate.

Libraries

A programmer can utilize programs written by other authors. Programs used by other programs are normally referred to as libraries. Each library provides a set of ready-made abstractions that can be used for writing solutions in a particular problem domain. Including a new library is like going to a school and learning a new skill that you can apply from now on. Using existing software libraries can speed up the development time considerably, but it also makes you dependent on the work and services of others. When problems arise it’s far more difficult to fix flaws in external libraries than in your own code.

A programmer includes libraries to the program similarly how a researcher makes citations to existing publications and uses the abstractions defined in the publications as words to define her own theories. Like researchers, professional programmers should be aware of the important software libraries in their field and be able to apply them flexibly when solving problems. Releasing your program under open source license contributes the program to the knowledge base of the whole industry similarly to how scientific publications contribute to the knowledge base of the entire field.

Powerful and accurate abstractions are critical tools for obtaining correct program behavior. With the right abstractions you can describe the program behavior more simply and concisely. A simple and concise program is easier to change and maintain, which is important as the ability to make rapid changes to a product gives any team a serious competitive advantage.



Live Pixels

Last weekend I started playing with the new Qt 5 particle effect system. One interesting effect I found in the examples was using custom particles and OpenGL shaders to color the moving particles, producing a cool-looking image on the screen made of “live” pixels. For this blog entry I have drawn an image of a skull, which in the example gets rotated and scaled across easing curves to further enhance the effect and finally fed to the particle system.

The original drawing with and without the effect
Show code »

livepixels.qml:

import QtQuick 2.0
import QtQuick.Particles 2.0

Rectangle{
    width: 800; height: 480
    ParticleSystem {
        width: image.width
        height: image.height
        anchors.centerIn: parent
        Emitter {
            size: 15
            sizeVariation: 10
            speed: PointDirection { xVariation: 8; yVariation: 8 }
            emitRate: 10000
            lifeSpan: 2000
            anchors.fill: parent
        }
        CustomParticle {
            property real maxWidth: parent.width
            property real maxHeight: parent.height
            property variant particleTexture: ShaderEffectSource {
                sourceItem: Image { source: "particle.png" }
                hideSource: true
            }
            property variant pictureTexture: ShaderEffectSource {
                sourceItem: sourceImage
                hideSource: true
                live: true
            }
            vertexShader:"
                uniform highp float maxWidth;
                uniform highp float maxHeight;
                varying highp vec2 fTex2;
                varying lowp float fFade;
                uniform lowp float qt_Opacity;

                void main() {
                    fTex2 = vec2(qt_ParticlePos.x / maxWidth, qt_ParticlePos.y / maxHeight);
                    highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
                    fFade = min(t*4., (1.-t*t)*.75) * qt_Opacity;
                    defaultMain();
                }
            "
            fragmentShader: "
                uniform sampler2D particleTexture;
                uniform sampler2D pictureTexture;
                varying highp vec2 qt_TexCoord0;
                varying highp vec2 fTex2;
                varying lowp float fFade;
                void main() {
                    gl_FragColor = texture2D(pictureTexture, fTex2) * texture2D(particleTexture, qt_TexCoord0).w * fFade;
            }"
        }
    }
    Item {
        id: sourceImage
        width: image.width; height: image.height
        anchors.centerIn: parent
        Image{
            id: image
            source: "skull.png"
            scale: 1 + 0.15*Math.random()
            Behavior on scale {
                NumberAnimation { duration: 3000; easing.type: Easing.InOutQuad }
            }
            Timer {
                interval: 3000
                repeat: true; running: true
                onTriggered: parent.scale = 1 + 0.15*Math.random()
            }
            transform: Rotation {
                id: rotation
                property real value: Math.random()
                property bool reverse: Math.random() > 0.5
                origin.x: image.width/2; origin.y: image.height/2
                axis.x: Math.random() < 0.2 || value < 0.5
                axis.y: Math.random() < 0.2 || value >= 0.5
                SequentialAnimation on angle {
                    loops: Animation.Infinite
                    NumberAnimation {
                        from: 0.0; to: rotation.reverse ? -8.0 : 8.0
                        duration: 2500; easing.type: Easing.InOutQuad
                    }
                    NumberAnimation {
                        from: rotation.reverse ? -8.0 : 8.0; to: 0.0
                        duration: 2500; easing.type: Easing.InOutQuad
                    }
                }
            }
        }
    }
}



Falling Cubes

Falling Cubes example shows two hundred cubes falling across the view port. The cubes have been colored using non-realistic Gooch shading, where the mesh surface color is mixed with warm and cold colors using surface’s normals. Gooch shader has been written in OpenGL GLSL Shading Language. Falling animation is implemented using SmoothedAnimation element.

Show code »

fallingcubes.qml:

import Qt3D 1.0
import QtQuick 1.0
import Qt3D.Shapes 1.0

Viewport {
    id: fallingCubes
    width: 800; height: 480
    property color coolColor: Qt.rgba(0.3 + 0.1*Math.random(),
                                      0.3 * Math.random(),
                                      0.5 + 0.1*Math.random())
    Item3D {
        x: -4.2; y: -1.62; scale: 0.23
        Quad {
            x: 18.4; y: 6.8; z: -10; scale: 18
            transform: Rotation3D {
                angle: 90; axis: Qt.vector3d(1.0, 0.0, 0.0)
            }
            effect: Effect {
                property real fade
                SequentialAnimation on fade {
                    id: fadeAnimation
                    property real offset: Math.random()
                    property real duration: 4000+4000*Math.random()
                    running: true; loops: Animation.Infinite
                    NumberAnimation {
                        from: 0.0; to: 1.0
                        duration: fadeAnimation.duration/2
                    }
                    NumberAnimation {
                        from: 1.0; to: 0.0
                        duration: fadeAnimation.duration/2
                    }
                }
                color: Qt.rgba(0.95 + 0.05*Math.random(),
                               0.5 + 0.2*fade,
                               0.1 + 0.1*Math.random())
            }
        }
        Repeater {
            model: 200
            FallingCube { coolColor: fallingCubes.coolColor }
        }
    }
}

FallingCube.qml:

import Qt3D 1.0
import QtQuick 1.0
import Qt3D.Shapes 1.0

Item3D {
    id: fallingCube
    property color coolColor
    function reset() {
        yBehavior.enabled = false;
        fallingCube.y = 25
        yBehavior.enabled = true;
        fallingCube.x = 40*Math.random();
        fallingCube.y = -15
    }
    Component.onCompleted: y = -15
    x: 40*Math.random(); y: 30*Math.random()-10; z: 20*(Math.random()-0.5)
    Behavior on x { SmoothedAnimation { velocity: 2*Math.random() } }
    Behavior on y {
        id: yBehavior
        SmoothedAnimation { velocity: 6+Math.random()*6 }
    }
    Timer {
        interval: 100; repeat: true; running: true
        onTriggered: if (fallingCube.y < -9) fallingCube.reset()
    }
    Cube {
        scale: 2
        effect: GoochShading {
            coolColor: fallingCube.coolColor
            warmColor: Qt.rgba(0.9 + 0.1*Math.random(),
                               0.2 + 0.2*Math.random(),
                               0.2*Math.random())
        }
        transform: Rotation3D {
            axis: Qt.vector3d(Math.random(), Math.random(), Math.random())
            SequentialAnimation on angle {
                id: angleAnimation
                property real offset: 360*Math.random()
                property real duration: 3000+3000*Math.random()
                running: true; loops: Animation.Infinite
                NumberAnimation {
                    from: angleAnimation.offset; to: 360
                    duration: (360-angleAnimation.offset)*angleAnimation.duration/360
                }
                NumberAnimation {
                    from: 0; to: angleAnimation.offset
                    duration: angleAnimation.offset*angleAnimation.duration/360
                }
            }
        }
    }
}

GoochShading.qml:

import Qt3D 1.0
import QtQuick 1.0

ShaderProgram {
    property color warmColor
    property color coolColor
    vertexShader: "
        attribute highp vec4 qt_Vertex;
        attribute highp vec3 qt_Normal;

        uniform lowp vec4 warmColor;
        uniform lowp vec4 coolColor;

        uniform highp mat3 qt_NormalMatrix;
        uniform highp mat4 qt_ModelViewProjectionMatrix;
        uniform highp mat4 qt_ModelViewMatrix;

        varying float facingProjector;

        struct qt_SingleLightParameters {
            mediump vec4 position;
            mediump vec3 spotDirection;
            mediump float spotExponent;
            mediump float spotCutoff;
            mediump float spotCosCutoff;
            mediump float constantAttenuation;
            mediump float linearAttenuation;
            mediump float quadraticAttenuation;
        };
        uniform qt_SingleLightParameters qt_Light;

        void main(void)
        {
            highp vec4 vertex = qt_ModelViewMatrix * qt_Vertex;
            highp vec3 normal = normalize(qt_NormalMatrix * qt_Normal);
            highp vec3 light = normalize(qt_Light.position.xyz - vertex.xyz);
            facingProjector = clamp(dot(normal, light), 0.0, 1.0);
            gl_Position = qt_ModelViewProjectionMatrix * qt_Vertex;
        }
    "
    fragmentShader: "
        uniform lowp vec4 warmColor;
        uniform lowp vec4 coolColor;

        varying float facingProjector;

        void main(void)
        {
            highp float ratio = 0.5 * (facingProjector + 1.0);
            gl_FragColor = mix(warmColor, coolColor, ratio);
        }
    "
}



Overlapping Letters

Show code »

helloworld.qml:

import QtQuick 1.0

Rectangle {
    width: 800; height: 200
    property string label: "Hello world!"
    property variant fontFamilies: [ "Georgia", "Verdana", "Tahoma",
                                     "Lucida Console", "Century Gothic",
                                     "Courier New",  "Times New Roman"]
    function randomFont() {
        return fontFamilies[Math.floor(Math.random()*fontFamilies.length)]
    }
    Row {
        id: row
        spacing: -10-10*Math.random()
        anchors.centerIn: parent
        Repeater {
            model: label.length
            Text {
                text: label[index]
                opacity: 0.5+0.5*Math.random()
                anchors.verticalCenter: parent.verticalCenter
                property real distance: 2*Math.abs(index-label.length/2) /
                                        label.length
                font {
                    family: randomFont()
                    capitalization: Font.AllUppercase
                    pixelSize: 130+30*Math.random()-50*distance
                }
                Repeater {
                    model: 5+5*Math.random()
                    Text {
                        text: parent.text
                        opacity: 0.6+0.4*Math.random()
                        anchors.centerIn: parent
                        font {
                            family: randomFont()
                            capitalization: Font.AllUppercase
                            pixelSize: 130+30*Math.random()-50*distance
                        }
                    }
                }
            }
        }
    }
}

Show code »

alphabets.qml:

import QtQuick 1.0

Rectangle {
    width: grid.width+100; height: grid.height+100
    property real letterHeight
    property real letterWidth
    property string alphabets: "abcdefghijklmnopqrstuvwxyzåäö"
    property variant fontFamilies: [ "Castellar", "Century Gothic",
                                     "Courier New", "Bodoni MT Condensed",
                                     "AngsanaUPC", "Bodoni MT Black",
                                     "Copperplate Gothic Bold", "Pistilli",
                                     "Rockwell Extra Bold"]
    function randomFont() {
       return fontFamilies[Math.floor(Math.random()*fontFamilies.length)]
    }
    function resizeLetters() {
        var rows = alphabets.length/grid.columns
        letterHeight = Math.round(grid.height/rows)
        letterWidth = Math.round(grid.width/grid.columns)
        for (var index = 0; index < grid.children.length; index++) {
            var child = grid.children[index]
            if (child.width != undefined) {
               child.height = letterHeight
               child.width = letterWidth
            }
        }
    }
    Timer {
        interval: 50
        running: true
        onTriggered: resizeLetters()
    }
    Grid {
        columns: 6
        anchors { centerIn: parent; verticalCenterOffset: -15 }
        Repeater {
            model: alphabets.length
            Rectangle {
                height: letterHeight
                width: letterWidth
                color:  Qt.rgba(0.4+0.6*Math.random(),
                                0.4+0.6*Math.random(),
                                0.4+0.6*Math.random())
            }
        }
    }
    Grid {
        id: grid
        columns: 6
        anchors { centerIn: parent; verticalCenterOffset: -15 }
        Repeater {
            model: alphabets.length
            Text {
                text: alphabets[index]
                font {
                    pixelSize: 120
                    family: randomFont()
                    capitalization: Font.AllUppercase
                }
                Repeater {
                    model: 3+Math.random()*2
                    Text {
                        text: parent.text
                        anchors.centerIn: parent
                        font {
                            pixelSize: 120
                            family: randomFont()
                            capitalization: Font.AllUppercase
                        }
                    }
                }
            }
        }
    }
}



Color Strips

Finding a good color palette for your project can be difficult. I’d love to learn the mysterious formulas behind harmonic color patterns, but so far coming up with suitable combinations has been mostly a matter of trial and error: the examples below are no different. If you don’t want to go through the hassle of inventing the schemes yourself, Adobe’s Kuler website is filled with ready-made palettes.

Randomly colored wall of horizontal strips

Show code »

ColorColumn.qml:

import QtQuick 1.0

Column {
    width: 800; height: 400
    property real pixelsPerRelativeHeight
    function rectangleColor() {
        var value = Math.random()
        var red = value < 0.4 ? 0.8+0.2*Math.random() : 0.1+0.4*Math.random()
        var green = value > 0.4 && value < 0.8 ? 0.7+0.3*Math.random() : 0.4+0.3*Math.random()
        var blue = value > 0.8 ? 0.6+0.2*Math.random() : 0.2*Math.random()
        return Qt.rgba(red, green, blue)
    }
    Component.onCompleted: {
        var totalRelativeHeight = 0
        for (var index = 0; index < children.length; index++) {
            if (children[index] !== repeater)
                totalRelativeHeight += children[index].relativeHeight
        }
        pixelsPerRelativeHeight = height/totalRelativeHeight
    }
    Repeater {
        id: repeater
        model: 40
        Rectangle {
            width: parent.width
            color: rectangleColor()
            height: Math.ceil(relativeHeight*pixelsPerRelativeHeight)
            property real value: Math.random()
            property real relativeHeight: Math.pow(Math.random(),3)
        }
    }
}


Randomly colored wall of vertical strips

Show code »

ColorRow.qml:

import QtQuick 1.0

Row {
    width: 800; height: 400
    property real pixelsPerRelativeWidth
    function rectangleColor() {
        var isRed = Math.random() < 0.1
        var grayness = 0.2+0.6*Math.random()
        var red = isRed ? grayness+0.6*Math.random()*(1-grayness) : grayness
        var green = isRed ? 0.5*grayness+0.5*Math.random()*grayness : 1.3*grayness
        var blue = isRed ? 0.8*grayness+0.4*Math.random()*grayness : 1.3*grayness
        return Qt.rgba(red, green, blue)
    }
    Component.onCompleted: {
        var totalRelativeWidth = 0
        for (var index = 0; index < children.length; index++) {
            if (children[index] !== repeater)
                totalRelativeWidth += children[index].relativeWidth
        }
        pixelsPerRelativeWidth = width/totalRelativeWidth
    }
    Repeater {
        id: repeater
        model: 80
        Rectangle {
            height: parent.height
            color: rectangleColor()
            width: Math.ceil(relativeWidth*pixelsPerRelativeWidth)
            property real relativeWidth: Math.pow(Math.random(),3)
        }
    }
}


Vertical segments of horizontal strips

Show code »

colorstrips_rowofcolumns.qml:

import QtQuick 1.0

Row {
    width: 800; height: 400
    property real pixelPerRelativeWidth
    Component.onCompleted: {
        var totalRelativeWidth = 0
        for (var index = 0; index < children.length; index++) {
            if (children[index] !== repeater)
                totalRelativeWidth += children[index].relativeWidth
        }
        pixelPerRelativeWidth = width/totalRelativeWidth
    }
    Repeater {
        id: repeater
        model: 50
        ColorColumn {
            height: parent.height
            width: Math.ceil(relativeWidth*pixelPerRelativeWidth)
            property real relativeWidth: Math.pow(Math.random(),3)
            function rectangleColor() {
                var value = Math.random()
                var blue = value < 0.4 ? 0.8+0.2*Math.random() : 0.1+0.4*Math.random()
                var red = value > 0.4 && value < 0.8 ? 0.7+0.3*Math.random() : 0.4+0.3*Math.random()
                var green = value > 0.8 ? 0.2+0.2*Math.random() : 0.2*Math.random()
                return Qt.rgba(red, green, blue)
            }
        }
    }
}


Horizontal segments of vertical strips

Show code »

colorstrips_columnofrows.qml:

import QtQuick 1.0

Column {
    width: 800; height: 400
    property real pixelsPerRelativeHeight
    Component.onCompleted: {
        var totalRelativeHeight = 0
        for (var index = 0; index < children.length; index++) {
            if (children[index] !== repeater)
                totalRelativeHeight += children[index].relativeHeight
        }
        pixelsPerRelativeHeight = height/totalRelativeHeight
    }
    Repeater {
        id: repeater
        model: 40
        ColorRow {
            width: parent.width
            height: Math.ceil(relativeHeight*pixelsPerRelativeHeight)
            property real relativeHeight: Math.pow(Math.random(),3)
            function rectangleColor() {
                var isBlue = Math.random() < 0.2
                var grayness = 0.2+0.6*Math.random()
                var red = isBlue ? 0.5*grayness+0.5*Math.random()*grayness : grayness
                var green = isBlue ? grayness+0.6*Math.random()*(1-grayness) : 1.3*grayness
                var blue = isBlue ? 0.7*grayness+0.3*Math.random()*grayness : 1.3*grayness
                return Qt.rgba(red, green, blue)
            }
        }
    }
}



Horizontal Flow

Horizontal Flow example shows how to implement a horizontally flickable list of items like a list of album covers. The component is based on the PathView element. Also, horizontal alphabetic scrollbar is provided on the bottom of the page for quickly jumping to a new position. Horizontal Flow also supports arrow key-based navigation for media center-style UIs.

Show code »

horizontalflow.qml:

import QtQuick 1.0

Rectangle {
    color: "black"
    width: 800; height: 280
    HorizontalPathView {
        id: horizontalPathView
        model: Model { id: model }
        delegate: Delegate {}
        anchors.fill: parent
    }
    AlphabeticScrollbar {
        view: horizontalPathView
        alphabets: model.alphabets
        alphabetIndeces: model.alphabetIndeces
        anchors {
            left: parent.left
            right: parent.right
            bottom: parent.bottom
            margins: 3
        }
    }
}

HorizontalPathView.qml:

import QtQuick 1.0

PathView {
    id: horizontalPathView
    focus: true
    highlight: Item {}
    pathItemCount: 11
    preferredHighlightBegin: 0.5; preferredHighlightEnd: 0.5
    path: Path {
        startX: -400; startY: horizontalPathView.height/2-70
        PathAttribute { name: "iconScale"; value: 0.5 }
        PathQuad {
            x: horizontalPathView.width/2
            y: horizontalPathView.height/2-20
            controlX: horizontalPathView.width/4
            controlY: horizontalPathView.height/2-45
        }
        PathAttribute { name: "iconScale"; value: 1.0 }
        PathQuad {
            x: horizontalPathView.width+400
            y: horizontalPathView.height/2-70
            controlX: 3*horizontalPathView.width/4
            controlY: horizontalPathView.height/2-45
        }
        PathAttribute { name: "iconScale"; value: 0.5 }
    }
    Keys.onLeftPressed: decrementCurrentIndex()
    Keys.onRightPressed: incrementCurrentIndex()
    function zLevel(index) {
        var dist = Math.abs((currentIndex - index) % count)
        if (dist > (pathItemCount/2.0 + 1))
            dist = count - dist
        return Math.floor(pathItemCount/2.0) - dist
    }
}

Model.qml:

import QtQuick 1.0

ListModel {
    property variant alphabets: []
    property variant alphabetIndeces: []
    function calculateAlphabets() {
        var newAlphabets = []
        var newAlphabetIndeces = []
        var previousItem, item = " "
        for (var index = 0; index < count; index++) {
            previousItem = item
            item = get(index).item
            if (previousItem.charAt(0) != item.charAt(0)) {
                newAlphabets[newAlphabets.length] = item.charAt(0)
                newAlphabetIndeces[newAlphabetIndeces.length] = index
            }
        }
        alphabets = newAlphabets
        alphabetIndeces = newAlphabetIndeces
    }
    Component.onCompleted: populate()
    function populate() {
        // go from A to Z
        for (var index = 65; index < 91; index++) {
            var alphabet = String.fromCharCode(index)
            var alphabetCount = Math.floor(Math.random()*5)
            for (var index2 = 0; index2 < alphabetCount; index2++)
                append({"number": count, "item": alphabet})
        }
        calculateAlphabets()
    }
}

Delegate.qml:

import QtQuick 1.0

Rectangle {
    width: 200; height: 200
    scale: PathView.iconScale
    z: PathView.view.zLevel(index)
    color: Qt.rgba(0.5+(PathView.view.count - number)*Math.random()/PathView.view.count,
                   0.3+number*Math.random()/PathView.view.count, 0.3*Math.random(), 0.7)
    signal clicked
    Keys.onReturnPressed: clicked()
    onClicked: PathView.view.currentIndex = index
    MouseArea {
        id: delegateMouse
        anchors.fill: parent
        onClicked: parent.clicked()
    }
    Rectangle {
        color: Qt.rgba(0.2, 0.3, 0.8)
        opacity: delegateMouse.pressed ? 0.8 : 0.0
        anchors.fill: parent
    }
    Text {
        smooth: true
        color: "white"
        font.pixelSize: 30
        text: "ITEM\n" + item
        anchors.centerIn: parent
        horizontalAlignment: Text.AlignHCenter
    }
}

AlphabeticScrollbar.qml:

import QtQuick 1.0

MouseArea {
    width: 400; height: 50
    property variant view
    property variant alphabets
    property variant alphabetIndeces
    property int currentIndex
    property real letterWidth: (width + 10)/alphabets.length
    onPressed: updatePosition(mouse.x)
    onPositionChanged: updatePosition(mouse.x)
    function updatePosition(x) {
        var index = Math.round((x-10)/letterWidth);
        if (index >= 0 && index < alphabetIndeces.length) {
            currentIndex = index;
            view.currentIndex = alphabetIndeces[index];
        }
    }
    Rectangle {
        radius: 10
        color: "white"
        visible: parent.pressed
        height: letterWidth+60; width: letterWidth+30
        x: Math.min(Math.max(0,parent.mouseX-width/2), parent.width-width)
        anchors { bottom: parent.bottom; bottomMargin: -10 }
        Text {
            font.pixelSize: 34
            text: alphabets[currentIndex]
            anchors { centerIn: parent; verticalCenterOffset: -20 }
        }
    }
    Rectangle {
        height: 20
        color: Qt.rgba(1.0, 1.0, 1.0, 0.2)
        anchors {left: parent.left; right: parent.right; bottom: parent.bottom }
    }
}



Time Picker

Time Picker example shows how to implement a time picker widget for the application. The widget is created as a modal dialog with two vertical ListView elements: one for choosing hours and another one for choosing minutes. The default values follow the current time. User can quickly flick to the wanted time or select visible hour and minute items by tapping.

Show code »

timepicker.qml:

import QtQuick 1.0

Rectangle {
    color: Qt.rgba(1.0, 0.9, 0.6)
    width: 800; height: 500
    TimePickerDialog {
        id: timePickerDialog
        anchors.centerIn: parent
    }
    Button {
        width: 130
        text: "Open"
        anchors.centerIn: parent
        onClicked: timePickerDialog.show()
    }
}

TimePickerDialog.qml:

import QtQuick 1.0

Rectangle {
    id: timePicker
    property int hours
    property int minutes
    property string text: qsTr("Set time to")
    z: 2
    radius: 4
    scale: 0.0
    color: Qt.rgba(0.1, 0.1, 0.1, 0.9)
    width: 320; height: contentColumn.height + 4
    border { width: 2; color: Qt.rgba(1.0, 1.0, 1.0, 0.2) }
    signal finished
    signal canceled
    onFinished: timePicker.state = ""
    onCanceled: timePicker.state = ""
    function show() {
        state = "visible"
    }
    function formatTime() {
        var time = new Date()
        time.setHours(hours)
        time.setMinutes(5*Math.round(minutes/5))
        return Qt.formatDateTime(time, "hh:mm")
    }
    Column {
        id: contentColumn
        x: 2; width: parent.width - 4
        Text {
            color: "white"
            height: 80
            font.pixelSize: 32
            text: timePicker.text + " " + timePicker.formatTime()
            elide: Text.ElideRight
            verticalAlignment: Text.AlignVCenter
            anchors.horizontalCenter: parent.horizontalCenter
        }
        Rectangle {
            color: Qt.rgba(0.9, 0.9, 0.9)
            x: 10; y: 6; height: 300; width: parent.width - 20
            Row {
                clip: true
                spacing: 1
                anchors.fill: parent
                ListView {
                    id: hourList
                    width: parent.width/2; height: 300
                    model: ListModel {
                        id: hourModel
                        Component.onCompleted: {
                            for (var index = 0; index < 24;  index++)
                                append({"hours": index})
                            hourList.currentIndex =
                                Math.ceil(new Date().getHours())
                        }
                    }
                    delegate: TimePickerItem {
                        number: hours
                        onIsCurrentItemChanged: {
                            if (isCurrentItem)
                                timePicker.hours = hours
                        }
                    }
                    snapMode: ListView.SnapToItem
                    flickDeceleration: 1000
                    preferredHighlightBegin: 120; preferredHighlightEnd: 120
                    highlightRangeMode: ListView.StrictlyEnforceRange
                    highlight: Item {}
                }
                ListView {
                    id: minuteList
                    width: parent.width/2; height: 300
                    model: ListModel {
                        id: minuteModel
                        Component.onCompleted: {
                            for (var index = 0; index < 12; index++)
                                append({"minutes": 5*index})
                            minuteList.currentIndex =
                                Math.ceil(new Date().getMinutes()/5)
                        }
                    }
                    delegate: TimePickerItem {
                        number: minutes
                        onIsCurrentItemChanged: {
                            if (isCurrentItem)
                                timePicker.minutes = minutes
                        }
                    }
                    snapMode: ListView.SnapToItem
                    flickDeceleration: 1000
                    preferredHighlightBegin: 120; preferredHighlightEnd: 120
                    highlightRangeMode: ListView.StrictlyEnforceRange
                    highlight: Item {}
                }
            }
            Rectangle {
                // highlight
                height: 60; width: parent.width
                color: Qt.rgba(0.2, 0.5, 0.8, 0.4)
                anchors.centerIn: parent
            }
            Rectangle {
                height: 55; width: parent.width
                gradient: Gradient {
                    GradientStop { position: 1.0; color: Qt.rgba(0,0,0,0) }
                    GradientStop { position: 0.0; color: Qt.rgba(0,0,0,0.6) }
                }
                anchors.top: parent.top
            }
            Rectangle {
                height: 55; width: parent.width
                gradient: Gradient {
                    GradientStop { position: 0.0; color: Qt.rgba(0,0,0,0) }
                    GradientStop { position: 1.0; color: Qt.rgba(0,0,0,0.6) }
                }
                anchors.bottom: parent.bottom
            }
        }
        Item { width: 1; height: 10 }
        Row {
            height: 70; width: parent.width
            Button {
                text: qsTr("Ok")
                width: parent.width/2 - 2
                onClicked: timePicker.finished()
            }
            Rectangle {
                width: 2; height: 70
                color: Qt.rgba(1.0, 1.0, 1.0, 0.2)
            }
            Button {
                text: qsTr("Cancel")
                width: parent.width/2-2
                onClicked: timePicker.canceled()
            }
        }
    }
    Rectangle {
        id: fadeBackground
        z: 1
        opacity: 0.0
        color: Qt.rgba(0.0, 0.0, fadeBackgroundMouse.pressed ? 0.5 : 0.45)
        parent: timePicker.parent
        anchors.fill: parent
        MouseArea {
            id: fadeBackgroundMouse
            enabled: false
            anchors.fill: parent
            onClicked: timePicker.canceled()
        }
    }
    states: State {
        name: "visible"
        PropertyChanges { target: timePicker; scale: 1.0 }
        PropertyChanges { target: fadeBackgroundMouse; enabled: true }
        PropertyChanges { target: fadeBackground; opacity: 0.45 }
    }
    transitions: Transition {
        NumberAnimation {
            properties: "opacity, scale"
            duration: 500; easing.type: Easing.InOutQuad
        }
    }
}

TimePickerItem.qml:

import QtQuick 1.0

Rectangle {
    id: timePickerItem
    property int number
    property bool pressed: itemMouse.pressed || highlightTimer.running
    property bool isCurrentItem: ListView.isCurrentItem
    color: index % 2 ? "white" : "lightgray"
    height: 60; width: parent.width
    Rectangle {
        color: "black"
        opacity: parent.pressed ? 0.3 : 0.0
        anchors { fill: parent; margins: -1 }
        Behavior on opacity {
            NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
        }
    }
    MouseArea {
        id: itemMouse
        anchors.fill: parent
        enabled: !ListView.isCurrentItem
        onClicked: ListView.view.currentIndex = index
    }
    Text {
        text: number >= 0 ? number : ""
        font.pixelSize: 26
        anchors.centerIn: parent
    }
    Timer {
        id: highlightTimer
        interval: 200
        running: parent.pressed
    }
}

Button.qml:

import QtQuick 1.0

Rectangle {
    property string text
    radius: 4.0
    color: "black"
    width: 107; height: 70
    scale: buttonMouse.pressed ? 0.95 : 1.0
    Behavior on scale {
        NumberAnimation { duration: 100; easing.type: Easing.InOutQuad }
    }
    signal clicked
    Text {
        color: "white"
        text: parent.text
        font.pixelSize: 28
        elide: Text.ElideRight
        smooth: buttonMouse.pressed
        anchors.centerIn: parent
    }
    MouseArea {
        id: buttonMouse
        anchors.fill: parent
        onClicked: parent.clicked()
    }
}



Splash!

Splash! example randomly generates new colourful logos.

Show code »

splash.qml:

import QtQuick 1.0

Rectangle {
    width: 800; height: 400
    Loader {
        anchors.centerIn: parent
        Component.onCompleted: reload()
        function reload() {
            source = ""
            source = "SplashText.qml"
            parent.color = Qt.rgba(0.7 + 0.2*Math.random(),
                                   0.7 + 0.2*Math.random(),
                                   0.9 + 0.1*Math.random())
        }
        Timer {
            interval: 1000; running: true; repeat: true
            onTriggered: parent.reload()
        }
    }
}

SplashText.qml:

import QtQuick 1.0

Rectangle {
    id: splashText
    property string text: "Splash!"
    width: 440; height: 160
    property bool reverse: Math.random() > 0.5
    color: Qt.rgba(0.5+0.5*Math.random(), 0.2+0.4*Math.random(),
                   0.2+0.2*Math.random())
    transform: Rotation {
        origin.x: splashText.width/2
        origin.y: splashText.height/2
        property real value: Math.random()
        axis.x: Math.random() < 0.2 || value < 0.3
        axis.y: Math.random() < 0.2 || value >= 0.3 && value < 0.7
        axis.z: Math.random() < 0.2 || value >= 0.7
        SequentialAnimation on angle {
            loops: Animation.Infinite
            NumberAnimation {
                from: 0.0; to: reverse ? -20.0 : 20.0
                duration: 500; easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                from: reverse ? -20.0 : 20.0; to: 0.0
                duration: 500; easing.type: Easing.InOutQuad
            }
        }
    }
    Repeater {
        model: 20
        Rectangle {
            height: Math.random()*parent.height
            width: 0.4*Math.random()*parent.width
            color: Qt.rgba(0.6+0.5*Math.random(), 0.4+0.4*Math.random(),
                           0.2+0.4*Math.random(), 0.5+0.5*Math.random())
            x: Math.random() > 0.3 ? Math.random()*parent.width - width/2 :
               Math.random()*width - width + (Math.random() > 0.5 ? parent.width : 0)
            y: Math.random()*parent.height - height/2
        }
    }
    Repeater {
        model: 5
        Text {
            text: splashText.text
            color: "white"
            font.pixelSize: 70+Math.random()*10
            anchors {
                centerIn: parent
                horizontalCenterOffset: 3*Math.random()
                verticalCenterOffset: 4*Math.random()
            }
        }
    }
}