import QtQuick 2.13 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.13 as Kirigami Item { id: root /** * @brief This property holds the delegate that will be dragged around. * * This item *must* be a child of the actual ListView's delegate. */ property Item listItem /** * @brief This property holds the ListView that the delegate belong to. */ property ListView listView /** * @brief This signal is emitted when the drag handle wants to move the item in the model. * * The following example does the move in the case a ListModel is used: * @code{.qml} * onMoveRequested: listModel.move(oldIndex, newIndex, 1) * @endcode * @param oldIndex the index the item is currently at * @param newIndex the index we want to move the item to */ signal moveRequested(int oldIndex, int newIndex) /** * @brief This signal is emitted when the drag operation is complete and the item has been * dropped in the new final position. */ signal dropped() implicitWidth: Kirigami.Units.iconSizes.smallMedium implicitHeight: implicitWidth MouseArea { id: mouseArea anchors.fill: parent drag { target: listItem axis: Drag.YAxis minimumY: 0 } cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor Kirigami.Icon { id: internal source: "handle-sort" property int startY property int mouseDownY property Item originalParent opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6 property int listItemLastY property bool draggingUp color: "white" function arrangeItem() { const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(mouseArea, 0, internal.mouseDownY).y); if (newIndex > -1 && ((internal.draggingUp && newIndex < index) || (!internal.draggingUp && newIndex > index))) { root.moveRequested(index, newIndex); } } anchors.fill: parent } preventStealing: true onPressed: mouse => { internal.originalParent = listItem.parent; listItem.parent = listView; listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y; internal.originalParent.z = 99; internal.startY = listItem.y; internal.listItemLastY = listItem.y; internal.mouseDownY = mouse.y; // while dragging listItem's height could change // we want a const maximumY during the dragging time mouseArea.drag.maximumY = listView.height - listItem.height; } onPositionChanged: mouse => { if (!pressed || listItem.y === internal.listItemLastY) { return; } internal.draggingUp = listItem.y < internal.listItemLastY internal.listItemLastY = listItem.y; internal.arrangeItem(); // autoscroll when the dragging item reaches the listView's top/bottom boundary scrollTimer.running = (listView.contentHeight > listView.height) && ( (listItem.y === 0 && !listView.atYBeginning) || (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd) ); } onReleased: mouse => { listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y; listItem.parent = internal.originalParent; dropAnimation.running = true; scrollTimer.running = false; root.dropped(); } onCanceled: released() SequentialAnimation { id: dropAnimation YAnimator { target: listItem from: listItem.y to: 0 duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } PropertyAction { target: listItem.parent property: "z" value: 0 } } Timer { id: scrollTimer interval: 50 repeat: true onTriggered: { if (internal.draggingUp) { listView.contentY -= Kirigami.Units.gridUnit; if (listView.atYBeginning) { listView.positionViewAtBeginning(); stop(); } } else { listView.contentY += Kirigami.Units.gridUnit; if (listView.atYEnd) { listView.positionViewAtEnd(); stop(); } } internal.arrangeItem(); } } } }