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

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