Warning, /frameworks/kirigami/src/controls/ListItemDragHandle.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *  SPDX-FileCopyrightText: 2018 by Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 import QtQuick
0008 import org.kde.kirigami 2 as Kirigami
0009 
0010 /**
0011  * Implements a drag handle supposed to be in items in ListViews to reorder items
0012  * The ListView must visualize a model which supports item reordering,
0013  * such as ListModel.move() or QAbstractItemModel  instances with moveRows() correctly implemented.
0014  * In order for ListItemDragHandle to work correctly, the listItem that is being dragged
0015  * should not directly be the delegate of the ListView, but a child of it.
0016  *
0017  * It is recommended to use DelagateRecycler as base delegate like the following code:
0018  * @code
0019  * import QtQuick 2.15
0020  * import QtQuick.Layouts 1.15
0021  * import QtQuick.Controls 2.15 as QQC2
0022  * import org.kde.kirigami 2.19 as Kirigami
0023  *   ...
0024  *   Component {
0025  *       id: delegateComponent
0026  *       QQC2.ItemDelegate {
0027  *           id: listItem
0028  *           contentItem: RowLayout {
0029  *               Kirigami.ListItemDragHandle {
0030  *                   listItem: listItem
0031  *                   listView: mainList
0032  *                   onMoveRequested: (oldIndex, newIndex) => {
0033  *                       listModel.move(oldIndex, newIndex, 1);
0034  *                   }
0035  *               }
0036  *               QQC2.Label {
0037  *                   text: model.label
0038  *               }
0039  *           }
0040  *       }
0041  *   }
0042  *   ListView {
0043  *       id: mainList
0044  *
0045  *       model: ListModel {
0046  *           id: listModel
0047  *           ListElement {
0048  *               label: "Item 1"
0049  *           }
0050  *           ListElement {
0051  *               label: "Item 2"
0052  *           }
0053  *           ListElement {
0054  *               label: "Item 3"
0055  *           }
0056  *       }
0057  *       //this is optional to make list items animated when reordered
0058  *       moveDisplaced: Transition {
0059  *           YAnimator {
0060  *               duration: Kirigami.Units.longDuration
0061  *               easing.type: Easing.InOutQuad
0062  *           }
0063  *       }
0064  *       delegate: Loader {
0065  *           width: mainList.width
0066  *           sourceComponent: delegateComponent
0067  *       }
0068  *   }
0069  *   ...
0070  * @endcode
0071  *
0072  * @since 2.5
0073  * @inherit QtQuick.Item
0074  */
0075 Item {
0076     id: root
0077 
0078     /**
0079      * @brief This property holds the delegate that will be dragged around.
0080      *
0081      * This item *must* be a child of the actual ListView's delegate.
0082      */
0083     property Item listItem
0084 
0085     /**
0086      * @brief This property holds the ListView that the delegate belong to.
0087      */
0088     property ListView listView
0089 
0090     /**
0091      * @brief This signal is emitted when the drag handle wants to move the item in the model.
0092      *
0093      * The following example does the move in the case a ListModel is used:
0094      * @code
0095      * onMoveRequested: (oldIndex, newIndex) => {
0096      *     listModel.move(oldIndex, newIndex, 1);
0097      * }
0098      * @endcode
0099      * @param oldIndex the index the item is currently at
0100      * @param newIndex the index we want to move the item to
0101      */
0102     signal moveRequested(int oldIndex, int newIndex)
0103 
0104     /**
0105      * @brief This signal is emitted when the drag operation is complete and the item has been
0106      * dropped in the new final position.
0107      */
0108     signal dropped()
0109 
0110     implicitWidth: Kirigami.Units.iconSizes.smallMedium
0111     implicitHeight: Kirigami.Units.iconSizes.smallMedium
0112 
0113     MouseArea {
0114         id: mouseArea
0115 
0116         anchors.fill: parent
0117 
0118         drag {
0119             target: listItem
0120             axis: Drag.YAxis
0121             minimumY: 0
0122         }
0123 
0124         cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
0125         preventStealing: true
0126 
0127         Kirigami.Icon {
0128             id: internal
0129 
0130             anchors.fill: parent
0131 
0132             source: "handle-sort"
0133             opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
0134 
0135             property int startY
0136             property int mouseDownY
0137             property Item originalParent
0138             property int listItemLastY
0139             property bool draggingUp
0140 
0141             function arrangeItem() {
0142                 const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(mouseArea, 0, internal.mouseDownY).y);
0143 
0144                 if (newIndex > -1 && ((internal.draggingUp && newIndex < index) || (!internal.draggingUp && newIndex > index))) {
0145                     root.moveRequested(index, newIndex);
0146                 }
0147             }
0148         }
0149 
0150         onPressed: mouse => {
0151             internal.originalParent = listItem.parent;
0152             listItem.parent = listView;
0153             listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
0154             internal.originalParent.z = 99;
0155             internal.startY = listItem.y;
0156             internal.listItemLastY = listItem.y;
0157             internal.mouseDownY = mouse.y;
0158             // while dragging listItem's height could change
0159             // we want a const maximumY during the dragging time
0160             mouseArea.drag.maximumY = listView.height - listItem.height;
0161         }
0162 
0163         onPositionChanged: mouse => {
0164             if (!pressed || listItem.y === internal.listItemLastY) {
0165                 return;
0166             }
0167 
0168             internal.draggingUp = listItem.y < internal.listItemLastY
0169             internal.listItemLastY = listItem.y;
0170 
0171             internal.arrangeItem();
0172 
0173              // autoscroll when the dragging item reaches the listView's top/bottom boundary
0174             scrollTimer.running = (listView.contentHeight > listView.height)
0175                 && ((listItem.y === 0 && !listView.atYBeginning)
0176                     || (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd));
0177         }
0178 
0179         onReleased: mouse => dropped()
0180         onCanceled: dropped()
0181 
0182         function dropped() {
0183             listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
0184             listItem.parent = internal.originalParent;
0185             dropAnimation.running = true;
0186             scrollTimer.running = false;
0187             root.dropped();
0188         }
0189 
0190         SequentialAnimation {
0191             id: dropAnimation
0192             YAnimator {
0193                 target: listItem
0194                 from: listItem.y
0195                 to: 0
0196                 duration: Kirigami.Units.longDuration
0197                 easing.type: Easing.InOutQuad
0198             }
0199             PropertyAction {
0200                 target: listItem.parent
0201                 property: "z"
0202                 value: 0
0203             }
0204         }
0205 
0206         Timer {
0207             id: scrollTimer
0208 
0209             interval: 50
0210             repeat: true
0211 
0212             onTriggered: {
0213                 if (internal.draggingUp) {
0214                     listView.contentY -= Kirigami.Units.gridUnit;
0215                     if (listView.atYBeginning) {
0216                         listView.positionViewAtBeginning();
0217                         stop();
0218                     }
0219                 } else {
0220                     listView.contentY += Kirigami.Units.gridUnit;
0221                     if (listView.atYEnd) {
0222                         listView.positionViewAtEnd();
0223                         stop();
0224                     }
0225                 }
0226                 internal.arrangeItem();
0227             }
0228         }
0229     }
0230 }