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()
            }
        }
    }
}