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

source code of qtdeclarative/src/quicktemplates/qquickswipedelegate.cpp