torstai 29. toukokuuta 2014

Zoomable and draggable QML item

To my knowledge there is no default Qml-item for handling zooming. PinchArea is the currently best for providing zoom by pinch, but it doesn't work on desktop, and the default zoom behaviour isn't really to my liking.

Luckily Qml provides different transformations, and you can create your own zoomable area with a little extra effort. The following is my take on a draggable and zoomable Qml-item. This works on Android and on desktop (zoom with wheel). You can test this code by creating a Qt Quick application with the wizard in Qt Creator 3.1.1, and copy pasting this code into the default file called "main.qml"

import QtQuick 2.2
import QtQuick.Window 2.1

Window {
    id: container
    visible: true
    width: 1280
    height: 768

    Rectangle {
        id: rect
        gradient: Gradient {
            GradientStop { position: 0.0; color: "red" }
            GradientStop { position: 0.33; color: "yellow" }
            GradientStop { position: 1.0; color: "green" }
        }
        border.width: 2
        width: 600
        height: 600
        transform: Scale {
            id: scaler
            origin.x: pinchArea.m_x2
            origin.y: pinchArea.m_y2
            xScale: pinchArea.m_zoom2
            yScale: pinchArea.m_zoom2
        }
        PinchArea {
            id: pinchArea
            anchors.fill: parent
            property real m_x1: 0
            property real m_y1: 0
            property real m_y2: 0
            property real m_x2: 0
            property real m_zoom1: 0.5
            property real m_zoom2: 0.5
            property real m_max: 2
            property real m_min: 0.5

            onPinchStarted: {
                console.log("Pinch Started")
                m_x1 = scaler.origin.x
                m_y1 = scaler.origin.y
                m_x2 = pinch.startCenter.x
                m_y2 = pinch.startCenter.y
                rect.x = rect.x + (pinchArea.m_x1-pinchArea.m_x2)*(1-pinchArea.m_zoom1)
                rect.y = rect.y + (pinchArea.m_y1-pinchArea.m_y2)*(1-pinchArea.m_zoom1)
            }
            onPinchUpdated: {
                console.log("Pinch Updated")
                m_zoom1 = scaler.xScale
                var dz = pinch.scale-pinch.previousScale
                var newZoom = m_zoom1+dz
                if (newZoom <= m_max && newZoom >= m_min) {
                    m_zoom2 = newZoom
                }
            }
            MouseArea {
                id: dragArea
                hoverEnabled: true
                anchors.fill: parent
                drag.target: rect
                drag.filterChildren: true
                onWheel: {
                    console.log("Wheel Scrolled")
                    pinchArea.m_x1 = scaler.origin.x
                    pinchArea.m_y1 = scaler.origin.y
                    pinchArea.m_zoom1 = scaler.xScale

                    pinchArea.m_x2 = mouseX
                    pinchArea.m_y2 = mouseY

                    var newZoom
                    if (wheel.angleDelta.y > 0) {
                        newZoom = pinchArea.m_zoom1+0.1
                        if (newZoom <= pinchArea.m_max) {
                            pinchArea.m_zoom2 = newZoom
                        } else {
                            pinchArea.m_zoom2 = pinchArea.m_max
                        }
                    } else {
                        newZoom = pinchArea.m_zoom1-0.1
                        if (newZoom >= pinchArea.m_min) {
                            pinchArea.m_zoom2 = newZoom
                        } else {
                            pinchArea.m_zoom2 = pinchArea.m_min
                        }
                    }
                    rect.x = rect.x + (pinchArea.m_x1-pinchArea.m_x2)*(1-pinchArea.m_zoom1)
                    rect.y = rect.y + (pinchArea.m_y1-pinchArea.m_y2)*(1-pinchArea.m_zoom1)
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: console.log("Click in child")
                }
            }
        }
    }
}

By default the descendant MouseAreas of the "dragArea" will "steal" the dragging event, and drag will not work. In this case you can enable the drag by providing setting "drag.filterChildren" to true in "dragArea".

torstai 22. toukokuuta 2014

Let there be sound

If you want to add e.g. SoundEffects to your awesome Qml app, then besides using the "import QtMultimedia 5.0" in the Qml file, remember to add QT += multimedia to your .pro file!