Kirigami2

ListItemDragHandle.qml
1/*
2 * SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2024 Filipe Azevedo <pasnox@gmail.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8import QtQuick
9import org.kde.kirigami as Kirigami
10
11/**
12 * Implements a drag handle supposed to be in items in ListViews to reorder items
13 * The ListView must visualize a model which supports item reordering,
14 * such as ListModel.move() or QAbstractItemModel instances with moveRows() correctly implemented.
15 * In order for ListItemDragHandle to work correctly, the listItem that is being dragged
16 * should not directly be the delegate of the ListView, but a child of it.
17 *
18 * It is recommended to use DelagateRecycler as base delegate like the following code:
19 * @code
20 * import QtQuick
21 * import QtQuick.Layouts
22 * import QtQuick.Controls as QQC2
23 * import org.kde.kirigami as Kirigami
24 * ...
25 * Component {
26 * id: delegateComponent
27 * QQC2.ItemDelegate {
28 * id: listItem
29 * contentItem: RowLayout {
30 * Kirigami.ListItemDragHandle {
31 * listItem: listItem
32 * listView: mainList
33 * onMoveRequested: (oldIndex, newIndex) => {
34 * listModel.move(oldIndex, newIndex, 1);
35 * }
36 * }
37 * QQC2.Label {
38 * text: model.label
39 * }
40 * }
41 * }
42 * }
43 * ListView {
44 * id: mainList
45 *
46 * model: ListModel {
47 * id: listModel
48 * ListElement {
49 * label: "Item 1"
50 * }
51 * ListElement {
52 * label: "Item 2"
53 * }
54 * ListElement {
55 * label: "Item 3"
56 * }
57 * }
58 * //this is optional to make list items animated when reordered
59 * moveDisplaced: Transition {
60 * YAnimator {
61 * duration: Kirigami.Units.longDuration
62 * easing.type: Easing.InOutQuad
63 * }
64 * }
65 * delegate: Loader {
66 * width: mainList.width
67 * sourceComponent: delegateComponent
68 * }
69 * }
70 * ...
71 * @endcode
72 *
73 * @since 2.5
74 * @inherit QtQuick.Item
75 */
76Item {
77 id: root
78
79 /**
80 * @brief This property holds the delegate that will be dragged around.
81 *
82 * This item *must* be a child of the actual ListView's delegate.
83 */
84 property Item listItem
86 /**
87 * @brief This property holds the ListView that the delegate belong to.
88 */
89 property ListView listView
90
91 /**
92 * @brief This property holds the fact that we are doing incremental move requests or not
93 */
94 property bool incrementalMoves: true
95
96 /**
97 * @brief This property holds the fact that the handle is being dragged
98 */
99 readonly property alias dragActive: mouseArea.drag.active
100
101 /**
102 * @brief This signal is emitted when the drag handle wants to move the item in the model.
103 *
104 * The following example does the move in the case a ListModel is used:
105 * @code
106 * onMoveRequested: (oldIndex, newIndex) => {
107 * listModel.move(oldIndex, newIndex, 1);
108 * }
109 * @endcode
110 * @param oldIndex the index the item is currently at
111 * @param newIndex the index we want to move the item to
112 */
113 signal moveRequested(int oldIndex, int newIndex)
115 /**
116 * @brief This signal is emitted when the drag operation is complete and the item has been
117 * dropped in the new final position.
118 * @param oldIndex the index the item is currently at
119 * @param newIndex the index we want to drop the item to
120 */
121 signal dropped(int oldIndex, int newIndex)
122
123 implicitWidth: Kirigami.Units.iconSizes.smallMedium
124 implicitHeight: Kirigami.Units.iconSizes.smallMedium
125
126 MouseArea {
127 id: mouseArea
128
129 anchors.fill: parent
130
131 drag {
132 target: listItem
133 axis: Drag.YAxis
134 minimumY: 0
135 }
136
137 cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
138 preventStealing: true
139
140 QtObject {
141 id: _previousMove
142
143 property int oldIndex: -1
144 property int newIndex: -1
145
146 function reset() {
147 _previousMove.oldIndex = -1;
148 _previousMove.newIndex = -1;
149 }
150 }
151
152 Kirigami.Icon {
153 id: internal
154
155 anchors.fill: parent
156
157 source: "handle-sort"
158 opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
159
160 property int startY
161 property int mouseDownY
162 property Item originalParent
163 property int listItemLastY
164 property bool draggingUp
165
166 function arrangeItem() {
167 const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(listItem, 0, listItem.height / 2).y);
168
169 if (newIndex > -1 && ((incrementalMoves && internal.draggingUp && newIndex < index) ||
170 (incrementalMoves && !internal.draggingUp && newIndex > index) ||
171 (!incrementalMoves && newIndex < listView.count))) {
172 if (_previousMove.oldIndex === index && _previousMove.newIndex === newIndex) {
173 return;
174 }
175
176 _previousMove.oldIndex = index;
177 _previousMove.newIndex = newIndex;
178
179 root.moveRequested(index, newIndex);
180 }
181 }
182 }
183
184 onPressed: mouse => {
185 internal.originalParent = listItem.parent;
186 listItem.parent = listView;
187 listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
188 internal.originalParent.z = 99;
189 internal.startY = listItem.y;
190 internal.listItemLastY = listItem.y;
191 internal.mouseDownY = mouse.y;
192 // while dragging listItem's height could change
193 // we want a const maximumY during the dragging time
194 mouseArea.drag.maximumY = listView.height - listItem.height;
195 }
196
197 onPositionChanged: mouse => {
198 if (!pressed || listItem.y === internal.listItemLastY) {
199 return;
200 }
201
202 internal.draggingUp = listItem.y < internal.listItemLastY
203 internal.listItemLastY = listItem.y;
204
205 internal.arrangeItem();
206
207 // autoscroll when the dragging item reaches the listView's top/bottom boundary
208 scrollTimer.running = (listView.contentHeight > listView.height)
209 && ((listItem.y === 0 && !listView.atYBeginning)
210 || (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd));
211 }
212
213 onReleased: mouse => dropped()
214 onCanceled: dropped()
215
216 function dropped() {
217 listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
218 listItem.parent = internal.originalParent;
219 dropAnimation.running = true;
220 scrollTimer.running = false;
221 root.dropped(_previousMove.oldIndex, _previousMove.newIndex);
222 _previousMove.reset();
223 }
224
225 SequentialAnimation {
226 id: dropAnimation
227 YAnimator {
228 target: listItem
229 from: listItem.y
230 to: 0
231 duration: Kirigami.Units.longDuration
232 easing.type: Easing.InOutQuad
233 }
234 PropertyAction {
235 target: listItem.parent
236 property: "z"
237 value: 0
238 }
239 }
240
241 Timer {
242 id: scrollTimer
243
244 interval: 50
245 repeat: true
246
247 onTriggered: {
248 if (internal.draggingUp) {
249 listView.contentY -= Kirigami.Units.gridUnit;
250 if (listView.atYBeginning) {
251 listView.positionViewAtBeginning();
252 stop();
253 }
254 } else {
255 listView.contentY += Kirigami.Units.gridUnit;
256 if (listView.atYEnd) {
257 listView.positionViewAtEnd();
258 stop();
259 }
260 }
261 internal.arrangeItem();
262 }
263 }
264 }
265}
void stop(Ekos::AlignState mode)
QAction * repeat(const QObject *recvr, const char *slot, QObject *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 11:55:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.