1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qquickswipedelegate_p.h"
38 #include "qquickswipedelegate_p_p.h"
39 #include "qquickcontrol_p_p.h"
40 #include "qquickitemdelegate_p_p.h"
41 #include "qquickvelocitycalculator_p_p.h"
42 
43 #include <QtGui/qstylehints.h>
44 #include <QtGui/private/qguiapplication_p.h>
45 #include <QtGui/qpa/qplatformtheme.h>
46 #include <QtQml/qqmlinfo.h>
47 #include <QtQuick/private/qquickanimation_p.h>
48 #include <QtQuick/private/qquicktransition_p.h>
49 #include <QtQuick/private/qquicktransitionmanager_p_p.h>
50 
51 QT_BEGIN_NAMESPACE
52 
53 /*!
54     \qmltype SwipeDelegate
55     \inherits ItemDelegate
56 //!     \instantiates QQuickSwipeDelegate
57     \inqmlmodule QtQuick.Controls
58     \since 5.7
59     \ingroup qtquickcontrols2-delegates
60     \brief Swipable item delegate.
61 
62     SwipeDelegate presents a view item that can be swiped left or right to
63     expose more options or information. It is used as a delegate in views such
64     as \l ListView.
65 
66     In the following example, SwipeDelegate is used in a \l ListView to allow
67     items to be removed from it by swiping to the left:
68 
69     \snippet qtquickcontrols2-swipedelegate.qml 1
70 
71     SwipeDelegate inherits its API from \l ItemDelegate, which is inherited
72     from AbstractButton. For instance, you can set \l {AbstractButton::text}{text},
73     and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton
74     API.
75 
76     Information regarding the progress of a swipe, as well as the components
77     that should be shown upon swiping, are both available through the
78     \l {SwipeDelegate::}{swipe} grouped property object. For example,
79     \c swipe.position holds the position of the
80     swipe within the range \c -1.0 to \c 1.0. The \c swipe.left
81     property determines which item will be displayed when the control is swiped
82     to the right, and vice versa for \c swipe.right. The positioning of these
83     components is left to applications to decide. For example, without specifying
84     any position for \c swipe.left or \c swipe.right, the following will
85     occur:
86 
87     \image qtquickcontrols2-swipedelegate.gif
88 
89     If \c swipe.left and \c swipe.right are anchored to the left and
90     right of the \l {Control::}{background} item (respectively), they'll behave like this:
91 
92     \image qtquickcontrols2-swipedelegate-leading-trailing.gif
93 
94     When using \c swipe.left and \c swipe.right, the control cannot be
95     swiped past the left and right edges. To achieve this type of "wrapping"
96     behavior, set \c swipe.behind instead. This will result in the same
97     item being shown regardless of which direction the control is swiped. For
98     example, in the image below, we set \c swipe.behind and then swipe the
99     control repeatedly in both directions:
100 
101     \image qtquickcontrols2-swipedelegate-behind.gif
102 
103     \sa {Customizing SwipeDelegate}, {Delegate Controls}, {Qt Quick Controls 2 - Swipe to Remove}{Swipe to Remove Example}
104 */
105 
106 namespace {
107     typedef QQuickSwipeDelegateAttached Attached;
108 
attachedObject(QQuickItem * item)109     Attached *attachedObject(QQuickItem *item) {
110         return qobject_cast<Attached*>(qmlAttachedPropertiesObject<QQuickSwipeDelegate>(item, false));
111     }
112 
113     enum PositionAnimation {
114         DontAnimatePosition,
115         AnimatePosition
116     };
117 }
118 
119 class QQuickSwipeTransitionManager : public QQuickTransitionManager
120 {
121 public:
122     QQuickSwipeTransitionManager(QQuickSwipe *swipe);
123 
124     void transition(QQuickTransition *transition, qreal position);
125 
126 protected:
127     void finished() override;
128 
129 private:
130     QQuickSwipe *m_swipe = nullptr;
131 };
132 
133 class QQuickSwipePrivate : public QObjectPrivate
134 {
135     Q_DECLARE_PUBLIC(QQuickSwipe)
136 
137 public:
QQuickSwipePrivate(QQuickSwipeDelegate * control)138     QQuickSwipePrivate(QQuickSwipeDelegate *control) : control(control) { }
139 
140     static QQuickSwipePrivate *get(QQuickSwipe *swipe);
141 
142     QQuickItem *createDelegateItem(QQmlComponent *component);
143     QQuickItem *showRelevantItemForPosition(qreal position);
144     QQuickItem *createRelevantItemForDistance(qreal distance);
145     void reposition(PositionAnimation animationPolicy);
146     void createLeftItem();
147     void createBehindItem();
148     void createRightItem();
149     void createAndShowLeftItem();
150     void createAndShowBehindItem();
151     void createAndShowRightItem();
152 
153     void warnAboutMixingDelegates();
154     void warnAboutSettingDelegatesWhileVisible();
155 
156     bool hasDelegates() const;
157 
158     bool isTransitioning() const;
159     void beginTransition(qreal position);
160     void finishTransition();
161 
162     QQuickSwipeDelegate *control = nullptr;
163     // Same range as position, but is set before press events so that we can
164     // keep track of which direction the user must swipe when using left and right delegates.
165     qreal positionBeforePress = 0;
166     qreal position = 0;
167     // A "less strict" version of complete that is true if complete was true
168     // before the last press event.
169     bool wasComplete = false;
170     bool complete = false;
171     bool enabled = true;
172     QQuickVelocityCalculator velocityCalculator;
173     QQmlComponent *left = nullptr;
174     QQmlComponent *behind = nullptr;
175     QQmlComponent *right = nullptr;
176     QQuickItem *leftItem = nullptr;
177     QQuickItem *behindItem = nullptr;
178     QQuickItem *rightItem = nullptr;
179     QQuickTransition *transition = nullptr;
180     QScopedPointer<QQuickSwipeTransitionManager> transitionManager;
181 };
182 
QQuickSwipeTransitionManager(QQuickSwipe * swipe)183 QQuickSwipeTransitionManager::QQuickSwipeTransitionManager(QQuickSwipe *swipe)
184     : m_swipe(swipe)
185 {
186 }
187 
transition(QQuickTransition * transition,qreal position)188 void QQuickSwipeTransitionManager::transition(QQuickTransition *transition, qreal position)
189 {
190     qmlExecuteDeferred(transition);
191 
192     QQmlProperty defaultTarget(m_swipe, QLatin1String("position"));
193     QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations();
194     const int count = animations.count(&animations);
195     for (int i = 0; i < count; ++i) {
196         QQuickAbstractAnimation *anim = animations.at(&animations, i);
197         anim->setDefaultTarget(defaultTarget);
198     }
199 
200     QList<QQuickStateAction> actions;
201     actions << QQuickStateAction(m_swipe, QLatin1String("position"), position);
202     QQuickTransitionManager::transition(actions, transition, m_swipe);
203 }
204 
finished()205 void QQuickSwipeTransitionManager::finished()
206 {
207     QQuickSwipePrivate::get(m_swipe)->finishTransition();
208 }
209 
get(QQuickSwipe * swipe)210 QQuickSwipePrivate *QQuickSwipePrivate::get(QQuickSwipe *swipe)
211 {
212     return swipe->d_func();
213 }
214 
createDelegateItem(QQmlComponent * component)215 QQuickItem *QQuickSwipePrivate::createDelegateItem(QQmlComponent *component)
216 {
217     // If we don't use the correct context, it won't be possible to refer to
218     // the control's id from within the delegates.
219     QQmlContext *creationContext = component->creationContext();
220     // The component might not have been created in QML, in which case
221     // the creation context will be null and we have to create it ourselves.
222     if (!creationContext)
223         creationContext = qmlContext(control);
224     QQmlContext *context = new QQmlContext(creationContext, control);
225     context->setContextObject(control);
226     QQuickItem *item = qobject_cast<QQuickItem*>(component->beginCreate(context));
227     if (item) {
228         item->setParentItem(control);
229         component->completeCreate();
230     }
231     return item;
232 }
233 
showRelevantItemForPosition(qreal position)234 QQuickItem *QQuickSwipePrivate::showRelevantItemForPosition(qreal position)
235 {
236     if (qFuzzyIsNull(position))
237         return nullptr;
238 
239     if (behind) {
240         createAndShowBehindItem();
241         return behindItem;
242     }
243 
244     if (right && position < 0.0) {
245         createAndShowRightItem();
246         return rightItem;
247     }
248 
249     if (left && position > 0.0) {
250         createAndShowLeftItem();
251         return leftItem;
252     }
253 
254     return nullptr;
255 }
256 
createRelevantItemForDistance(qreal distance)257 QQuickItem *QQuickSwipePrivate::createRelevantItemForDistance(qreal distance)
258 {
259     if (qFuzzyIsNull(distance))
260         return nullptr;
261 
262     if (behind) {
263         createBehindItem();
264         return behindItem;
265     }
266 
267     // a) If the position before the press was 0.0, we know that *any* movement
268     // whose distance is negative will result in the right item being shown and
269     // vice versa.
270     // b) Once the control has been exposed (that is, swiped to the left or right,
271     // and hence the position is either -1.0 or 1.0), we must use the width of the
272     // relevant item to determine if the distance is larger than that item,
273     // in order to know whether or not to display it.
274     // c) If the control has been exposed, and the swipe is larger than the width
275     // of the relevant item from which the swipe started from, we must show the
276     // item on the other side (if any).
277 
278     if (right) {
279         if ((distance < 0.0 && positionBeforePress == 0.0) /* a) */
280             || (rightItem && positionBeforePress == -1.0 && distance < rightItem->width()) /* b) */
281             || (leftItem && positionBeforePress == 1.0 && qAbs(distance) > leftItem->width())) /* c) */ {
282             createRightItem();
283             return rightItem;
284         }
285     }
286 
287     if (left) {
288         if ((distance > 0.0 && positionBeforePress == 0.0) /* a) */
289             || (leftItem && positionBeforePress == 1.0 && qAbs(distance) < leftItem->width()) /* b) */
290             || (rightItem && positionBeforePress == -1.0 && qAbs(distance) > rightItem->width())) /* c) */ {
291             createLeftItem();
292             return leftItem;
293         }
294     }
295 
296     return nullptr;
297 }
298 
reposition(PositionAnimation animationPolicy)299 void QQuickSwipePrivate::reposition(PositionAnimation animationPolicy)
300 {
301     QQuickItem *relevantItem = showRelevantItemForPosition(position);
302     const qreal relevantWidth = relevantItem ? relevantItem->width() : 0.0;
303     const qreal contentItemX = position * relevantWidth + control->leftPadding();
304 
305     // "Behavior on x" relies on the property system to know when it should update,
306     // so we can prevent it from animating by setting the x position directly.
307     if (animationPolicy == AnimatePosition) {
308         if (QQuickItem *contentItem = control->contentItem())
309             contentItem->setProperty("x", contentItemX);
310         if (QQuickItem *background = control->background())
311             background->setProperty("x", position * relevantWidth);
312     } else {
313         if (QQuickItem *contentItem = control->contentItem())
314             contentItem->setX(contentItemX);
315         if (QQuickItem *background = control->background())
316             background->setX(position * relevantWidth);
317     }
318 }
319 
createLeftItem()320 void QQuickSwipePrivate::createLeftItem()
321 {
322     if (!leftItem) {
323         Q_Q(QQuickSwipe);
324         q->setLeftItem(createDelegateItem(left));
325         if (!leftItem)
326             qmlWarning(control) << "Failed to create left item:" << left->errors();
327     }
328 }
329 
createBehindItem()330 void QQuickSwipePrivate::createBehindItem()
331 {
332     if (!behindItem) {
333         Q_Q(QQuickSwipe);
334         q->setBehindItem(createDelegateItem(behind));
335         if (!behindItem)
336             qmlWarning(control) << "Failed to create behind item:" << behind->errors();
337     }
338 }
339 
createRightItem()340 void QQuickSwipePrivate::createRightItem()
341 {
342     if (!rightItem) {
343         Q_Q(QQuickSwipe);
344         q->setRightItem(createDelegateItem(right));
345         if (!rightItem)
346             qmlWarning(control) << "Failed to create right item:" << right->errors();
347     }
348 }
349 
createAndShowLeftItem()350 void QQuickSwipePrivate::createAndShowLeftItem()
351 {
352     createLeftItem();
353 
354     if (leftItem)
355         leftItem->setVisible(true);
356 
357     if (rightItem)
358         rightItem->setVisible(false);
359 }
360 
createAndShowBehindItem()361 void QQuickSwipePrivate::createAndShowBehindItem()
362 {
363     createBehindItem();
364 
365     if (behindItem)
366         behindItem->setVisible(true);
367 }
368 
createAndShowRightItem()369 void QQuickSwipePrivate::createAndShowRightItem()
370 {
371     createRightItem();
372 
373     // This item may have already existed but was hidden.
374     if (rightItem)
375         rightItem->setVisible(true);
376 
377     // The left item isn't visible when the right item is visible, so save rendering effort by hiding it.
378     if (leftItem)
379         leftItem->setVisible(false);
380 }
381 
warnAboutMixingDelegates()382 void QQuickSwipePrivate::warnAboutMixingDelegates()
383 {
384     qmlWarning(control) << "cannot set both behind and left/right properties";
385 }
386 
warnAboutSettingDelegatesWhileVisible()387 void QQuickSwipePrivate::warnAboutSettingDelegatesWhileVisible()
388 {
389     qmlWarning(control) << "left/right/behind properties may only be set when swipe.position is 0";
390 }
391 
hasDelegates() const392 bool QQuickSwipePrivate::hasDelegates() const
393 {
394     return left || right || behind;
395 }
396 
isTransitioning() const397 bool QQuickSwipePrivate::isTransitioning() const
398 {
399     return transitionManager && transitionManager->isRunning();
400 }
401 
beginTransition(qreal newPosition)402 void QQuickSwipePrivate::beginTransition(qreal newPosition)
403 {
404     Q_Q(QQuickSwipe);
405     if (!transition) {
406         q->setPosition(newPosition);
407         finishTransition();
408         return;
409     }
410 
411     if (!transitionManager)
412         transitionManager.reset(new QQuickSwipeTransitionManager(q));
413 
414     transitionManager->transition(transition, newPosition);
415 }
416 
finishTransition()417 void QQuickSwipePrivate::finishTransition()
418 {
419     Q_Q(QQuickSwipe);
420     q->setComplete(qFuzzyCompare(qAbs(position), qreal(1.0)));
421     if (complete)
422         emit q->opened();
423     else
424         emit q->closed();
425 }
426 
QQuickSwipe(QQuickSwipeDelegate * control)427 QQuickSwipe::QQuickSwipe(QQuickSwipeDelegate *control)
428     : QObject(*(new QQuickSwipePrivate(control)))
429 {
430 }
431 
left() const432 QQmlComponent *QQuickSwipe::left() const
433 {
434     Q_D(const QQuickSwipe);
435     return d->left;
436 }
437 
setLeft(QQmlComponent * left)438 void QQuickSwipe::setLeft(QQmlComponent *left)
439 {
440     Q_D(QQuickSwipe);
441     if (left == d->left)
442         return;
443 
444     if (d->behind) {
445         d->warnAboutMixingDelegates();
446         return;
447     }
448 
449     if (!qFuzzyIsNull(d->position)) {
450         d->warnAboutSettingDelegatesWhileVisible();
451         return;
452     }
453 
454     d->left = left;
455 
456     if (!d->left) {
457         delete d->leftItem;
458         d->leftItem = nullptr;
459     }
460 
461     d->control->setFiltersChildMouseEvents(d->hasDelegates());
462 
463     emit leftChanged();
464 }
465 
behind() const466 QQmlComponent *QQuickSwipe::behind() const
467 {
468     Q_D(const QQuickSwipe);
469     return d->behind;
470 }
471 
setBehind(QQmlComponent * behind)472 void QQuickSwipe::setBehind(QQmlComponent *behind)
473 {
474     Q_D(QQuickSwipe);
475     if (behind == d->behind)
476         return;
477 
478     if (d->left || d->right) {
479         d->warnAboutMixingDelegates();
480         return;
481     }
482 
483     if (!qFuzzyIsNull(d->position)) {
484         d->warnAboutSettingDelegatesWhileVisible();
485         return;
486     }
487 
488     d->behind = behind;
489 
490     if (!d->behind) {
491         delete d->behindItem;
492         d->behindItem = nullptr;
493     }
494 
495     d->control->setFiltersChildMouseEvents(d->hasDelegates());
496 
497     emit behindChanged();
498 }
499 
right() const500 QQmlComponent *QQuickSwipe::right() const
501 {
502     Q_D(const QQuickSwipe);
503     return d->right;
504 }
505 
setRight(QQmlComponent * right)506 void QQuickSwipe::setRight(QQmlComponent *right)
507 {
508     Q_D(QQuickSwipe);
509     if (right == d->right)
510         return;
511 
512     if (d->behind) {
513         d->warnAboutMixingDelegates();
514         return;
515     }
516 
517     if (!qFuzzyIsNull(d->position)) {
518         d->warnAboutSettingDelegatesWhileVisible();
519         return;
520     }
521 
522     d->right = right;
523 
524     if (!d->right) {
525         delete d->rightItem;
526         d->rightItem = nullptr;
527     }
528 
529     d->control->setFiltersChildMouseEvents(d->hasDelegates());
530 
531     emit rightChanged();
532 }
533 
leftItem() const534 QQuickItem *QQuickSwipe::leftItem() const
535 {
536     Q_D(const QQuickSwipe);
537     return d->leftItem;
538 }
539 
setLeftItem(QQuickItem * item)540 void QQuickSwipe::setLeftItem(QQuickItem *item)
541 {
542     Q_D(QQuickSwipe);
543     if (item == d->leftItem)
544         return;
545 
546     delete d->leftItem;
547     d->leftItem = item;
548 
549     if (d->leftItem) {
550         d->leftItem->setParentItem(d->control);
551 
552         if (qFuzzyIsNull(d->leftItem->z()))
553             d->leftItem->setZ(-2);
554     }
555 
556     emit leftItemChanged();
557 }
558 
behindItem() const559 QQuickItem *QQuickSwipe::behindItem() const
560 {
561     Q_D(const QQuickSwipe);
562     return d->behindItem;
563 }
564 
setBehindItem(QQuickItem * item)565 void QQuickSwipe::setBehindItem(QQuickItem *item)
566 {
567     Q_D(QQuickSwipe);
568     if (item == d->behindItem)
569         return;
570 
571     delete d->behindItem;
572     d->behindItem = item;
573 
574     if (d->behindItem) {
575         d->behindItem->setParentItem(d->control);
576 
577         if (qFuzzyIsNull(d->behindItem->z()))
578             d->behindItem->setZ(-2);
579     }
580 
581     emit behindItemChanged();
582 }
583 
rightItem() const584 QQuickItem *QQuickSwipe::rightItem() const
585 {
586     Q_D(const QQuickSwipe);
587     return d->rightItem;
588 }
589 
setRightItem(QQuickItem * item)590 void QQuickSwipe::setRightItem(QQuickItem *item)
591 {
592     Q_D(QQuickSwipe);
593     if (item == d->rightItem)
594         return;
595 
596     delete d->rightItem;
597     d->rightItem = item;
598 
599     if (d->rightItem) {
600         d->rightItem->setParentItem(d->control);
601 
602         if (qFuzzyIsNull(d->rightItem->z()))
603             d->rightItem->setZ(-2);
604     }
605 
606     emit rightItemChanged();
607 }
608 
position() const609 qreal QQuickSwipe::position() const
610 {
611     Q_D(const QQuickSwipe);
612     return d->position;
613 }
614 
setPosition(qreal position)615 void QQuickSwipe::setPosition(qreal position)
616 {
617     Q_D(QQuickSwipe);
618     const qreal adjustedPosition = qBound<qreal>(-1.0, position, 1.0);
619     if (adjustedPosition == d->position)
620         return;
621 
622     d->position = adjustedPosition;
623     d->reposition(AnimatePosition);
624     emit positionChanged();
625 }
626 
isComplete() const627 bool QQuickSwipe::isComplete() const
628 {
629     Q_D(const QQuickSwipe);
630     return d->complete;
631 }
632 
setComplete(bool complete)633 void QQuickSwipe::setComplete(bool complete)
634 {
635     Q_D(QQuickSwipe);
636     if (complete == d->complete)
637         return;
638 
639     d->complete = complete;
640     emit completeChanged();
641     if (d->complete)
642         emit completed();
643 }
644 
isEnabled() const645 bool QQuickSwipe::isEnabled() const
646 {
647     Q_D(const QQuickSwipe);
648     return d->enabled;
649 }
650 
setEnabled(bool enabled)651 void QQuickSwipe::setEnabled(bool enabled)
652 {
653     Q_D(QQuickSwipe);
654     if (enabled == d->enabled)
655         return;
656 
657     d->enabled = enabled;
658     emit enabledChanged();
659 }
660 
transition() const661 QQuickTransition *QQuickSwipe::transition() const
662 {
663     Q_D(const QQuickSwipe);
664     return d->transition;
665 }
666 
setTransition(QQuickTransition * transition)667 void QQuickSwipe::setTransition(QQuickTransition *transition)
668 {
669     Q_D(QQuickSwipe);
670     if (transition == d->transition)
671         return;
672 
673     d->transition = transition;
674     emit transitionChanged();
675 }
676 
open(QQuickSwipeDelegate::Side side)677 void QQuickSwipe::open(QQuickSwipeDelegate::Side side)
678 {
679     Q_D(QQuickSwipe);
680     if (qFuzzyCompare(qAbs(d->position), qreal(1.0)))
681         return;
682 
683     if ((side != QQuickSwipeDelegate::Left && side != QQuickSwipeDelegate::Right)
684             || (!d->left && !d->behind && side == QQuickSwipeDelegate::Left)
685             || (!d->right && !d->behind && side == QQuickSwipeDelegate::Right))
686         return;
687 
688     d->beginTransition(side);
689     d->wasComplete = true;
690     d->velocityCalculator.reset();
691     d->positionBeforePress = d->position;
692 }
693 
close()694 void QQuickSwipe::close()
695 {
696     Q_D(QQuickSwipe);
697     if (qFuzzyIsNull(d->position))
698         return;
699 
700     if (d->control->isPressed()) {
701         // We don't support closing when we're pressed; release() or clicked() should be used instead.
702         return;
703     }
704 
705     d->beginTransition(0.0);
706     d->wasComplete = false;
707     d->positionBeforePress = 0.0;
708     d->velocityCalculator.reset();
709 }
710 
QQuickSwipeDelegatePrivate(QQuickSwipeDelegate * control)711 QQuickSwipeDelegatePrivate::QQuickSwipeDelegatePrivate(QQuickSwipeDelegate *control)
712     : swipe(control)
713 {
714 }
715 
handleMousePressEvent(QQuickItem * item,QMouseEvent * event)716 bool QQuickSwipeDelegatePrivate::handleMousePressEvent(QQuickItem *item, QMouseEvent *event)
717 {
718     Q_Q(QQuickSwipeDelegate);
719     QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
720     // If the position is 0, we want to handle events ourselves - we don't want child items to steal them.
721     // This code will only get called when a child item has been created;
722     // events will go through the regular channels (mousePressEvent()) until then.
723     if (qFuzzyIsNull(swipePrivate->position)) {
724         q->mousePressEvent(event);
725         // The press point could be incorrect if the press happened over a child item,
726         // so we correct it after calling the base class' mousePressEvent(), rather
727         // than having to duplicate its code just so we can set the pressPoint.
728         setPressPoint(item->mapToItem(q, event->pos()));
729         return true;
730     }
731 
732     // The position is non-zero, this press could be either for a delegate or the control itself
733     // (the control can be clicked to e.g. close the swipe). Either way, we must begin measuring
734     // mouse movement in case it turns into a swipe, in which case we grab the mouse.
735     swipePrivate->positionBeforePress = swipePrivate->position;
736     swipePrivate->velocityCalculator.startMeasuring(event->pos(), event->timestamp());
737     setPressPoint(item->mapToItem(q, event->pos()));
738 
739     // When a delegate uses the attached properties and signals, it declares that it wants mouse events.
740     Attached *attached = attachedObject(item);
741     if (attached) {
742         attached->setPressed(true);
743         // Stop the event from propagating, as QQuickItem explicitly ignores events.
744         event->accept();
745         return true;
746     }
747 
748     return false;
749 }
750 
handleMouseMoveEvent(QQuickItem * item,QMouseEvent * event)751 bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEvent *event)
752 {
753     Q_Q(QQuickSwipeDelegate);
754 
755     if (holdTimer > 0) {
756         if (QLineF(pressPoint, event->localPos()).length() > QGuiApplication::styleHints()->startDragDistance())
757             stopPressAndHold();
758     }
759 
760     // The delegate can still be pressed when swipe.enabled is false,
761     // but the mouse moving shouldn't have any effect on swipe.position.
762     QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
763     if (!swipePrivate->enabled)
764         return false;
765 
766     // Protect against division by zero.
767     if (width == 0)
768         return false;
769 
770     // Don't bother reacting to events if we don't have any delegates.
771     if (!swipePrivate->left && !swipePrivate->right && !swipePrivate->behind)
772         return false;
773 
774     // Don't handle move events for the control if it wasn't pressed.
775     if (item == q && !pressed)
776         return false;
777 
778     const QPointF mappedEventPos = item->mapToItem(q, event->pos());
779     const qreal distance = (mappedEventPos - pressPoint).x();
780     if (!q->keepMouseGrab()) {
781         // Taken from QQuickDrawerPrivate::grabMouse; see comments there.
782         int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5);
783         const bool overThreshold = QQuickWindowPrivate::dragOverThreshold(distance, Qt::XAxis, event, threshold);
784         if (window && overThreshold) {
785             QQuickItem *grabber = q->window()->mouseGrabberItem();
786             if (!grabber || !grabber->keepMouseGrab()) {
787                 q->grabMouse();
788                 q->setKeepMouseGrab(true);
789                 q->setPressed(true);
790                 swipe.setComplete(false);
791 
792                 if (Attached *attached = attachedObject(item))
793                     attached->setPressed(false);
794             }
795         }
796     }
797 
798     if (q->keepMouseGrab()) {
799         // Ensure we don't try to calculate a position when the user tried to drag
800         // to the left when the left item is already exposed, and vice versa.
801         // The code below assumes that the drag is valid, so if we don't have this check,
802         // the wrong items are visible and the swiping wraps.
803         if (swipePrivate->behind
804             || ((swipePrivate->left || swipePrivate->right)
805                 && (qFuzzyIsNull(swipePrivate->positionBeforePress)
806                     || (swipePrivate->positionBeforePress == -1.0 && distance >= 0.0)
807                     || (swipePrivate->positionBeforePress == 1.0 && distance <= 0.0)))) {
808 
809             // We must instantiate the items here so that we can calculate the
810             // position against the width of the relevant item.
811             QQuickItem *relevantItem = swipePrivate->createRelevantItemForDistance(distance);
812             // If there isn't any relevant item, the user may have swiped back to the 0 position,
813             // or they swiped back to a position that is equal to positionBeforePress.
814             const qreal normalizedDistance = relevantItem ? distance / relevantItem->width() : 0.0;
815             qreal position = 0;
816 
817             // If the control was exposed before the drag begun, the distance should be inverted.
818             // For example, if the control had been swiped to the right, the position would be 1.0.
819             // If the control was then swiped to the left by a distance of -20 pixels, the normalized
820             // distance might be -0.2, for example, which cannot be used as the position; the swipe
821             // started from the right, so we account for that by adding the position.
822             if (qFuzzyIsNull(normalizedDistance)) {
823                 // There are two cases when the normalizedDistance can be 0,
824                 // and we must distinguish between them:
825                 //
826                 // a) The swipe returns to the position that it was at before the press event.
827                 // In this case, the distance will be 0.
828                 // There would have been many position changes in the meantime, so we can't just
829                 // ignore the move event; we have to set position to what it was before the press.
830                 //
831                 // b) If the position was at, 1.0, for example, and the control was then swiped
832                 // to the left by the exact width of the left item, there won't be any relevant item
833                 // (because the swipe's position would be at 0.0). In turn, the normalizedDistance
834                 // would be 0 (because of the lack of a relevant item), but the distance will be non-zero.
835                 position = qFuzzyIsNull(distance) ? swipePrivate->positionBeforePress : 0;
836             } else if (!swipePrivate->wasComplete) {
837                 position = normalizedDistance;
838             } else {
839                 position = distance > 0 ? normalizedDistance - 1.0 : normalizedDistance + 1.0;
840             }
841 
842             if (swipePrivate->isTransitioning())
843                 swipePrivate->transitionManager->cancel();
844             swipe.setPosition(position);
845         }
846     } else {
847         // The swipe wasn't initiated.
848         if (event->pos().y() < 0 || event->pos().y() > height) {
849             // The mouse went outside the vertical bounds of the control, so
850             // we should no longer consider it pressed.
851             q->setPressed(false);
852         }
853     }
854 
855     event->accept();
856 
857     return q->keepMouseGrab();
858 }
859 
860 static const qreal exposeVelocityThreshold = 300.0;
861 
handleMouseReleaseEvent(QQuickItem * item,QMouseEvent * event)862 bool QQuickSwipeDelegatePrivate::handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event)
863 {
864     Q_Q(QQuickSwipeDelegate);
865     QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
866     swipePrivate->velocityCalculator.stopMeasuring(event->pos(), event->timestamp());
867 
868     const bool hadGrabbedMouse = q->keepMouseGrab();
869     q->setKeepMouseGrab(false);
870 
871     // Animations for the background and contentItem delegates are typically
872     // only enabled when !control.down, so that the animations aren't running
873     // when the user is swiping. To ensure that the animations are enabled
874     // *before* the positions of these delegates change (via the swipe.setPosition() calls below),
875     // we must cancel the press. QQuickAbstractButton::mouseUngrabEvent() does this
876     // for us, but by then it's too late.
877     if (hadGrabbedMouse) {
878         // TODO: this is copied from QQuickAbstractButton::mouseUngrabEvent().
879         // Eventually it should be moved into a private helper so that we don't have to duplicate it.
880         q->setPressed(false);
881         stopPressRepeat();
882         stopPressAndHold();
883         emit q->canceled();
884     }
885 
886     // The control can be exposed by either swiping past the halfway mark, or swiping fast enough.
887     const qreal swipeVelocity = swipePrivate->velocityCalculator.velocity().x();
888     if (swipePrivate->position > 0.5 ||
889         (swipePrivate->position > 0.0 && swipeVelocity > exposeVelocityThreshold)) {
890         swipePrivate->beginTransition(1.0);
891         swipePrivate->wasComplete = true;
892     } else if (swipePrivate->position < -0.5 ||
893         (swipePrivate->position < 0.0 && swipeVelocity < -exposeVelocityThreshold)) {
894         swipePrivate->beginTransition(-1.0);
895         swipePrivate->wasComplete = true;
896     } else if (!swipePrivate->isTransitioning()) {
897         // The position is either <= 0.5 or >= -0.5, so the position should go to 0.
898         // However, if the position was already 0 or close to it, we were just clicked,
899         // and we don't need to start a transition.
900         if (!qFuzzyIsNull(swipePrivate->position))
901             swipePrivate->beginTransition(0.0);
902         swipePrivate->wasComplete = false;
903     }
904 
905     if (Attached *attached = attachedObject(item)) {
906         const bool wasPressed = attached->isPressed();
907         if (wasPressed) {
908             attached->setPressed(false);
909             emit attached->clicked();
910         }
911     }
912 
913     // Only consume child events if we had grabbed the mouse.
914     return hadGrabbedMouse;
915 }
916 
warnIfHorizontallyAnchored(QQuickItem * item,const QString & itemName)917 static void warnIfHorizontallyAnchored(QQuickItem *item, const QString &itemName)
918 {
919     if (!item)
920         return;
921 
922     QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors;
923     if (anchors && (anchors->fill() || anchors->centerIn() || anchors->left().item || anchors->right().item)
924             && !item->property("_q_QQuickSwipeDelegate_warned").toBool()) {
925         qmlWarning(item) << QString::fromLatin1("SwipeDelegate: cannot use horizontal anchors with %1; unable to layout the item.").arg(itemName);
926         item->setProperty("_q_QQuickSwipeDelegate_warned", true);
927     }
928 }
929 
resizeContent()930 void QQuickSwipeDelegatePrivate::resizeContent()
931 {
932     warnIfHorizontallyAnchored(background, QStringLiteral("background"));
933     warnIfHorizontallyAnchored(contentItem, QStringLiteral("contentItem"));
934 
935     // If the background and contentItem are repositioned due to a swipe,
936     // we don't want to call QQuickControlPrivate's implementation of this function,
937     // as it repositions the contentItem to be visible.
938     // However, we still want to resize the control vertically.
939     QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
940     if (!swipePrivate->complete) {
941         QQuickItemDelegatePrivate::resizeContent();
942     } else if (contentItem) {
943         Q_Q(QQuickSwipeDelegate);
944         contentItem->setY(q->topPadding());
945         contentItem->setHeight(q->availableHeight());
946     }
947 }
948 
QQuickSwipeDelegate(QQuickItem * parent)949 QQuickSwipeDelegate::QQuickSwipeDelegate(QQuickItem *parent)
950     : QQuickItemDelegate(*(new QQuickSwipeDelegatePrivate(this)), parent)
951 {
952 }
953 
954 /*!
955     \since QtQuick.Controls 2.2 (Qt 5.9)
956     \qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.open(enumeration side)
957 
958     This method sets the \c position of the swipe so that it opens
959     from the specified \a side.
960 
961     Available values:
962     \value SwipeDelegate.Left  The \c position is set to \c 1, which makes the swipe open
963                                from the left. Either \c swipe.left or \c swipe.behind must
964                                have been specified; otherwise the call is ignored.
965     \value SwipeDelegate.Right The \c position is set to \c -1, which makes the swipe open
966                                from the right. Either \c swipe.right or \c swipe.behind must
967                                have been specified; otherwise the call is ignored.
968 
969     Any animations defined for the \l {Item::}{x} position of \l {Control::}{contentItem}
970     and \l {Control::}{background} will be triggered.
971 
972     \sa swipe, swipe.close()
973 */
974 
975 /*!
976     \since QtQuick.Controls 2.1 (Qt 5.8)
977     \qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.close()
978 
979     This method sets the \c position of the swipe to \c 0. Any animations
980     defined for the \l {Item::}{x} position of \l {Control::}{contentItem}
981     and \l {Control::}{background} will be triggered.
982 
983     \sa swipe, swipe.open()
984 */
985 
986 /*!
987     \since QtQuick.Controls 2.2 (Qt 5.9)
988     \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.opened()
989 
990     This signal is emitted when the delegate has been swiped open
991     and the transition has finished.
992 
993     It is useful for performing some action upon completion of a swipe.
994     For example, it can be used to remove the delegate from the list
995     that it is in.
996 
997     \sa swipe, swipe.closed()
998 */
999 
1000 /*!
1001     \since QtQuick.Controls 2.2 (Qt 5.9)
1002     \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.closed()
1003 
1004     This signal is emitted when the delegate has been swiped to closed
1005     and the transition has finished.
1006 
1007     It is useful for performing some action upon cancellation of a swipe.
1008     For example, it can be used to cancel the removal of the delegate from
1009     the list that it is in.
1010 
1011     \sa swipe, swipe.opened()
1012 */
1013 
1014 /*!
1015     \since QtQuick.Controls 2.1 (Qt 5.8)
1016     \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.completed()
1017 
1018     This signal is emitted when \c swipe.complete becomes \c true.
1019 
1020     It is useful for performing some action upon completion of a swipe.
1021     For example, it can be used to remove the delegate from the list
1022     that it is in.
1023 
1024     \sa swipe
1025 */
1026 
1027 /*!
1028     \qmlproperty real QtQuick.Controls::SwipeDelegate::swipe.position
1029     \qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.complete
1030     \qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.enabled
1031     \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.left
1032     \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.behind
1033     \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.right
1034     \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.leftItem
1035     \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.behindItem
1036     \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.rightItem
1037     \qmlproperty Transition QtQuick.Controls::SwipeDelegate::swipe.transition
1038 
1039     \table
1040     \header
1041         \li Name
1042         \li Description
1043     \row
1044         \li position
1045         \li This read-only property holds the position of the swipe relative to either
1046             side of the control. When this value reaches either
1047             \c -1.0 (left side) or \c 1.0 (right side) and the mouse button is
1048             released, \c complete will be \c true.
1049     \row
1050         \li complete
1051         \li This read-only property holds whether the control is fully exposed after
1052             having been swiped to the left or right.
1053 
1054             When complete is \c true, any interactive items declared in \c left,
1055             \c right, or \c behind will receive mouse events.
1056     \row
1057         \li enabled
1058         \li This property determines whether or not the control can be swiped.
1059 
1060             This property was added in QtQuick.Controls 2.2.
1061     \row
1062         \li left
1063         \li This property holds the left delegate.
1064 
1065             The left delegate sits behind both \l {Control::}{contentItem} and
1066             \l {Control::}{background}. When the SwipeDelegate is swiped to the right,
1067             this item will be gradually revealed.
1068 
1069             \include qquickswipedelegate-interaction.qdocinc
1070     \row
1071         \li behind
1072         \li This property holds the delegate that is shown when the
1073             SwipeDelegate is swiped to both the left and right.
1074 
1075             As with the \c left and \c right delegates, it sits behind both
1076             \l {Control::}{contentItem} and \l {Control::}{background}. However, a
1077             SwipeDelegate whose \c behind has been set can be continuously swiped
1078             from either side, and will always show the same item.
1079 
1080             \include qquickswipedelegate-interaction.qdocinc
1081     \row
1082         \li right
1083         \li This property holds the right delegate.
1084 
1085             The right delegate sits behind both \l {Control::}{contentItem} and
1086             \l {Control::}{background}. When the SwipeDelegate is swiped to the left,
1087             this item will be gradually revealed.
1088 
1089             \include qquickswipedelegate-interaction.qdocinc
1090     \row
1091         \li leftItem
1092         \li This read-only property holds the item instantiated from the \c left component.
1093 
1094             If \c left has not been set, or the position hasn't changed since
1095             creation of the SwipeDelegate, this property will be \c null.
1096     \row
1097         \li behindItem
1098         \li This read-only property holds the item instantiated from the \c behind component.
1099 
1100             If \c behind has not been set, or the position hasn't changed since
1101             creation of the SwipeDelegate, this property will be \c null.
1102     \row
1103         \li rightItem
1104         \li This read-only property holds the item instantiated from the \c right component.
1105 
1106             If \c right has not been set, or the position hasn't changed since
1107             creation of the SwipeDelegate, this property will be \c null.
1108     \row
1109         \li transition
1110         \li This property holds the transition that is applied when a swipe is released,
1111             or \l swipe.open() or \l swipe.close() is called.
1112 
1113             \snippet qtquickcontrols2-swipedelegate-transition.qml 1
1114 
1115             This property was added in Qt Quick Controls 2.2.
1116     \endtable
1117 
1118     \sa {Control::}{contentItem}, {Control::}{background}, swipe.open(), swipe.close()
1119 */
swipe() const1120 QQuickSwipe *QQuickSwipeDelegate::swipe() const
1121 {
1122     Q_D(const QQuickSwipeDelegate);
1123     return const_cast<QQuickSwipe*>(&d->swipe);
1124 }
1125 
qmlAttachedProperties(QObject * object)1126 QQuickSwipeDelegateAttached *QQuickSwipeDelegate::qmlAttachedProperties(QObject *object)
1127 {
1128     return new QQuickSwipeDelegateAttached(object);
1129 }
1130 
isChildOrGrandchildOf(QQuickItem * child,QQuickItem * item)1131 static bool isChildOrGrandchildOf(QQuickItem *child, QQuickItem *item)
1132 {
1133     return item && (child == item || item->isAncestorOf(child));
1134 }
1135 
childMouseEventFilter(QQuickItem * child,QEvent * event)1136 bool QQuickSwipeDelegate::childMouseEventFilter(QQuickItem *child, QEvent *event)
1137 {
1138     Q_D(QQuickSwipeDelegate);
1139     // The contentItem is, by default, usually a non-interactive item like Text, and
1140     // the same applies to the background. This means that simply stacking the left/right/behind
1141     // items before these items won't allow us to get mouse events when the control is not currently exposed
1142     // but has been previously. Therefore, we instead call setFiltersChildMouseEvents(true) in the constructor
1143     // and filter out child events only when the child is the left/right/behind item.
1144     const QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
1145     if (!isChildOrGrandchildOf(child, swipePrivate->leftItem) && !isChildOrGrandchildOf(child, swipePrivate->behindItem)
1146         && !isChildOrGrandchildOf(child, swipePrivate->rightItem)) {
1147         return false;
1148     }
1149 
1150     switch (event->type()) {
1151     case QEvent::MouseButtonPress: {
1152         return d->handleMousePressEvent(child, static_cast<QMouseEvent *>(event));
1153     } case QEvent::MouseMove: {
1154         return d->handleMouseMoveEvent(child, static_cast<QMouseEvent *>(event));
1155     } case QEvent::MouseButtonRelease: {
1156         // Make sure that the control gets release events if it has created child
1157         // items that are stealing events from it.
1158         QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
1159         QQuickItemDelegate::mouseReleaseEvent(mouseEvent);
1160         return d->handleMouseReleaseEvent(child, mouseEvent);
1161     } case QEvent::UngrabMouse: {
1162         // If the mouse was pressed over e.g. rightItem and then dragged down,
1163         // the ListView would eventually grab the mouse, at which point we must
1164         // clear the pressed flag so that it doesn't stay pressed after the release.
1165         Attached *attached = attachedObject(child);
1166         if (attached)
1167             attached->setPressed(false);
1168         return false;
1169     } default:
1170         return false;
1171     }
1172 }
1173 
1174 // We only override this to set positionBeforePress;
1175 // otherwise, it's the same as the base class implementation.
mousePressEvent(QMouseEvent * event)1176 void QQuickSwipeDelegate::mousePressEvent(QMouseEvent *event)
1177 {
1178     Q_D(QQuickSwipeDelegate);
1179     QQuickItemDelegate::mousePressEvent(event);
1180 
1181     QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
1182     if (!swipePrivate->enabled)
1183         return;
1184 
1185     swipePrivate->positionBeforePress = swipePrivate->position;
1186     swipePrivate->velocityCalculator.startMeasuring(event->pos(), event->timestamp());
1187 }
1188 
mouseMoveEvent(QMouseEvent * event)1189 void QQuickSwipeDelegate::mouseMoveEvent(QMouseEvent *event)
1190 {
1191     Q_D(QQuickSwipeDelegate);
1192     if (filtersChildMouseEvents())
1193         d->handleMouseMoveEvent(this, event);
1194     else
1195         QQuickItemDelegate::mouseMoveEvent(event);
1196 }
1197 
mouseReleaseEvent(QMouseEvent * event)1198 void QQuickSwipeDelegate::mouseReleaseEvent(QMouseEvent *event)
1199 {
1200     Q_D(QQuickSwipeDelegate);
1201     if (!filtersChildMouseEvents() || !d->handleMouseReleaseEvent(this, event))
1202         QQuickItemDelegate::mouseReleaseEvent(event);
1203 }
1204 
touchEvent(QTouchEvent * event)1205 void QQuickSwipeDelegate::touchEvent(QTouchEvent *event)
1206 {
1207     // Don't allow QQuickControl accept the touch event, because QQuickSwipeDelegate
1208     // is still based on synthesized mouse events
1209     event->ignore();
1210 }
1211 
componentComplete()1212 void QQuickSwipeDelegate::componentComplete()
1213 {
1214     Q_D(QQuickSwipeDelegate);
1215     QQuickItemDelegate::componentComplete();
1216     QQuickSwipePrivate::get(&d->swipe)->reposition(DontAnimatePosition);
1217 }
1218 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)1219 void QQuickSwipeDelegate::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1220 {
1221     Q_D(QQuickSwipeDelegate);
1222     QQuickControl::geometryChanged(newGeometry, oldGeometry);
1223 
1224     if (isComponentComplete() && !qFuzzyCompare(newGeometry.width(), oldGeometry.width())) {
1225         QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
1226         swipePrivate->reposition(DontAnimatePosition);
1227     }
1228 }
1229 
defaultFont() const1230 QFont QQuickSwipeDelegate::defaultFont() const
1231 {
1232     return QQuickTheme::font(QQuickTheme::ListView);
1233 }
1234 
defaultPalette() const1235 QPalette QQuickSwipeDelegate::defaultPalette() const
1236 {
1237     return QQuickTheme::palette(QQuickTheme::ListView);
1238 }
1239 
1240 #if QT_CONFIG(accessibility)
accessibleRole() const1241 QAccessible::Role QQuickSwipeDelegate::accessibleRole() const
1242 {
1243     return QAccessible::ListItem;
1244 }
1245 #endif
1246 
1247 class QQuickSwipeDelegateAttachedPrivate : public QObjectPrivate
1248 {
1249     Q_DECLARE_PUBLIC(QQuickSwipeDelegateAttached)
1250 
1251 public:
1252     // True when left/right/behind is non-interactive and is pressed.
1253     bool pressed = false;
1254 };
1255 
1256 /*!
1257     \since QtQuick.Controls 2.1 (Qt 5.8)
1258     \qmlattachedsignal QtQuick.Controls::SwipeDelegate::clicked()
1259 
1260     This signal can be attached to a non-interactive item declared in
1261     \c swipe.left, \c swipe.right, or \c swipe.behind, in order to react to
1262     clicks. Items can only be clicked when \c swipe.complete is \c true.
1263 
1264     For interactive controls (such as \l Button) declared in these
1265     items, use their respective \c clicked() signal instead.
1266 
1267     To respond to clicks on the SwipeDelegate itself, use its
1268     \l {AbstractButton::}{clicked()} signal.
1269 
1270     \note See the documentation for \l pressed for information on
1271     how to use the event-related properties correctly.
1272 
1273     \sa pressed
1274 */
1275 
QQuickSwipeDelegateAttached(QObject * object)1276 QQuickSwipeDelegateAttached::QQuickSwipeDelegateAttached(QObject *object)
1277     : QObject(*(new QQuickSwipeDelegateAttachedPrivate), object)
1278 {
1279     QQuickItem *item = qobject_cast<QQuickItem *>(object);
1280     if (item) {
1281         // This allows us to be notified when an otherwise non-interactive item
1282         // is pressed and clicked. The alternative is much more more complex:
1283         // iterating through children that contain the event pos and finding
1284         // the first one with an attached object.
1285         item->setAcceptedMouseButtons(Qt::AllButtons);
1286     } else {
1287         qWarning() << "Attached properties of SwipeDelegate must be accessed through an Item";
1288     }
1289 }
1290 
1291 /*!
1292     \since QtQuick.Controls 2.1 (Qt 5.8)
1293     \qmlattachedproperty bool QtQuick.Controls::SwipeDelegate::pressed
1294     \readonly
1295 
1296     This property can be attached to a non-interactive item declared in
1297     \c swipe.left, \c swipe.right, or \c swipe.behind, in order to detect if it
1298     is pressed. Items can only be pressed when \c swipe.complete is \c true.
1299 
1300     For example:
1301 
1302     \code
1303     swipe.right: Label {
1304         anchors.right: parent.right
1305         height: parent.height
1306         text: "Action"
1307         color: "white"
1308         padding: 12
1309         background: Rectangle {
1310             color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
1311         }
1312     }
1313     \endcode
1314 
1315     It is possible to have multiple items which individually receive mouse and
1316     touch events. For example, to have two actions in the \c swipe.right item,
1317     use the following code:
1318 
1319     \code
1320     swipe.right: Row {
1321         anchors.right: parent.right
1322         height: parent.height
1323 
1324         Label {
1325             id: moveLabel
1326             text: qsTr("Move")
1327             color: "white"
1328             verticalAlignment: Label.AlignVCenter
1329             padding: 12
1330             height: parent.height
1331 
1332             SwipeDelegate.onClicked: console.log("Moving...")
1333 
1334             background: Rectangle {
1335                 color: moveLabel.SwipeDelegate.pressed ? Qt.darker("#ffbf47", 1.1) : "#ffbf47"
1336             }
1337         }
1338         Label {
1339             id: deleteLabel
1340             text: qsTr("Delete")
1341             color: "white"
1342             verticalAlignment: Label.AlignVCenter
1343             padding: 12
1344             height: parent.height
1345 
1346             SwipeDelegate.onClicked: console.log("Deleting...")
1347 
1348             background: Rectangle {
1349                 color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
1350             }
1351         }
1352     }
1353     \endcode
1354 
1355     Note how the \c color assignment in each \l {Control::}{background} item
1356     qualifies the attached property with the \c id of the label. This
1357     is important; using the attached properties on an item causes that item
1358     to accept events. Suppose we had left out the \c id in the previous example:
1359 
1360     \code
1361     color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
1362     \endcode
1363 
1364     The \l Rectangle background item is a child of the label, so it naturally
1365     receives events before it. In practice, this means that the background
1366     color will change, but the \c onClicked handler in the label will never
1367     get called.
1368 
1369     For interactive controls (such as \l Button) declared in these
1370     items, use their respective \c pressed property instead.
1371 
1372     For presses on the SwipeDelegate itself, use its
1373     \l {AbstractButton::}{pressed} property.
1374 
1375     \sa clicked()
1376 */
isPressed() const1377 bool QQuickSwipeDelegateAttached::isPressed() const
1378 {
1379     Q_D(const QQuickSwipeDelegateAttached);
1380     return d->pressed;
1381 }
1382 
setPressed(bool pressed)1383 void QQuickSwipeDelegateAttached::setPressed(bool pressed)
1384 {
1385     Q_D(QQuickSwipeDelegateAttached);
1386     if (pressed == d->pressed)
1387         return;
1388 
1389     d->pressed = pressed;
1390     emit pressedChanged();
1391 }
1392 
1393 QT_END_NAMESPACE
1394