Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using drag and drop with ListView to create an inventory UI

I'd like to create an inventory UI for my game using ListView, where the items can be removed from the inventory by dragging and dropping them in the level. If an item was not dropped properly (still inside the inventory), it should be placed back where it was before the drag.

I've got the following code, but I don't know how to achieve what I'm after, even after looking at the Drag and Drop example.

import QtQuick 2.3

Rectangle {
    id: root
    width: 400
    height: 400

    ListView {
        id: listView
        width: parent.width / 2
        height: parent.height

        model: ListModel {
            Component.onCompleted: {
                for (var i = 0; i < 10; ++i) {
                    append({value: i});
                }
            }
        }

        delegate: Item {
            id: delegateItem
            width: listView.width
            height: 50

            Rectangle {
                id: dragRect
                width: listView.width
                height: 50
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                color: "salmon"
                border.color: Qt.darker(color)

                Text {
                    anchors.centerIn: parent
                    text: modelData
                }

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    drag.target: dragRect
                }

                Drag.hotSpot.x: dragRect.width / 2
                Drag.hotSpot.y: dragRect.height / 2
            }
        }
    }

    Rectangle {
        width: parent.width / 2
        height: parent.height
        anchors.right: parent.right
        color: "#aaff0011"

        DropArea {
            id: dropArea
            anchors.fill: parent
        }
    }
}
like image 732
Mitch Avatar asked Sep 25 '14 18:09

Mitch


1 Answers

You can achieve this with the following code:

import QtQuick 2.3

Rectangle {
    id: root
    width: 400
    height: 400

    ListView {
        id: listView
        width: parent.width / 2
        height: parent.height

        property int dragItemIndex: -1

        model: ListModel {
            Component.onCompleted: {
                for (var i = 0; i < 10; ++i) {
                    append({value: i});
                }
            }
        }

        delegate: Item {
            id: delegateItem
            width: listView.width
            height: 50

            Rectangle {
                id: dragRect
                width: listView.width
                height: 50
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                color: "salmon"
                border.color: Qt.darker(color)

                Text {
                    anchors.centerIn: parent
                    text: modelData
                }

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    drag.target: dragRect

                    drag.onActiveChanged: {
                        if (mouseArea.drag.active) {
                            listView.dragItemIndex = index;
                        }
                        dragRect.Drag.drop();
                    }
                }

                states: [
                    State {
                        when: dragRect.Drag.active
                        ParentChange {
                            target: dragRect
                            parent: root
                        }

                        AnchorChanges {
                            target: dragRect
                            anchors.horizontalCenter: undefined
                            anchors.verticalCenter: undefined
                        }
                    }
                ]

                Drag.active: mouseArea.drag.active
                Drag.hotSpot.x: dragRect.width / 2
                Drag.hotSpot.y: dragRect.height / 2
            }
        }
    }

    Rectangle {
        width: parent.width / 2
        height: parent.height
        anchors.right: parent.right
        color: "#aaff0011"

        DropArea {
            id: dropArea
            anchors.fill: parent
            onDropped: {
                listView.model.remove(listView.dragItemIndex);
                listView.dragItemIndex = -1;
            }
        }
    }
}

Some things to notice in this example:

  1. We store the dragItemIndex so that we know which item is being dragged. We might be able to achieve the same thing by looking at DropArea's drag.source property, but then we'd have to expose an index property in the delegate, and the documentation discourages storing state in delegates.

  2. To achieve the "items get placed back where they were on unsuccessful drags" functionality, we make dragRect a child of the actual delegate item, so that it has a parent to stick to. If we didn't do this, the item's parent would be the ListView, and when unsuccessfully dropped, it would just lay where it was last dragged.

  3. We use the same state change behaviour as the Drag and Drop example; upon dragging we want to remove the anchors from the item and let it be dragged freely. If the drag fails, the when condition for the state (dragRect.Drag.active) becomes false, and the item is parented back to the delegate item that hasn't moved from its original place in the list view. The anchors are also restored. This is a useful feature of states; being able to implicitly restore previous state.

  4. In the MouseArea's drag.onActiveChanged signal handler, we call dragRect.Drag.drop() so that we can respond to that event in DropArea's onDropped signal handler, and remove the item. After the item has been removed, we reset the dragItemIndex to an invalid index.

like image 196
Mitch Avatar answered Nov 15 '22 08:11

Mitch