Search code examples
qtqmlqt-quick

Drag and dropping identical QML components on top of each other


I have a repeater that generates a bunch of rectangles. I want to be able to drag these rectangles around and generate an event when I drop one rectangle on another (think files and folders on an OS. I can drag folders & files, but I can drop a file on a folder and it stores it inside). I've tried the following but the dropArea gets confused because the object itself is draggable. OnDropped just never triggers. Is this possible?

Repeater {
            model: 5
            Item {
                id: element

                Item {
                    id: surfaceContainer

                    width: 150
                    height: 150
                    x: index * width

                    DropArea {
                        id: dropArea

                        anchors.fill: surfaceContainer

                        onContainsDragChanged: {
                            if (containsDrag && drag.source != draggableRectangle)
                                console.log("CHANGED")
                        }

                        onEntered: {
                            if (drag.source != draggableRectangle)
                                console.log("ENTERED")
                        }

                        onDropped: {
                            console.log("DROPPED")
                        }

                        onExited: {
                            if (drag.source != draggableRectangle && containsDrag)
                                drag.source.color = "yellow"
                        }
                    }

                    Rectangle {
                        id: draggableRectangle

                        x: width / 2

                        color: "blue"
                        height: 100
                        width: 100

                        Drag.active: dragArea.drag.active

                        MouseArea {
                            id: dragArea

                            anchors.fill: draggableRectangle
                            drag.target: draggableRectangle

                            onDoubleClicked: {
                                connectionId.visible = !connectionId.visible
                            }
                        }
                    }
                }
            }
        }

Solution

  • [EDIT]

    I made the following changes to your application:

    • Initialize the DropArea to match the Rectangle geometry
    • Everytime the Rectangle has completed dragging, to re-update the DropArea to match the Rectangle geometry
    • When an Rectangle is being dragged, only enable the DropArea that are valid destinations, i.e. it can only be dragged to a DropArea of a different delegate
    import QtQuick
    import QtQuick.Controls
    Page {
        property var sourceItem: null
        property var destItem: null
        property string statusText: ""
        Repeater {
            model: [ "A", "B", "C", "D", "E" ]
            Item {
                z: dragArea.drag.active ? 2 : 0
                DropArea {
                    id: dropArea
                    x: index * 150 + 100
                    y: 100
                    width: 100
                    height: 100
                    enabled: sourceItem && sourceItem !== modelData
                    onContainsDragChanged: {
                        if (containsDrag) {
                            destItem = modelData;
                        } else {
                            destItem = null;
                        }
                    }
                }
                Rectangle {
                    id: dragRect
                    x: index * 150 + 100
                    y: 100
                    width: 100
                    height: 100
                    color: dropArea.containsDrag ? "green" : dragArea.drag.active ? "#c00" : "#f88"
                    Drag.active: dragArea.drag.active
                    Drag.hotSpot.x: 10
                    Drag.hotSpot.y: 10
                    Label {
                        anchors.centerIn: parent
                        text: modelData
                    }
                    MouseArea {
                        id: dragArea
                        anchors.fill: parent
                        
                        drag.target: parent
                        drag.onActiveChanged: {
                        if (drag.active) {
                            sourceItem = modelData;
                        } else {
                            if (sourceItem && destItem) {
                                statusText = "Dragged " + JSON.stringify(sourceItem) + " to " + JSON.stringify(destItem);
                            } else {
                                statusText = "";
                            }
                            sourceItem = null;
                            dropArea.x = dragRect.x;
                            dropArea.y = dragRect.y; 
                        }
    }
                    }
                }
            }
        }
        Frame {
            anchors.horizontalCenter: parent.horizontalCenter
            y: parent.height * 3 / 4
            visible: statusText
            Text {
                text: statusText
            }
        }
    }
    

    You can Try it Online!