Changeset View
Changeset View
Standalone View
Standalone View
src/controls/ListItemDragHandle.qml
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (C) 2018 by Marco Martin <mart@kde.org> | ||||
3 | * | ||||
4 | * This program is free software; you can redistribute it and/or modify | ||||
5 | * it under the terms of the GNU Library General Public License as | ||||
6 | * published by the Free Software Foundation; either version 2, or | ||||
7 | * (at your option) any later version. | ||||
8 | * | ||||
9 | * This program is distributed in the hope that it will be useful, | ||||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
12 | * GNU Library General Public License for more details | ||||
13 | * | ||||
14 | * You should have received a copy of the GNU Library General Public | ||||
15 | * License along with this program; if not, write to the | ||||
16 | * Free Software Foundation, Inc., | ||||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. | ||||
18 | */ | ||||
19 | | ||||
20 | import QtQuick 2.6 | ||||
21 | import QtQuick.Layouts 1.2 | ||||
22 | import org.kde.kirigami 2.4 as Kirigami | ||||
23 | | ||||
24 | /** | ||||
25 | * Implements a drag handle supposed to be in items in ListViews to reorder items | ||||
26 | * The ListView must visualize a model which supports item reordering, | ||||
27 | * such as ListModel.move() or QAbstractItemModel instances with moveRows() correctly implemented. | ||||
28 | * In order for ListItemDragHandle to work correctly, the listItem that is being dragged | ||||
29 | * should not directly be the delegate of the ListView, but a child of it. | ||||
30 | * | ||||
31 | * It is recomended to use DelagateRecycler as base delegate like the following code: | ||||
32 | * @code | ||||
33 | * ... | ||||
34 | * Component { | ||||
35 | * id: delegateComponent | ||||
36 | * Kirigami.AbstractListItem { | ||||
37 | * id: listItem | ||||
38 | * contentItem: RowLayout { | ||||
39 | * Kirigami.ListItemDragHandle { | ||||
40 | * listItem: listItem | ||||
41 | * listView: mainList | ||||
42 | * onMoveRequested: listModel.move(oldIndex, newIndex, 1) | ||||
43 | * } | ||||
44 | * Controls.Label { | ||||
45 | * text: model.label | ||||
46 | * } | ||||
47 | * } | ||||
48 | * } | ||||
49 | * } | ||||
50 | * ListView { | ||||
51 | * id: mainList | ||||
52 | * | ||||
53 | * model: ListModel { | ||||
54 | * id: listModel | ||||
55 | * ListItem { | ||||
56 | * lablel: "Item 1" | ||||
57 | * } | ||||
58 | * ListItem { | ||||
59 | * lablel: "Item 2" | ||||
60 | * } | ||||
61 | * ListItem { | ||||
62 | * lablel: "Item 3" | ||||
63 | * } | ||||
64 | * } | ||||
65 | * //this is optional to make list items animated when reordered | ||||
66 | * moveDisplaced: Transition { | ||||
67 | * YAnimator { | ||||
68 | * duration: Kirigami.Units.longDuration | ||||
69 | * easing.type: Easing.InOutQuad | ||||
70 | * } | ||||
71 | * } | ||||
72 | * delegate: Kirigami.DelegateRecycler { | ||||
73 | * width: mainList.width | ||||
74 | * sourceComponent: delegateComponent | ||||
75 | * } | ||||
76 | * } | ||||
77 | * ... | ||||
78 | * @endcode | ||||
79 | * | ||||
80 | * @inherits MouseArea | ||||
81 | * @since 2.5 | ||||
82 | */ | ||||
83 | MouseArea { | ||||
84 | id: root | ||||
85 | | ||||
86 | /** | ||||
87 | * listItem: Item | ||||
88 | * The id of the delegate that we want to drag around, which *must* | ||||
89 | * be a child of the actual ListView's delegate | ||||
90 | */ | ||||
91 | property Item listItem | ||||
92 | | ||||
93 | /** | ||||
94 | * listView: Listview | ||||
95 | * The id of the ListView the delegates belong to. | ||||
96 | */ | ||||
97 | property ListView listView | ||||
98 | | ||||
99 | /** | ||||
100 | * Emitted when the drag handle wants to move the item in the model | ||||
101 | * The following example does the move in the case a ListModel is used | ||||
102 | * @code | ||||
103 | * onMoveRequested: listModel.move(oldIndex, newIndex, 1) | ||||
104 | * @endcode | ||||
105 | * @param oldIndex the index the item is currently at | ||||
106 | * @param newIndex the index we want to move the item to | ||||
107 | */ | ||||
108 | signal moveRequested(int oldIndex, int newIndex) | ||||
109 | | ||||
110 | hoverEnabled: !Kirigami.Settings.tabletMode | ||||
111 | | ||||
112 | drag { | ||||
113 | target: listItem | ||||
114 | axis: Drag.YAxis | ||||
115 | minimumY: 0 | ||||
116 | maximumY: listView.height - listItem.height | ||||
117 | } | ||||
118 | Kirigami.Icon { | ||||
119 | id: internal | ||||
120 | source: "handle-sort" | ||||
121 | property int startY | ||||
122 | property int mouseDownY | ||||
123 | property Item originalParent | ||||
124 | property int autoScrollThreshold: listItem.height * 3 | ||||
125 | opacity: root.pressed || root.containsMouse ? 1 : 0.6 | ||||
126 | | ||||
127 | function arrangeItem() { | ||||
128 | var newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(listItem, 0, 0).y + internal.mouseDownY); | ||||
129 | | ||||
130 | if (Math.abs(listItem.y - internal.startY) > height && newIndex > -1 && newIndex != index) { | ||||
131 | root.moveRequested(index, newIndex); | ||||
132 | } | ||||
133 | } | ||||
134 | | ||||
135 | anchors.fill: parent | ||||
136 | } | ||||
137 | preventStealing: true | ||||
138 | implicitWidth: Kirigami.Units.iconSizes.smallMedium | ||||
139 | implicitHeight: implicitWidth | ||||
140 | | ||||
141 | | ||||
142 | onPressed: { | ||||
143 | internal.originalParent = listItem.parent; | ||||
144 | listItem.parent = listView; | ||||
145 | listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y; | ||||
146 | internal.originalParent.z = 99; | ||||
147 | internal.startY = listItem.y; | ||||
148 | internal.mouseDownY = mouse.y; | ||||
149 | } | ||||
150 | | ||||
151 | onPositionChanged: { | ||||
152 | internal.arrangeItem(); | ||||
153 | | ||||
154 | scrollTimer.interval = 500 * Math.max(0.1, (1-Math.max(internal.autoScrollThreshold - listItem.y, listItem.y - listView.height + internal.autoScrollThreshold + listItem.height) / internal.autoScrollThreshold)); | ||||
155 | scrollTimer.running = (listItem.y < internal.autoScrollThreshold || | ||||
156 | listItem.y > listView.height - internal.autoScrollThreshold); | ||||
157 | } | ||||
158 | onReleased: { | ||||
159 | listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y; | ||||
160 | listItem.parent = internal.originalParent; | ||||
161 | dropAnimation.running = true; | ||||
162 | scrollTimer.running = false; | ||||
163 | } | ||||
164 | onCanceled: released() | ||||
165 | SequentialAnimation { | ||||
166 | id: dropAnimation | ||||
167 | YAnimator { | ||||
168 | target: listItem | ||||
169 | from: listItem.y | ||||
170 | to: 0 | ||||
171 | duration: Kirigami.Units.longDuration | ||||
172 | easing.type: Easing.InOutQuad | ||||
173 | } | ||||
174 | PropertyAction { | ||||
175 | target: listItem.parent | ||||
176 | property: "z" | ||||
177 | value: 0 | ||||
178 | } | ||||
179 | } | ||||
180 | Timer { | ||||
181 | id: scrollTimer | ||||
182 | interval: 500 | ||||
183 | repeat: true | ||||
184 | onTriggered: { | ||||
185 | if (listItem.y < internal.autoScrollThreshold) { | ||||
186 | listView.contentY = Math.max(0, listView.contentY - Kirigami.Units.gridUnit) | ||||
187 | } else { | ||||
188 | listView.contentY = Math.min(listView.contentHeight - listView.height, listView.contentY + Kirigami.Units.gridUnit) | ||||
189 | } | ||||
190 | internal.arrangeItem(); | ||||
191 | } | ||||
192 | } | ||||
193 | } |