Making components draggable in QML

This is something which isn't trivial, and after figuring out how to implement it myself, I have written this blog post to share and will explain sections of the code, possibly in more detail at a later stage when this blog post is edited again.

Making components draggable in QML should be trivial to implement in components, and it is, in terms of implementing the event handlers and the dragging status and adding an offset property, except that the mouse position needs to be mapped globally to it's parent component to work correctly:

var globalMouse = nodeRectangle.parent.mapToGlobal(mouse.x, mouse.y);

Here is the complete source code for the component, which can be adapted for your use. This code was taken from a project where this component is used as a draggable node, so the most relevant parts for dragging functionality are the Rectangle and MouseArea components:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import QtQuick 2.0
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.5

Item {
    width: 200;
    height: 300;
    DropShadow {
        radius: 2
        samples: 10
        horizontalOffset: 2
        verticalOffset: 2
        transparentBorder: true
        fast: false
        cached: false
        anchors.fill: nodeRectangle
        source: nodeRectangle
    }
    Rectangle {
        id: nodeRectangle
        property bool isDragging
        property int offsetX : 0
        property int offsetY : 0
        QtObject {
            id: lastPosition
            property int x : nodeRectangle.parent.x
            property int y : nodeRectangle.parent.y
        }
        anchors.fill: parent
        radius: 10
        anchors.bottomMargin: 25
        anchors.leftMargin: 25
        anchors.rightMargin: 25
        anchors.topMargin: 25
        border.width: 0
        gradient: Gradient {
            GradientStop {
                position: 0.110
                color: "#ffffff"
            }
            GradientStop {
                position: 0.108
                color: "#cbcbcb"
            }

            GradientStop {
                position: 0.074
                color: "#dfdfdf"
            }
        }
        MouseArea {
            id: mouseArea
            anchors.bottomMargin: 0
            anchors.fill: parent
            onPressed: {
                nodeRectangle.isDragging = true;
                var globalMouse = nodeRectangle.parent.mapToGlobal(mouse.x, mouse.y);
                nodeRectangle.offsetX = globalMouse.x - nodeRectangle.parent.x;
                nodeRectangle.offsetY = globalMouse.y - nodeRectangle.parent.y;
            }
            onPositionChanged: {
                if (nodeRectangle.isDragging) {
                    var globalMouse = nodeRectangle.parent.mapToGlobal(mouse.x, mouse.y);
                    nodeRectangle.parent.x = (lastPosition.x - nodeRectangle.parent.x) + globalMouse.x - nodeRectangle.offsetX;
                    nodeRectangle.parent.y = (lastPosition.y - nodeRectangle.parent.y) + globalMouse.y - nodeRectangle.offsetY;
                }
            }
            Rectangle {
            id: closeButton
            x: 127
            y: 10
            width: 15
            height: 15
            radius: 10
            anchors.topMargin: 8
            anchors.top: parent.top
            gradient: Gradient {
                GradientStop {
                    position: 0
                    color: "#faaaaa"
                }

                GradientStop {
                    position: 1
                    color: "#f86767"
                }
            }
            border.color: "#ba5252"
            border.width: 1
            property bool isHovered : false
            MouseArea {
                id: closeButtonArea
                anchors.fill: parent
                hoverEnabled: true
                onEntered: {
                    closeButton.isHovered = true;
                }
                onExited: {
                    closeButton.isHovered = false;
                }
                onClicked: {
                    nodeRectangle.parent.destroy();
                }
            }
            states: State {
                name: "hovered"; when: closeButton.isHovered == true
                PropertyChanges { target: closeButton; color: "#d04a4a" }
            }
            transitions: Transition {
                NumberAnimation { properties: "color"; easing.type: Easing.SineCurve; duration: 100; }
            }
        }
            Text {
                id: text1
                x: 180
                y: 4
                color: "#202020"
                text: qsTr("Node Title")
                anchors.left: parent.left
                anchors.leftMargin: 8
                anchors.right: parent.right
                anchors.rightMargin: 120
                font.pixelSize: 15
            }
            Text {
                id: text2
                x: 187
                y: 53
                height: 22
                text: qsTr("Name")
                horizontalAlignment: Text.AlignRight
                anchors.rightMargin: 22
                font.pixelSize: 15
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.leftMargin: 11
            }
            Rectangle {
                id: nodeConnector
                x: 142
                y: 57
                width: 15
                height: 15
                color: "#cfca67"
                radius: 15
                border.color: "#e0dc8d"
                border.width: 2
            }
        }
    }
}

Comments

There are currently no comments

New Comment

required

required (not published)

optional

required