Changeset View
Changeset View
Standalone View
Standalone View
src/controls/templates/SwipeListItem.qml
Show All 11 Lines | |||||
12 | * GNU Library General Public License for more details | 12 | * GNU Library General Public License for more details | ||
13 | * | 13 | * | ||
14 | * You should have received a copy of the GNU Library General Public | 14 | * You should have received a copy of the GNU Library General Public | ||
15 | * License along with this program; if not, write to the | 15 | * License along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | 16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. | 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. | ||
18 | */ | 18 | */ | ||
19 | 19 | | |||
20 | import QtQuick 2.5 | 20 | import QtQuick 2.7 | ||
21 | import QtQuick.Layouts 1.2 | 21 | import QtQuick.Layouts 1.2 | ||
22 | import QtQuick.Controls 1.0 as Controls | | |||
23 | import QtQuick.Controls.Private 1.0 | 22 | import QtQuick.Controls.Private 1.0 | ||
24 | import org.kde.kirigami 1.0 | 23 | import org.kde.kirigami 2.0 | ||
25 | import "../private" | 24 | import "../private" | ||
25 | import QtQuick.Templates 2.0 as T2 | ||||
26 | 26 | | |||
27 | /** | 27 | /** | ||
28 | * An item delegate Intended to support extra actions obtainable | 28 | * An item delegate Intended to support extra actions obtainable | ||
29 | * by uncovering them by dragging away the item with the handle | 29 | * by uncovering them by dragging away the item with the handle | ||
30 | * This acts as a container for normal list items. | 30 | * This acts as a container for normal list items. | ||
31 | * Any subclass of AbstractListItem can be assigned as the contentItem property. | 31 | * Any subclass of AbstractListItem can be assigned as the contentItem property. | ||
32 | * @code | 32 | * @code | ||
33 | * ListView { | 33 | * ListView { | ||
Show All 14 Lines | |||||
48 | * ] | 48 | * ] | ||
49 | * } | 49 | * } | ||
50 | * | 50 | * | ||
51 | * } | 51 | * } | ||
52 | * @endcode | 52 | * @endcode | ||
53 | * | 53 | * | ||
54 | * @inherit QtQuick.Item | 54 | * @inherit QtQuick.Item | ||
55 | */ | 55 | */ | ||
56 | Item { | 56 | T2.ItemDelegate { | ||
57 | id: listItem | 57 | id: listItem | ||
58 | 58 | | |||
59 | //BEGIN properties | 59 | //BEGIN properties | ||
60 | /** | 60 | /** | ||
61 | * contentItem: Item | | |||
62 | * This property holds the visual content item. | | |||
63 | * | | |||
64 | * Note: The content item is automatically resized inside the | | |||
65 | * padding of the control. | | |||
66 | */ | | |||
67 | default property Item contentItem | | |||
68 | | ||||
69 | /** | | |||
70 | * supportsMouseEvents: bool | 61 | * supportsMouseEvents: bool | ||
71 | * Holds if the item emits signals related to mouse interaction. | 62 | * Holds if the item emits signals related to mouse interaction. | ||
72 | *TODO: remove | 63 | *TODO: remove | ||
73 | * The default value is false. | 64 | * The default value is false. | ||
74 | */ | 65 | */ | ||
75 | property alias supportsMouseEvents: itemMouse.enabled | 66 | property alias supportsMouseEvents: listItem.hoverEnabled | ||
76 | | ||||
77 | /** | | |||
78 | * clicked: signal | | |||
79 | * This signal is emitted when there is a click. | | |||
80 | * | | |||
81 | * This is disabled by default, set enabled to true to use it. | | |||
82 | * @see enabled | | |||
83 | */ | | |||
84 | signal clicked | | |||
85 | | ||||
86 | | ||||
87 | /** | | |||
88 | * pressAndHold: signal | | |||
89 | * The user pressed the item with the mouse and didn't release it for a | | |||
90 | * certain amount of time. | | |||
91 | * | | |||
92 | * This is disabled by default, set enabled to true to use it. | | |||
93 | * @see enabled | | |||
94 | */ | | |||
95 | signal pressAndHold | | |||
96 | | ||||
97 | /** | | |||
98 | * checked: bool | | |||
99 | * If true makes the list item look as checked or pressed. It has to be set | | |||
100 | * from the code, it won't change by itself. | | |||
101 | */ | | |||
102 | property bool checked: false | | |||
103 | | ||||
104 | /** | | |||
105 | * pressed: bool | | |||
106 | * True when the user is pressing the mouse over the list item and | | |||
107 | * supportsMouseEvents is set to true | | |||
108 | */ | | |||
109 | property alias pressed: itemMouse.pressed | | |||
110 | 67 | | |||
111 | /** | 68 | /** | ||
112 | * containsMouse: bool | 69 | * containsMouse: bool | ||
113 | * True when the user hover the mouse over the list item | 70 | * True when the user hover the mouse over the list item | ||
114 | * NOTE: on mobile touch devices this will be true only when pressed is also true | 71 | * NOTE: on mobile touch devices this will be true only when pressed is also true | ||
115 | */ | 72 | */ | ||
116 | property alias containsMouse: itemMouse.containsMouse | 73 | property alias containsMouse: listItem.hovered | ||
117 | 74 | | |||
118 | /** | 75 | /** | ||
119 | * sectionDelegate: bool | 76 | * sectionDelegate: bool | ||
120 | * If true the item will be a delegate for a section, so will look like a | 77 | * If true the item will be a delegate for a section, so will look like a | ||
121 | * "title" for the items under it. | 78 | * "title" for the items under it. | ||
122 | */ | 79 | */ | ||
123 | property bool sectionDelegate: false | 80 | property bool sectionDelegate: false | ||
124 | 81 | | |||
125 | /** | 82 | /** | ||
126 | * separatorVisible: bool | 83 | * separatorVisible: bool | ||
127 | * True if the separator between items is visible | 84 | * True if the separator between items is visible | ||
128 | * default: true | 85 | * default: true | ||
129 | */ | 86 | */ | ||
130 | property bool separatorVisible: true | 87 | property bool separatorVisible: true | ||
131 | 88 | | |||
132 | /** | 89 | /** | ||
133 | * actions: list<Action> | 90 | * actions: list<Action> | ||
134 | * Defines the actions for the list item: at most 4 buttons will | 91 | * Defines the actions for the list item: at most 4 buttons will | ||
135 | * contain the actions for the item, that can be revealed by | 92 | * contain the actions for the item, that can be revealed by | ||
136 | * sliding away the list item. | 93 | * sliding away the list item. | ||
137 | */ | 94 | */ | ||
138 | property list<Action> actions | 95 | property list<Action> actions | ||
139 | 96 | | |||
140 | | ||||
141 | /** | | |||
142 | * position: real | | |||
143 | * This property holds the position of the dragged list item relative to its | | |||
144 | * final destination (just like the Drawer). That is, the position | | |||
145 | * will be 0 when the list item is fully closed, and 1 when fully open. | | |||
146 | */ | | |||
147 | property real position: 0 | | |||
148 | | ||||
149 | /** | | |||
150 | * background: Item | | |||
151 | * This property holds the background item. | | |||
152 | * | | |||
153 | * Note: If the background item has no explicit size specified, | | |||
154 | * it automatically follows the control's size. | | |||
155 | * In most cases, there is no need to specify width or | | |||
156 | * height for a background item. | | |||
157 | */ | | |||
158 | property Item background | | |||
159 | | ||||
160 | /** | 97 | /** | ||
161 | * textColor: color | 98 | * textColor: color | ||
162 | * Color for the text in the item | 99 | * Color for the text in the item | ||
163 | * | 100 | * | ||
164 | * Note: if custom text elements are inserted in an AbstractListItem, | 101 | * Note: if custom text elements are inserted in an AbstractListItem, | ||
165 | * their color proprty will ahve to be manually binded with this property | 102 | * their color proprty will ahve to be manually binded with this property | ||
166 | */ | 103 | */ | ||
167 | property color textColor: Theme.viewTextColor | 104 | property color textColor: Theme.viewTextColor | ||
Show All 16 Lines | |||||
184 | 121 | | |||
185 | /** | 122 | /** | ||
186 | * activeBackgroundColor: color | 123 | * activeBackgroundColor: color | ||
187 | * Color for the background of the item when pressed or selected | 124 | * Color for the background of the item when pressed or selected | ||
188 | * It is advised to leave the default value (Theme.highlightColor) | 125 | * It is advised to leave the default value (Theme.highlightColor) | ||
189 | */ | 126 | */ | ||
190 | property color activeBackgroundColor: Theme.highlightColor | 127 | property color activeBackgroundColor: Theme.highlightColor | ||
191 | 128 | | |||
129 | default property alias _default: listItem.contentItem | ||||
130 | | ||||
131 | implicitWidth: contentItem ? contentItem.implicitWidth : Units.gridUnit * 12 | ||||
132 | width: parent ? parent.width : implicitWidth | ||||
133 | implicitHeight: contentItem.implicitHeight + Units.smallSpacing * 5 | ||||
134 | | ||||
135 | leftPadding: Units.smallSpacing * 2 | ||||
136 | topPadding: Units.smallSpacing * 2 | ||||
137 | rightPadding: Units.smallSpacing * 2 | ||||
138 | bottomPadding: Units.smallSpacing * 2 | ||||
139 | | ||||
140 | //END properties | ||||
141 | | ||||
192 | Item { | 142 | Item { | ||
193 | id: behindItem | 143 | id: behindItem | ||
194 | parent: listItem | 144 | parent: listItem | ||
145 | z: -1 | ||||
195 | anchors { | 146 | anchors { | ||
196 | fill: parent | 147 | fill: parent | ||
197 | leftMargin: height | | |||
198 | } | 148 | } | ||
199 | Rectangle { | 149 | Rectangle { | ||
200 | id: shadowHolder | 150 | id: shadowHolder | ||
201 | color: Theme.backgroundColor | 151 | color: Theme.backgroundColor | ||
202 | anchors.fill: parent | 152 | anchors.fill: parent | ||
203 | } | 153 | } | ||
204 | EdgeShadow { | 154 | EdgeShadow { | ||
205 | edge: Qt.TopEdge | 155 | edge: Qt.TopEdge | ||
206 | anchors { | 156 | anchors { | ||
207 | right: parent.right | 157 | right: parent.right | ||
208 | left: parent.left | 158 | left: parent.left | ||
209 | top: parent.top | 159 | top: parent.top | ||
210 | } | 160 | } | ||
211 | } | 161 | } | ||
212 | EdgeShadow { | 162 | EdgeShadow { | ||
213 | edge: Qt.LeftEdge | 163 | edge: Qt.LeftEdge | ||
214 | x: behindItem.width - (behindItem.width * listItem.position) | 164 | x: listItem.background.x + listItem.background.width | ||
215 | anchors { | 165 | anchors { | ||
216 | top: parent.top | 166 | top: parent.top | ||
217 | bottom: parent.bottom | 167 | bottom: parent.bottom | ||
218 | } | 168 | } | ||
219 | } | 169 | } | ||
220 | } | 170 | MouseArea { | ||
221 | 171 | anchors.fill: parent | |||
222 | implicitWidth: parent ? parent.width : contentItem.width + paddingItem.anchors.margins * 2 | 172 | preventStealing: true | ||
223 | implicitHeight: contentItem.height + Units.smallSpacing * 5 | 173 | enabled: background.x != 0 | ||
224 | //END properties | 174 | onClicked: { | ||
225 | 175 | positionAnimation.from = background.x; | |||
226 | //BEGIN signal handlers | 176 | positionAnimation.to = 0; | ||
227 | onBackgroundChanged: { | 177 | positionAnimation.running = true; | ||
228 | if (background) { | | |||
229 | background.parent = itemMouse; | | |||
230 | background.anchors.fill = itemMouse; | | |||
231 | background.z = -1; | | |||
232 | } | | |||
233 | } | | |||
234 | | ||||
235 | onContentItemChanged: { | | |||
236 | contentItem.parent = paddingItem | | |||
237 | contentItem.z = 1; | | |||
238 | } | | |||
239 | | ||||
240 | Component.onCompleted: { | | |||
241 | backgroundChanged(); | | |||
242 | contentItemChanged(); | | |||
243 | } | | |||
244 | | ||||
245 | onPositionChanged: { | | |||
246 | if (!mainFlickable.loopCheck && !handleMouse.pressed && !mainFlickable.flicking && | | |||
247 | !mainFlickable.dragging && !positionAnimation.running) { | | |||
248 | mainFlickable.contentX = (listItem.width-listItem.height) * mainFlickable.internalPosition; | | |||
249 | } | 178 | } | ||
250 | } | 179 | } | ||
251 | //END signal handlers | | |||
252 | | ||||
253 | //BEGIN UI implementation | | |||
254 | Row { | 180 | Row { | ||
255 | id: actionsLayout | 181 | id: actionsLayout | ||
256 | z: 1 | 182 | z: 1 | ||
257 | anchors { | 183 | anchors { | ||
258 | right: parent.right | 184 | right: parent.right | ||
259 | verticalCenter: parent.verticalCenter | 185 | verticalCenter: parent.verticalCenter | ||
260 | rightMargin: y | 186 | rightMargin: listItem.rightPadding | ||
261 | } | 187 | } | ||
262 | height: Math.min( parent.height / 1.5, Units.iconSizes.medium) | 188 | height: Math.min( parent.height / 1.5, Units.iconSizes.medium) | ||
263 | width: childrenRect.width | 189 | width: childrenRect.width | ||
264 | property bool exclusive: false | 190 | property bool exclusive: false | ||
265 | property Item checkedButton | 191 | property Item checkedButton | ||
266 | spacing: Units.largeSpacing | 192 | spacing: Units.largeSpacing | ||
267 | Repeater { | 193 | Repeater { | ||
268 | model: { | 194 | model: { | ||
Show All 16 Lines | 210 | MouseArea { | |||
285 | anchors { | 211 | anchors { | ||
286 | fill: parent; | 212 | fill: parent; | ||
287 | margins: -Units.smallSpacing; | 213 | margins: -Units.smallSpacing; | ||
288 | } | 214 | } | ||
289 | enabled: (modelData && modelData.enabled !== undefined) ? modelData.enabled : true; | 215 | enabled: (modelData && modelData.enabled !== undefined) ? modelData.enabled : true; | ||
290 | onClicked: { | 216 | onClicked: { | ||
291 | if (modelData && modelData.trigger !== undefined) { | 217 | if (modelData && modelData.trigger !== undefined) { | ||
292 | modelData.trigger(); | 218 | modelData.trigger(); | ||
293 | // assume the model is a list of QAction or Action | | |||
294 | } else if (toolbar.model.length > index) { | | |||
295 | toolbar.model[index].trigger(); | | |||
296 | } else { | | |||
297 | console.log("Don't know how to trigger the action") | | |||
298 | } | | |||
299 | positionAnimation.to = 0; | | |||
300 | positionAnimation.running = true; | | |||
301 | } | | |||
302 | } | | |||
303 | } | 219 | } | ||
304 | } | 220 | positionAnimation.from = background.x; | ||
305 | } | | |||
306 | | ||||
307 | PropertyAnimation { | | |||
308 | id: positionAnimation | | |||
309 | target: mainFlickable | | |||
310 | properties: "contentX" | | |||
311 | duration: Units.longDuration | | |||
312 | easing.type: Easing.InOutQuad | | |||
313 | } | | |||
314 | | ||||
315 | Flickable { | | |||
316 | id: mainFlickable | | |||
317 | z: 2 | | |||
318 | interactive: false | | |||
319 | boundsBehavior: Flickable.StopAtBounds | | |||
320 | anchors.fill: parent | | |||
321 | contentWidth: mainItem.width | | |||
322 | contentHeight: height | | |||
323 | onFlickEnded: { | | |||
324 | if (contentX > width / 2) { | | |||
325 | positionAnimation.to = width - height; | | |||
326 | } else { | | |||
327 | positionAnimation.to = 0; | 221 | positionAnimation.to = 0; | ||
328 | } | | |||
329 | positionAnimation.running = true; | 222 | positionAnimation.running = true; | ||
330 | } | 223 | } | ||
331 | readonly property real internalPosition: (mainFlickable.contentX/(listItem.width-listItem.height)); | | |||
332 | property bool loopCheck: false | | |||
333 | onInternalPositionChanged: { | | |||
334 | if (!loopCheck) { | | |||
335 | loopCheck = true; | | |||
336 | listItem.position = internalPosition; | | |||
337 | loopCheck = false; | | |||
338 | } | 224 | } | ||
339 | } | 225 | } | ||
340 | | ||||
341 | Item { | | |||
342 | id: mainItem | | |||
343 | width: (mainFlickable.width * 2) - handleMouse.width | | |||
344 | height: mainFlickable.height | | |||
345 | MouseArea { | | |||
346 | id: itemMouse | | |||
347 | anchors { | | |||
348 | left: parent.left | | |||
349 | top: parent.top | | |||
350 | bottom: parent.bottom | | |||
351 | } | | |||
352 | hoverEnabled: !Settings.isMobile | | |||
353 | width: mainFlickable.width | | |||
354 | onClicked: listItem.clicked() | | |||
355 | onPressAndHold: listItem.pressAndHold() | | |||
356 | | ||||
357 | Item { | | |||
358 | id: paddingItem | | |||
359 | anchors { | | |||
360 | fill: parent | | |||
361 | margins: Units.smallSpacing | | |||
362 | rightMargin: handleIcon.width + Units.smallSpacing | | |||
363 | } | 226 | } | ||
364 | } | 227 | } | ||
365 | } | 228 | } | ||
366 | 229 | | |||
367 | MouseArea { | 230 | MouseArea { | ||
368 | id: handleMouse | 231 | id: handleMouse | ||
232 | parent: listItem.background | ||||
233 | z: 99 | ||||
369 | anchors { | 234 | anchors { | ||
370 | left: itemMouse.right | 235 | right: parent.right | ||
371 | right: itemMouse.right | | |||
372 | top: parent.top | 236 | top: parent.top | ||
373 | bottom: parent.bottom | 237 | bottom: parent.bottom | ||
374 | leftMargin: -height | 238 | rightMargin: listItem.rightPadding | ||
375 | } | 239 | } | ||
376 | preventStealing: true | 240 | preventStealing: true | ||
241 | width: height | ||||
377 | property var downTimestamp; | 242 | property var downTimestamp; | ||
378 | property int startX | 243 | property int startX | ||
379 | property int startMouseX | 244 | property int startMouseX | ||
380 | 245 | | |||
246 | property MouseArea edgeFilter | ||||
247 | | ||||
381 | onClicked: { | 248 | onClicked: { | ||
382 | if (Math.abs(startX - mainFlickable.contentX) > Units.gridUnit || | 249 | positionAnimation.from = background.x; | ||
383 | Math.abs(startMouseX - mouse.x) > Units.gridUnit) { | 250 | if (listItem.background.x > -listItem.background.width/2) { | ||
384 | return; | 251 | positionAnimation.to = -listItem.width + height; | ||
385 | } | | |||
386 | if (mainFlickable.contentX > mainFlickable.width / 2) { | | |||
387 | positionAnimation.to = 0; | | |||
388 | } else { | 252 | } else { | ||
389 | positionAnimation.to = mainFlickable.width - mainFlickable.height; | 253 | positionAnimation.to = 0; | ||
390 | } | 254 | } | ||
391 | positionAnimation.running = true; | 255 | positionAnimation.running = true; | ||
392 | } | 256 | } | ||
393 | onPressed: { | 257 | onPressed: { | ||
394 | downTimestamp = (new Date()).getTime(); | 258 | downTimestamp = (new Date()).getTime(); | ||
395 | startX = mainFlickable.contentX; | 259 | startX = listItem.background.x; | ||
396 | startMouseX = mouse.x; | 260 | startMouseX = mouse.x; | ||
397 | } | 261 | } | ||
398 | onPositionChanged: { | 262 | onPositionChanged: { | ||
399 | mainFlickable.contentX = Math.max(0, Math.min(mainFlickable.width - height, mainFlickable.contentX + (startMouseX - mouse.x))) | 263 | listItem.background.x = Math.min(0, Math.max(-listItem.width + height, listItem.background.x - (startMouseX - mouse.x))); | ||
400 | } | 264 | } | ||
401 | onReleased: { | 265 | onReleased: { | ||
402 | var speed = ((startX - mainFlickable.contentX) / ((new Date()).getTime() - downTimestamp) * 1000); | 266 | var speed = ((startX - listItem.background.x) / ((new Date()).getTime() - downTimestamp) * 1000); | ||
403 | mainFlickable.flick(speed, 0); | 267 | | ||
268 | if (Math.abs(speed) < Units.gridUnit) { | ||||
269 | return; | ||||
270 | } | ||||
271 | if (speed > listItem.width/2) { | ||||
272 | positionAnimation.to = -listItem.width + height; | ||||
273 | } else { | ||||
274 | positionAnimation.to = 0; | ||||
275 | } | ||||
276 | positionAnimation.from = background.x; | ||||
277 | positionAnimation.running = true; | ||||
404 | } | 278 | } | ||
405 | Icon { | 279 | Icon { | ||
406 | id: handleIcon | 280 | id: handleIcon | ||
407 | anchors.centerIn: parent | 281 | anchors.verticalCenter: parent.verticalCenter | ||
408 | selected: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) | 282 | selected: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) | ||
409 | width: Units.iconSizes.smallMedium | 283 | width: Units.iconSizes.smallMedium | ||
410 | height: width | 284 | height: width | ||
411 | source: (mainFlickable.contentX > mainFlickable.width / 2) ? "handle-right" : "handle-left" | 285 | x: y | ||
286 | source: listItem.background.x < -listItem.background.width/2 ? "handle-right" : "handle-left" | ||||
287 | } | ||||
288 | } | ||||
289 | | ||||
290 | XAnimator { | ||||
291 | id: positionAnimation | ||||
292 | target: background | ||||
293 | duration: Units.longDuration | ||||
294 | easing.type: Easing.InOutQuad | ||||
295 | } | ||||
296 | | ||||
297 | //BEGIN signal handlers | ||||
298 | onContentItemChanged: { | ||||
299 | contentItem.parent = background; | ||||
300 | contentItem.z = 0; | ||||
412 | } | 301 | } | ||
302 | Component.onCompleted: { | ||||
303 | //this will happen only once | ||||
304 | if (Settings.isMobile && !listItem.ListView.view.parent.parent._swipeFilter) { | ||||
305 | var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml")); | ||||
306 | listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent); | ||||
413 | } | 307 | } | ||
414 | } | 308 | } | ||
309 | Connections { | ||||
310 | target: enabled ? listItem.ListView.view.parent.parent._swipeFilter : null | ||||
311 | property bool enabled: listItem.ListView.view.parent.parent._swipeFilter ? listItem.ListView.view.parent.parent._swipeFilter.currentItem === listItem : false | ||||
312 | onPeekChanged: listItem.background.x = -(listItem.background.width - listItem.background.height) * listItem.ListView.view.parent.parent._swipeFilter.peek | ||||
313 | onCurrentItemChanged: { | ||||
314 | if (!enabled) { | ||||
315 | positionAnimation.to = 0; | ||||
316 | positionAnimation.from = background.x; | ||||
317 | positionAnimation.running = true; | ||||
415 | } | 318 | } | ||
416 | //END UI implementation | 319 | } | ||
320 | } | ||||
321 | //END signal handlers | ||||
417 | 322 | | |||
418 | Accessible.role: Accessible.ListItem | 323 | Accessible.role: Accessible.ListItem | ||
419 | } | 324 | } |