f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
1532 lines
52 KiB
C++
1532 lines
52 KiB
C++
// Copyright (C) 2017 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
// Qt-Security score:significant reason:default
|
|
|
|
#include "qquickswipedelegate_p.h"
|
|
#include "qquickswipedelegate_p_p.h"
|
|
#include "qquickcontrol_p_p.h"
|
|
#include "qquickitemdelegate_p_p.h"
|
|
#include "qquickvelocitycalculator_p_p.h"
|
|
|
|
#include <QtGui/qstylehints.h>
|
|
#include <QtGui/private/qguiapplication_p.h>
|
|
#include <QtGui/private/qeventpoint_p.h>
|
|
#include <QtGui/qpa/qplatformtheme.h>
|
|
#include <QtQml/qqmlinfo.h>
|
|
#include <QtQuick/private/qquickanimation_p.h>
|
|
#include <QtQuick/private/qquicktransition_p.h>
|
|
#include <QtQuick/private/qquicktransitionmanager_p_p.h>
|
|
|
|
#include <algorithm>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
/*!
|
|
\qmltype SwipeDelegate
|
|
\inherits ItemDelegate
|
|
//! \nativetype QQuickSwipeDelegate
|
|
\inqmlmodule QtQuick.Controls
|
|
\since 5.7
|
|
\ingroup qtquickcontrols-delegates
|
|
\brief Swipable item delegate.
|
|
|
|
SwipeDelegate presents a view item that can be swiped left or right to
|
|
expose more options or information. It is used as a delegate in views such
|
|
as \l ListView.
|
|
|
|
In the following example, SwipeDelegate is used in a \l ListView to allow
|
|
items to be removed from it by swiping to the left:
|
|
|
|
\snippet qtquickcontrols-swipedelegate.qml 1
|
|
|
|
SwipeDelegate inherits its API from \l ItemDelegate, which is inherited
|
|
from AbstractButton. For instance, you can set \l {AbstractButton::text}{text},
|
|
and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton
|
|
API.
|
|
|
|
Information regarding the progress of a swipe, as well as the components
|
|
that should be shown upon swiping, are both available through the
|
|
\l {SwipeDelegate::}{swipe} grouped property object. For example,
|
|
\c swipe.position holds the position of the
|
|
swipe within the range \c -1.0 to \c 1.0. The \c swipe.left
|
|
property determines which item will be displayed when the control is swiped
|
|
to the right, and vice versa for \c swipe.right. The positioning of these
|
|
components is left to applications to decide. For example, without specifying
|
|
any position for \c swipe.left or \c swipe.right, the following will
|
|
occur:
|
|
|
|
\image qtquickcontrols-swipedelegate.gif
|
|
{Swipe delegate revealing actions on swipe gesture}
|
|
|
|
If \c swipe.left and \c swipe.right are anchored to the left and
|
|
right of the \l {Control::}{background} item (respectively), they'll behave like this:
|
|
|
|
\image qtquickcontrols-swipedelegate-leading-trailing.gif
|
|
{Swipe delegate showing leading and trailing actions}
|
|
|
|
When using \c swipe.left and \c swipe.right, the control cannot be
|
|
swiped past the left and right edges. To achieve this type of "wrapping"
|
|
behavior, set \c swipe.behind instead. This will result in the same
|
|
item being shown regardless of which direction the control is swiped. For
|
|
example, in the image below, we set \c swipe.behind and then swipe the
|
|
control repeatedly in both directions:
|
|
|
|
\image qtquickcontrols-swipedelegate-behind.gif
|
|
{Swipe delegate showing behind swipe action}
|
|
|
|
\sa {Customizing SwipeDelegate}, {Delegate Controls}, {Qt Quick Controls 2 - Gallery}{Gallery Example}
|
|
*/
|
|
|
|
namespace {
|
|
typedef QQuickSwipeDelegateAttached Attached;
|
|
|
|
Attached *attachedObject(QQuickItem *item) {
|
|
return qobject_cast<Attached*>(qmlAttachedPropertiesObject<QQuickSwipeDelegate>(item, false));
|
|
}
|
|
|
|
enum PositionAnimation {
|
|
DontAnimatePosition,
|
|
AnimatePosition
|
|
};
|
|
}
|
|
|
|
class QQuickSwipeTransitionManager : public QQuickTransitionManager
|
|
{
|
|
public:
|
|
QQuickSwipeTransitionManager(QQuickSwipe *swipe);
|
|
|
|
void transition(QQuickTransition *transition, qreal position);
|
|
|
|
protected:
|
|
void finished() override;
|
|
|
|
private:
|
|
QQuickSwipe *m_swipe = nullptr;
|
|
};
|
|
|
|
class QQuickSwipePrivate : public QObjectPrivate
|
|
{
|
|
Q_DECLARE_PUBLIC(QQuickSwipe)
|
|
|
|
public:
|
|
QQuickSwipePrivate(QQuickSwipeDelegate *control) : control(control) { }
|
|
|
|
static QQuickSwipePrivate *get(QQuickSwipe *swipe);
|
|
|
|
QQuickItem *createDelegateItem(QQmlComponent *component);
|
|
QQuickItem *showRelevantItemForPosition(qreal position);
|
|
QQuickItem *createRelevantItemForDistance(qreal distance);
|
|
void reposition(PositionAnimation animationPolicy);
|
|
void createLeftItem();
|
|
void createBehindItem();
|
|
void createRightItem();
|
|
void createAndShowLeftItem();
|
|
void createAndShowBehindItem();
|
|
void createAndShowRightItem();
|
|
|
|
void warnAboutMixingDelegates();
|
|
void warnAboutSettingDelegatesWhileVisible();
|
|
|
|
bool hasDelegates() const;
|
|
|
|
bool isTransitioning() const;
|
|
void beginTransition(qreal position);
|
|
void finishTransition();
|
|
|
|
QQuickSwipeDelegate *control = nullptr;
|
|
// Same range as position, but is set before press events so that we can
|
|
// keep track of which direction the user must swipe when using left and right delegates.
|
|
qreal positionBeforePress = 0;
|
|
qreal position = 0;
|
|
// A "less strict" version of complete that is true if complete was true
|
|
// before the last press event.
|
|
bool wasComplete = false;
|
|
bool complete = false;
|
|
bool enabled = true;
|
|
bool waitForTransition = false;
|
|
QQuickVelocityCalculator velocityCalculator;
|
|
QQmlComponent *left = nullptr;
|
|
QQmlComponent *behind = nullptr;
|
|
QQmlComponent *right = nullptr;
|
|
QQuickItem *leftItem = nullptr;
|
|
QQuickItem *behindItem = nullptr;
|
|
QQuickItem *rightItem = nullptr;
|
|
QQuickTransition *transition = nullptr;
|
|
QScopedPointer<QQuickSwipeTransitionManager> transitionManager;
|
|
};
|
|
|
|
QQuickSwipeTransitionManager::QQuickSwipeTransitionManager(QQuickSwipe *swipe)
|
|
: m_swipe(swipe)
|
|
{
|
|
}
|
|
|
|
void QQuickSwipeTransitionManager::transition(QQuickTransition *transition, qreal position)
|
|
{
|
|
qmlExecuteDeferred(transition);
|
|
|
|
QQmlProperty defaultTarget(m_swipe, QLatin1String("position"));
|
|
QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations();
|
|
const int count = animations.count(&animations);
|
|
for (int i = 0; i < count; ++i) {
|
|
QQuickAbstractAnimation *anim = animations.at(&animations, i);
|
|
anim->setDefaultTarget(defaultTarget);
|
|
}
|
|
|
|
QList<QQuickStateAction> actions;
|
|
actions << QQuickStateAction(m_swipe, QLatin1String("position"), position);
|
|
QQuickTransitionManager::transition(actions, transition, m_swipe);
|
|
}
|
|
|
|
void QQuickSwipeTransitionManager::finished()
|
|
{
|
|
QQuickSwipePrivate::get(m_swipe)->finishTransition();
|
|
}
|
|
|
|
QQuickSwipePrivate *QQuickSwipePrivate::get(QQuickSwipe *swipe)
|
|
{
|
|
return swipe->d_func();
|
|
}
|
|
|
|
QQuickItem *QQuickSwipePrivate::createDelegateItem(QQmlComponent *component)
|
|
{
|
|
// If we don't use the correct context, it won't be possible to refer to
|
|
// the control's id from within the delegates.
|
|
QQmlContext *context = component->creationContext();
|
|
// The component might not have been created in QML, in which case
|
|
// the creation context will be null and we have to create it ourselves.
|
|
if (!context)
|
|
context = qmlContext(control);
|
|
QQuickItem *item = qobject_cast<QQuickItem*>(component->beginCreate(context));
|
|
if (item) {
|
|
item->setParentItem(control);
|
|
component->completeCreate();
|
|
QJSEngine::setObjectOwnership(item, QJSEngine::JavaScriptOwnership);
|
|
}
|
|
return item;
|
|
}
|
|
|
|
QQuickItem *QQuickSwipePrivate::showRelevantItemForPosition(qreal position)
|
|
{
|
|
if (qFuzzyIsNull(position))
|
|
return nullptr;
|
|
|
|
if (behind) {
|
|
createAndShowBehindItem();
|
|
return behindItem;
|
|
}
|
|
|
|
if (right && position < 0.0) {
|
|
createAndShowRightItem();
|
|
return rightItem;
|
|
}
|
|
|
|
if (left && position > 0.0) {
|
|
createAndShowLeftItem();
|
|
return leftItem;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QQuickItem *QQuickSwipePrivate::createRelevantItemForDistance(qreal distance)
|
|
{
|
|
if (qFuzzyIsNull(distance))
|
|
return nullptr;
|
|
|
|
if (behind) {
|
|
createBehindItem();
|
|
return behindItem;
|
|
}
|
|
|
|
// a) If the position before the press was 0.0, we know that *any* movement
|
|
// whose distance is negative will result in the right item being shown and
|
|
// vice versa.
|
|
// b) Once the control has been exposed (that is, swiped to the left or right,
|
|
// and hence the position is either -1.0 or 1.0), we must use the width of the
|
|
// relevant item to determine if the distance is larger than that item,
|
|
// in order to know whether or not to display it.
|
|
// c) If the control has been exposed, and the swipe is larger than the width
|
|
// of the relevant item from which the swipe started from, we must show the
|
|
// item on the other side (if any).
|
|
|
|
if (right) {
|
|
if ((distance < 0.0 && positionBeforePress == 0.0) /* a) */
|
|
|| (rightItem && positionBeforePress == -1.0 && distance < rightItem->width()) /* b) */
|
|
|| (leftItem && positionBeforePress == 1.0 && qAbs(distance) > leftItem->width())) /* c) */ {
|
|
createRightItem();
|
|
return rightItem;
|
|
}
|
|
}
|
|
|
|
if (left) {
|
|
if ((distance > 0.0 && positionBeforePress == 0.0) /* a) */
|
|
|| (leftItem && positionBeforePress == 1.0 && qAbs(distance) < leftItem->width()) /* b) */
|
|
|| (rightItem && positionBeforePress == -1.0 && qAbs(distance) > rightItem->width())) /* c) */ {
|
|
createLeftItem();
|
|
return leftItem;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void QQuickSwipePrivate::reposition(PositionAnimation animationPolicy)
|
|
{
|
|
QQuickItem *relevantItem = showRelevantItemForPosition(position);
|
|
const qreal relevantWidth = relevantItem ? relevantItem->width() : 0.0;
|
|
const qreal contentItemX = position * relevantWidth + control->leftPadding();
|
|
|
|
// "Behavior on x" relies on the property system to know when it should update,
|
|
// so we can prevent it from animating by setting the x position directly.
|
|
if (animationPolicy == AnimatePosition) {
|
|
if (QQuickItem *contentItem = control->contentItem())
|
|
contentItem->setProperty("x", contentItemX);
|
|
if (QQuickItem *background = control->background())
|
|
background->setProperty("x", position * relevantWidth);
|
|
} else {
|
|
if (QQuickItem *contentItem = control->contentItem())
|
|
contentItem->setX(contentItemX);
|
|
if (QQuickItem *background = control->background())
|
|
background->setX(position * relevantWidth);
|
|
}
|
|
}
|
|
|
|
void QQuickSwipePrivate::createLeftItem()
|
|
{
|
|
if (!leftItem) {
|
|
Q_Q(QQuickSwipe);
|
|
q->setLeftItem(createDelegateItem(left));
|
|
if (!leftItem)
|
|
qmlWarning(control) << "Failed to create left item:" << left->errors();
|
|
}
|
|
}
|
|
|
|
void QQuickSwipePrivate::createBehindItem()
|
|
{
|
|
if (!behindItem) {
|
|
Q_Q(QQuickSwipe);
|
|
q->setBehindItem(createDelegateItem(behind));
|
|
if (!behindItem)
|
|
qmlWarning(control) << "Failed to create behind item:" << behind->errors();
|
|
}
|
|
}
|
|
|
|
void QQuickSwipePrivate::createRightItem()
|
|
{
|
|
if (!rightItem) {
|
|
Q_Q(QQuickSwipe);
|
|
q->setRightItem(createDelegateItem(right));
|
|
if (!rightItem)
|
|
qmlWarning(control) << "Failed to create right item:" << right->errors();
|
|
}
|
|
}
|
|
|
|
void QQuickSwipePrivate::createAndShowLeftItem()
|
|
{
|
|
createLeftItem();
|
|
|
|
if (leftItem)
|
|
leftItem->setVisible(true);
|
|
|
|
if (rightItem)
|
|
rightItem->setVisible(false);
|
|
}
|
|
|
|
void QQuickSwipePrivate::createAndShowBehindItem()
|
|
{
|
|
createBehindItem();
|
|
|
|
if (behindItem)
|
|
behindItem->setVisible(true);
|
|
}
|
|
|
|
void QQuickSwipePrivate::createAndShowRightItem()
|
|
{
|
|
createRightItem();
|
|
|
|
// This item may have already existed but was hidden.
|
|
if (rightItem)
|
|
rightItem->setVisible(true);
|
|
|
|
// The left item isn't visible when the right item is visible, so save rendering effort by hiding it.
|
|
if (leftItem)
|
|
leftItem->setVisible(false);
|
|
}
|
|
|
|
void QQuickSwipePrivate::warnAboutMixingDelegates()
|
|
{
|
|
qmlWarning(control) << "cannot set both behind and left/right properties";
|
|
}
|
|
|
|
void QQuickSwipePrivate::warnAboutSettingDelegatesWhileVisible()
|
|
{
|
|
qmlWarning(control) << "left/right/behind properties may only be set when swipe.position is 0";
|
|
}
|
|
|
|
bool QQuickSwipePrivate::hasDelegates() const
|
|
{
|
|
return left || right || behind;
|
|
}
|
|
|
|
bool QQuickSwipePrivate::isTransitioning() const
|
|
{
|
|
return transitionManager && transitionManager->isRunning();
|
|
}
|
|
|
|
void QQuickSwipePrivate::beginTransition(qreal newPosition)
|
|
{
|
|
if (waitForTransition)
|
|
return;
|
|
Q_Q(QQuickSwipe);
|
|
if (!transition) {
|
|
q->setPosition(newPosition);
|
|
finishTransition();
|
|
return;
|
|
}
|
|
|
|
if (!transitionManager)
|
|
transitionManager.reset(new QQuickSwipeTransitionManager(q));
|
|
|
|
transitionManager->transition(transition, newPosition);
|
|
}
|
|
|
|
void QQuickSwipePrivate::finishTransition()
|
|
{
|
|
Q_Q(QQuickSwipe);
|
|
waitForTransition = false;
|
|
q->setComplete(qFuzzyCompare(qAbs(position), qreal(1.0)));
|
|
if (complete) {
|
|
emit q->opened();
|
|
} else {
|
|
if (qFuzzyIsNull(position))
|
|
wasComplete = false;
|
|
emit q->closed();
|
|
}
|
|
}
|
|
|
|
QQuickSwipe::QQuickSwipe(QQuickSwipeDelegate *control)
|
|
: QObject(*(new QQuickSwipePrivate(control)))
|
|
{
|
|
}
|
|
|
|
QQmlComponent *QQuickSwipe::left() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->left;
|
|
}
|
|
|
|
void QQuickSwipe::setLeft(QQmlComponent *left)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (left == d->left)
|
|
return;
|
|
|
|
if (d->behind) {
|
|
d->warnAboutMixingDelegates();
|
|
return;
|
|
}
|
|
|
|
if (!qFuzzyIsNull(d->position)) {
|
|
d->warnAboutSettingDelegatesWhileVisible();
|
|
return;
|
|
}
|
|
|
|
d->left = left;
|
|
|
|
if (!d->left) {
|
|
delete d->leftItem;
|
|
d->leftItem = nullptr;
|
|
}
|
|
|
|
d->control->setFiltersChildMouseEvents(d->hasDelegates());
|
|
|
|
emit leftChanged();
|
|
}
|
|
|
|
QQmlComponent *QQuickSwipe::behind() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->behind;
|
|
}
|
|
|
|
void QQuickSwipe::setBehind(QQmlComponent *behind)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (behind == d->behind)
|
|
return;
|
|
|
|
if (d->left || d->right) {
|
|
d->warnAboutMixingDelegates();
|
|
return;
|
|
}
|
|
|
|
if (!qFuzzyIsNull(d->position)) {
|
|
d->warnAboutSettingDelegatesWhileVisible();
|
|
return;
|
|
}
|
|
|
|
d->behind = behind;
|
|
|
|
if (!d->behind) {
|
|
delete d->behindItem;
|
|
d->behindItem = nullptr;
|
|
}
|
|
|
|
d->control->setFiltersChildMouseEvents(d->hasDelegates());
|
|
|
|
emit behindChanged();
|
|
}
|
|
|
|
QQmlComponent *QQuickSwipe::right() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->right;
|
|
}
|
|
|
|
void QQuickSwipe::setRight(QQmlComponent *right)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (right == d->right)
|
|
return;
|
|
|
|
if (d->behind) {
|
|
d->warnAboutMixingDelegates();
|
|
return;
|
|
}
|
|
|
|
if (!qFuzzyIsNull(d->position)) {
|
|
d->warnAboutSettingDelegatesWhileVisible();
|
|
return;
|
|
}
|
|
|
|
d->right = right;
|
|
|
|
if (!d->right) {
|
|
delete d->rightItem;
|
|
d->rightItem = nullptr;
|
|
}
|
|
|
|
d->control->setFiltersChildMouseEvents(d->hasDelegates());
|
|
|
|
emit rightChanged();
|
|
}
|
|
|
|
QQuickItem *QQuickSwipe::leftItem() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->leftItem;
|
|
}
|
|
|
|
void QQuickSwipe::setLeftItem(QQuickItem *item)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (item == d->leftItem)
|
|
return;
|
|
|
|
delete d->leftItem;
|
|
d->leftItem = item;
|
|
|
|
if (d->leftItem) {
|
|
d->leftItem->setParentItem(d->control);
|
|
|
|
if (qFuzzyIsNull(d->leftItem->z()))
|
|
d->leftItem->setZ(-2);
|
|
}
|
|
|
|
emit leftItemChanged();
|
|
}
|
|
|
|
QQuickItem *QQuickSwipe::behindItem() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->behindItem;
|
|
}
|
|
|
|
void QQuickSwipe::setBehindItem(QQuickItem *item)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (item == d->behindItem)
|
|
return;
|
|
|
|
delete d->behindItem;
|
|
d->behindItem = item;
|
|
|
|
if (d->behindItem) {
|
|
d->behindItem->setParentItem(d->control);
|
|
|
|
if (qFuzzyIsNull(d->behindItem->z()))
|
|
d->behindItem->setZ(-2);
|
|
}
|
|
|
|
emit behindItemChanged();
|
|
}
|
|
|
|
QQuickItem *QQuickSwipe::rightItem() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->rightItem;
|
|
}
|
|
|
|
void QQuickSwipe::setRightItem(QQuickItem *item)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (item == d->rightItem)
|
|
return;
|
|
|
|
delete d->rightItem;
|
|
d->rightItem = item;
|
|
|
|
if (d->rightItem) {
|
|
d->rightItem->setParentItem(d->control);
|
|
|
|
if (qFuzzyIsNull(d->rightItem->z()))
|
|
d->rightItem->setZ(-2);
|
|
}
|
|
|
|
emit rightItemChanged();
|
|
}
|
|
|
|
qreal QQuickSwipe::position() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->position;
|
|
}
|
|
|
|
void QQuickSwipe::setPosition(qreal position)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
const qreal adjustedPosition = std::clamp(position, qreal(-1.0), qreal(1.0));
|
|
if (adjustedPosition == d->position)
|
|
return;
|
|
|
|
d->position = adjustedPosition;
|
|
d->reposition(AnimatePosition);
|
|
emit positionChanged();
|
|
}
|
|
|
|
bool QQuickSwipe::isComplete() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->complete;
|
|
}
|
|
|
|
void QQuickSwipe::setComplete(bool complete)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (complete == d->complete)
|
|
return;
|
|
|
|
d->complete = complete;
|
|
emit completeChanged();
|
|
if (d->complete)
|
|
emit completed();
|
|
}
|
|
|
|
bool QQuickSwipe::isEnabled() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->enabled;
|
|
}
|
|
|
|
void QQuickSwipe::setEnabled(bool enabled)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (enabled == d->enabled)
|
|
return;
|
|
|
|
d->enabled = enabled;
|
|
emit enabledChanged();
|
|
}
|
|
|
|
QQuickTransition *QQuickSwipe::transition() const
|
|
{
|
|
Q_D(const QQuickSwipe);
|
|
return d->transition;
|
|
}
|
|
|
|
void QQuickSwipe::setTransition(QQuickTransition *transition)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (transition == d->transition)
|
|
return;
|
|
|
|
d->transition = transition;
|
|
emit transitionChanged();
|
|
}
|
|
|
|
void QQuickSwipe::open(QQuickSwipeDelegate::Side side)
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (qFuzzyCompare(qAbs(d->position), qreal(1.0)))
|
|
return;
|
|
|
|
if ((side != QQuickSwipeDelegate::Left && side != QQuickSwipeDelegate::Right)
|
|
|| (!d->left && !d->behind && side == QQuickSwipeDelegate::Left)
|
|
|| (!d->right && !d->behind && side == QQuickSwipeDelegate::Right))
|
|
return;
|
|
|
|
d->beginTransition(side);
|
|
d->wasComplete = true;
|
|
d->velocityCalculator.reset();
|
|
d->positionBeforePress = d->position;
|
|
}
|
|
|
|
void QQuickSwipe::close()
|
|
{
|
|
Q_D(QQuickSwipe);
|
|
if (qFuzzyIsNull(d->position))
|
|
return;
|
|
|
|
if (d->control->isPressed()) {
|
|
// We don't support closing when we're pressed; release() or clicked() should be used instead.
|
|
return;
|
|
}
|
|
|
|
d->beginTransition(0.0);
|
|
d->waitForTransition = true;
|
|
d->wasComplete = false;
|
|
d->positionBeforePress = 0.0;
|
|
d->velocityCalculator.reset();
|
|
}
|
|
|
|
QQuickSwipeDelegatePrivate::QQuickSwipeDelegatePrivate(QQuickSwipeDelegate *control)
|
|
: swipe(control)
|
|
{
|
|
}
|
|
|
|
void QQuickSwipeDelegatePrivate::resizeBackground()
|
|
{
|
|
if (!background)
|
|
return;
|
|
|
|
resizingBackground = true;
|
|
|
|
QQuickItemPrivate *p = QQuickItemPrivate::get(background);
|
|
const bool extraAllocated = extra.isAllocated();
|
|
// Don't check for or set the x here since it will just be overwritten by reposition().
|
|
if (((!p->widthValid() || !extraAllocated || !extra->hasBackgroundWidth))
|
|
|| (extraAllocated && (extra->hasLeftInset || extra->hasRightInset))) {
|
|
background->setWidth(width - getLeftInset() - getRightInset());
|
|
}
|
|
if (((!p->heightValid() || !extraAllocated || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y()))
|
|
|| (extraAllocated && (extra->hasTopInset || extra->hasBottomInset))) {
|
|
background->setY(getTopInset());
|
|
background->setHeight(height - getTopInset() - getBottomInset());
|
|
}
|
|
|
|
resizingBackground = false;
|
|
}
|
|
|
|
bool QQuickSwipeDelegatePrivate::handleMousePressEvent(QQuickItem *item, QMouseEvent *event)
|
|
{
|
|
Q_Q(QQuickSwipeDelegate);
|
|
const auto posInItem = item->mapToItem(q, event->position().toPoint());
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
|
|
// If the position is 0, we want to handle events ourselves - we don't want child items to steal them.
|
|
// This code will only get called when a child item has been created;
|
|
// events will go through the regular channels (mousePressEvent()) until then.
|
|
if (qFuzzyIsNull(swipePrivate->position)) {
|
|
q->mousePressEvent(event);
|
|
// The press point could be incorrect if the press happened over a child item,
|
|
// so we correct it after calling the base class' mousePressEvent(), rather
|
|
// than having to duplicate its code just so we can set the pressPoint.
|
|
setPressPoint(posInItem);
|
|
return true;
|
|
}
|
|
|
|
// If the delegate is swiped open, send the event to the exposed item,
|
|
// in case it's an interactive child (like a Button).
|
|
if (swipePrivate->complete)
|
|
forwardMouseEvent(event, item, posInItem);
|
|
|
|
// The position is non-zero, this press could be either for a delegate or the control itself
|
|
// (the control can be clicked to e.g. close the swipe). Either way, we must begin measuring
|
|
// mouse movement in case it turns into a swipe, in which case we grab the mouse.
|
|
swipePrivate->positionBeforePress = swipePrivate->position;
|
|
swipePrivate->velocityCalculator.startMeasuring(event->position().toPoint(), event->timestamp());
|
|
setPressPoint(item->mapToItem(q, event->position().toPoint()));
|
|
|
|
// When a delegate or any of its children uses the attached properties and signals,
|
|
// it declares that it wants mouse events.
|
|
const bool delivered = attachedObjectsSetPressed(item, event->scenePosition(), true);
|
|
if (delivered)
|
|
event->accept();
|
|
return delivered;
|
|
}
|
|
|
|
bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEvent *event)
|
|
{
|
|
Q_Q(QQuickSwipeDelegate);
|
|
|
|
if (holdTimer > 0) {
|
|
if (QLineF(pressPoint, event->position()).length() > QGuiApplication::styleHints()->startDragDistance())
|
|
stopPressAndHold();
|
|
}
|
|
|
|
// The delegate can still be pressed when swipe.enabled is false,
|
|
// but the mouse moving shouldn't have any effect on swipe.position.
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
|
|
if (!swipePrivate->enabled)
|
|
return false;
|
|
|
|
// Protect against division by zero.
|
|
if (width == 0)
|
|
return false;
|
|
|
|
// Don't bother reacting to events if we don't have any delegates.
|
|
if (!swipePrivate->left && !swipePrivate->right && !swipePrivate->behind)
|
|
return false;
|
|
|
|
if (item != q && swipePrivate->complete) {
|
|
// If the delegate is swiped open, send the event to the exposed item,
|
|
// in case it's an interactive child (like a Button).
|
|
const auto posInItem = item->mapToItem(q, event->position().toPoint());
|
|
forwardMouseEvent(event, item, posInItem);
|
|
}
|
|
|
|
// Don't handle move events for the control if it wasn't pressed.
|
|
if (item == q && !pressed)
|
|
return false;
|
|
|
|
static constexpr QGuiApplicationPrivate::QLastCursorPosition uninitializedCursorPosition;
|
|
const qreal distance = (event->globalPosition() == uninitializedCursorPosition ? 0 :
|
|
(item->mapFromGlobal(event->globalPosition()) -
|
|
item->mapFromGlobal(event->points().first().globalPressPosition())).x());
|
|
if (!q->keepMouseGrab()) {
|
|
// We used to use the custom threshold that QQuickDrawerPrivate::grabMouse used,
|
|
// but since it's larger than what Flickable uses, it results in Flickable
|
|
// stealing events from us (QTBUG-50045), so now we use the default.
|
|
const bool overThreshold = QQuickDeliveryAgentPrivate::dragOverThreshold(distance,
|
|
Qt::XAxis, event);
|
|
if (window && overThreshold) {
|
|
QQuickItem *grabber = q->window()->mouseGrabberItem();
|
|
if (!grabber || !grabber->keepMouseGrab()) {
|
|
q->grabMouse();
|
|
q->setKeepMouseGrab(true);
|
|
q->setPressed(true);
|
|
swipe.setComplete(false);
|
|
|
|
attachedObjectsSetPressed(item, event->scenePosition(), false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (q->keepMouseGrab()) {
|
|
// Ensure we don't try to calculate a position when the user tried to drag
|
|
// to the left when the left item is already exposed, and vice versa.
|
|
// The code below assumes that the drag is valid, so if we don't have this check,
|
|
// the wrong items are visible and the swiping wraps.
|
|
if (swipePrivate->behind
|
|
|| ((swipePrivate->left || swipePrivate->right)
|
|
&& (qFuzzyIsNull(swipePrivate->positionBeforePress)
|
|
|| (swipePrivate->positionBeforePress == -1.0 && distance >= 0.0)
|
|
|| (swipePrivate->positionBeforePress == 1.0 && distance <= 0.0)))) {
|
|
|
|
// We must instantiate the items here so that we can calculate the
|
|
// position against the width of the relevant item.
|
|
QQuickItem *relevantItem = swipePrivate->createRelevantItemForDistance(distance);
|
|
// If there isn't any relevant item, the user may have swiped back to the 0 position,
|
|
// or they swiped back to a position that is equal to positionBeforePress.
|
|
const qreal normalizedDistance = relevantItem ? distance / relevantItem->width() : 0.0;
|
|
qreal position = 0;
|
|
|
|
// If the control was exposed before the drag begun, the distance should be inverted.
|
|
// For example, if the control had been swiped to the right, the position would be 1.0.
|
|
// If the control was then swiped to the left by a distance of -20 pixels, the normalized
|
|
// distance might be -0.2, for example, which cannot be used as the position; the swipe
|
|
// started from the right, so we account for that by adding the position.
|
|
if (qFuzzyIsNull(normalizedDistance)) {
|
|
// There are two cases when the normalizedDistance can be 0,
|
|
// and we must distinguish between them:
|
|
//
|
|
// a) The swipe returns to the position that it was at before the press event.
|
|
// In this case, the distance will be 0.
|
|
// There would have been many position changes in the meantime, so we can't just
|
|
// ignore the move event; we have to set position to what it was before the press.
|
|
//
|
|
// b) If the position was at, 1.0, for example, and the control was then swiped
|
|
// to the left by the exact width of the left item, there won't be any relevant item
|
|
// (because the swipe's position would be at 0.0). In turn, the normalizedDistance
|
|
// would be 0 (because of the lack of a relevant item), but the distance will be non-zero.
|
|
position = qFuzzyIsNull(distance) ? swipePrivate->positionBeforePress : 0;
|
|
} else if (!swipePrivate->wasComplete) {
|
|
position = normalizedDistance;
|
|
} else {
|
|
position = distance > 0 ? normalizedDistance - 1.0 : normalizedDistance + 1.0;
|
|
}
|
|
|
|
if (swipePrivate->isTransitioning())
|
|
swipePrivate->transitionManager->cancel();
|
|
swipe.setPosition(position);
|
|
}
|
|
} else {
|
|
// The swipe wasn't initiated.
|
|
if (event->position().toPoint().y() < 0 || event->position().toPoint().y() > height) {
|
|
// The mouse went outside the vertical bounds of the control, so
|
|
// we should no longer consider it pressed.
|
|
q->setPressed(false);
|
|
}
|
|
}
|
|
|
|
event->accept();
|
|
|
|
return q->keepMouseGrab();
|
|
}
|
|
|
|
static const qreal exposeVelocityThreshold = 300.0;
|
|
|
|
bool QQuickSwipeDelegatePrivate::handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event)
|
|
{
|
|
Q_Q(QQuickSwipeDelegate);
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
|
|
swipePrivate->velocityCalculator.stopMeasuring(event->position().toPoint(), event->timestamp());
|
|
|
|
const bool hadGrabbedMouse = q->keepMouseGrab();
|
|
q->setKeepMouseGrab(false);
|
|
|
|
// QQuickSwipe::close() doesn't allow closing while pressed, but now we're releasing.
|
|
// So set the pressed state false if this release _could_ result in closing, so that check can be bypassed.
|
|
if (!qIsNull(swipePrivate->position))
|
|
q->setPressed(false);
|
|
|
|
// Animations for the background and contentItem delegates are typically
|
|
// only enabled when !control.down, so that the animations aren't running
|
|
// when the user is swiping. To ensure that the animations are enabled
|
|
// *before* the positions of these delegates change (via the swipe.setPosition() calls below),
|
|
// we must cancel the press. QQuickAbstractButton::mouseUngrabEvent() does this
|
|
// for us, but by then it's too late.
|
|
if (hadGrabbedMouse) {
|
|
// TODO: this is copied from QQuickAbstractButton::mouseUngrabEvent().
|
|
// Eventually it should be moved into a private helper so that we don't have to duplicate it.
|
|
q->setPressed(false);
|
|
stopPressRepeat();
|
|
stopPressAndHold();
|
|
emit q->canceled();
|
|
}
|
|
|
|
// Inform the given item that the mouse is released, in case it's an interactive child.
|
|
if (item != q && (swipePrivate->complete || swipePrivate->wasComplete))
|
|
forwardMouseEvent(event, item, item->mapFromScene(event->scenePosition()));
|
|
|
|
// The control can be exposed by either swiping past the halfway mark, or swiping fast enough.
|
|
const qreal swipeVelocity = swipePrivate->velocityCalculator.velocity().x();
|
|
if (swipePrivate->position > 0.5 ||
|
|
(swipePrivate->position > 0.0 && swipeVelocity > exposeVelocityThreshold)) {
|
|
swipePrivate->beginTransition(1.0);
|
|
swipePrivate->wasComplete = true;
|
|
} else if (swipePrivate->position < -0.5 ||
|
|
(swipePrivate->position < 0.0 && swipeVelocity < -exposeVelocityThreshold)) {
|
|
swipePrivate->beginTransition(-1.0);
|
|
swipePrivate->wasComplete = true;
|
|
} else if (!swipePrivate->isTransitioning()) {
|
|
// The position is either <= 0.5 or >= -0.5, so the position should go to 0.
|
|
// However, if the position was already 0 or close to it, we were just clicked,
|
|
// and we don't need to start a transition.
|
|
if (!qFuzzyIsNull(swipePrivate->position))
|
|
swipePrivate->beginTransition(0.0);
|
|
swipePrivate->wasComplete = false;
|
|
}
|
|
|
|
// Inform any QQuickSwipeDelegateAttached objects that the mouse is released.
|
|
attachedObjectsSetPressed(item, event->scenePosition(), false);
|
|
|
|
// Only consume child events if we had grabbed the mouse.
|
|
return hadGrabbedMouse;
|
|
}
|
|
|
|
/*! \internal
|
|
Send a localized copy of \a event with \a localPos to the \a destination item.
|
|
*/
|
|
void QQuickSwipeDelegatePrivate::forwardMouseEvent(QMouseEvent *event, QQuickItem *destination, QPointF localPos)
|
|
{
|
|
Q_Q(QQuickSwipeDelegate);
|
|
QMutableSinglePointEvent localizedEvent(*event);
|
|
QMutableEventPoint::setPosition(localizedEvent.point(0), localPos);
|
|
QGuiApplication::sendEvent(destination, &localizedEvent);
|
|
q->setPressed(!localizedEvent.isAccepted());
|
|
}
|
|
|
|
/*! \internal
|
|
For each QQuickSwipeDelegateAttached object on children of \a item:
|
|
if \a scenePos is in the attachee (the item to which it's attached), then
|
|
set its \a pressed state. Unless \a cancel is \c true, when the state
|
|
transitions from pressed to released, also emit \l QQuickSwipeDelegateAttached::clicked().
|
|
Returns \c true if at least one relevant attached object was found.
|
|
*/
|
|
bool QQuickSwipeDelegatePrivate::attachedObjectsSetPressed(QQuickItem *item, QPointF scenePos, bool pressed, bool cancel)
|
|
{
|
|
bool found = false;
|
|
QVarLengthArray<QQuickItem *, 16> itemAndChildren;
|
|
itemAndChildren.append(item);
|
|
for (int i = 0; i < itemAndChildren.size(); ++i) {
|
|
auto item = itemAndChildren.at(i);
|
|
auto posInItem = item->mapFromScene(scenePos);
|
|
if (item->contains(posInItem)) {
|
|
if (Attached *attached = attachedObject(item)) {
|
|
const bool wasPressed = attached->isPressed();
|
|
attached->setPressed(pressed);
|
|
if (wasPressed && !pressed && !cancel)
|
|
emit attached->clicked();
|
|
found = true;
|
|
}
|
|
}
|
|
for (auto child : item->childItems())
|
|
itemAndChildren.append(child);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static void warnIfHorizontallyAnchored(QQuickItem *item, const QString &itemName)
|
|
{
|
|
if (!item)
|
|
return;
|
|
|
|
QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors;
|
|
if (anchors && (anchors->fill() || anchors->centerIn() || anchors->left().item || anchors->right().item)
|
|
&& !item->property("_q_QQuickSwipeDelegate_warned").toBool()) {
|
|
qmlWarning(item) << QString::fromLatin1("SwipeDelegate: cannot use horizontal anchors with %1; unable to layout the item.").arg(itemName);
|
|
item->setProperty("_q_QQuickSwipeDelegate_warned", true);
|
|
}
|
|
}
|
|
|
|
void QQuickSwipeDelegatePrivate::resizeContent()
|
|
{
|
|
warnIfHorizontallyAnchored(background, QStringLiteral("background"));
|
|
warnIfHorizontallyAnchored(contentItem, QStringLiteral("contentItem"));
|
|
|
|
// If the background and contentItem are repositioned due to a swipe,
|
|
// we don't want to call QQuickControlPrivate's implementation of this function,
|
|
// as it repositions the contentItem to be visible.
|
|
// However, we still want to position the contentItem vertically
|
|
// and resize it (in case the control was resized while open).
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
|
|
if (!swipePrivate->complete) {
|
|
QQuickItemDelegatePrivate::resizeContent();
|
|
} else if (contentItem) {
|
|
Q_Q(QQuickSwipeDelegate);
|
|
contentItem->setY(q->topPadding());
|
|
contentItem->setWidth(q->availableWidth());
|
|
contentItem->setHeight(q->availableHeight());
|
|
}
|
|
}
|
|
|
|
QPalette QQuickSwipeDelegatePrivate::defaultPalette() const
|
|
{
|
|
return QQuickTheme::palette(QQuickTheme::ListView);
|
|
}
|
|
|
|
/*! \internal
|
|
Recursively search right and/or left item tree of swipe delegate for any item that
|
|
contains the \a event position.
|
|
|
|
Returns the first such item found, otherwise \c nullptr.
|
|
*/
|
|
QQuickItem *QQuickSwipeDelegatePrivate::getPressedItem(QQuickItem *childItem, QMouseEvent *event) const
|
|
{
|
|
if (!childItem || !event)
|
|
return nullptr;
|
|
|
|
QQuickItem *item = nullptr;
|
|
|
|
if (childItem->acceptedMouseButtons() &&
|
|
childItem->contains(childItem->mapFromScene(event->scenePosition()))) {
|
|
item = childItem;
|
|
} else {
|
|
const auto &childItems = childItem->childItems();
|
|
for (const auto &child: childItems) {
|
|
if ((item = getPressedItem(child, event)))
|
|
break;
|
|
}
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
QQuickSwipeDelegate::QQuickSwipeDelegate(QQuickItem *parent)
|
|
: QQuickItemDelegate(*(new QQuickSwipeDelegatePrivate(this)), parent)
|
|
{
|
|
// QQuickSwipeDelegate still depends on synthesized mouse events
|
|
setAcceptTouchEvents(false);
|
|
}
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.2 (Qt 5.9)
|
|
\qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.open(enumeration side)
|
|
|
|
This method sets the \c position of the swipe so that it opens
|
|
from the specified \a side.
|
|
|
|
Available values:
|
|
\value SwipeDelegate.Left The \c position is set to \c 1, which makes the swipe open
|
|
from the left. Either \c swipe.left or \c swipe.behind must
|
|
have been specified; otherwise the call is ignored.
|
|
\value SwipeDelegate.Right The \c position is set to \c -1, which makes the swipe open
|
|
from the right. Either \c swipe.right or \c swipe.behind must
|
|
have been specified; otherwise the call is ignored.
|
|
|
|
Any animations defined for the \l {Item::}{x} position of \l {Control::}{contentItem}
|
|
and \l {Control::}{background} will be triggered.
|
|
|
|
\sa swipe, swipe.close()
|
|
*/
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.1 (Qt 5.8)
|
|
\qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.close()
|
|
|
|
This method sets the \c position of the swipe to \c 0. Any animations
|
|
defined for the \l {Item::}{x} position of \l {Control::}{contentItem}
|
|
and \l {Control::}{background} will be triggered.
|
|
|
|
\sa swipe, swipe.open()
|
|
*/
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.2 (Qt 5.9)
|
|
\qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.opened()
|
|
|
|
This signal is emitted when the delegate has been swiped open
|
|
and the transition has finished.
|
|
|
|
It is useful for performing some action upon completion of a swipe.
|
|
For example, it can be used to remove the delegate from the list
|
|
that it is in.
|
|
|
|
\sa swipe, swipe.closed()
|
|
*/
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.2 (Qt 5.9)
|
|
\qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.closed()
|
|
|
|
This signal is emitted when the delegate has been swiped to closed
|
|
and the transition has finished.
|
|
|
|
It is useful for performing some action upon cancellation of a swipe.
|
|
For example, it can be used to cancel the removal of the delegate from
|
|
the list that it is in.
|
|
|
|
\sa swipe, swipe.opened()
|
|
*/
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.1 (Qt 5.8)
|
|
\qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.completed()
|
|
|
|
This signal is emitted when \c swipe.complete becomes \c true.
|
|
|
|
It is useful for performing some action upon completion of a swipe.
|
|
For example, it can be used to remove the delegate from the list
|
|
that it is in.
|
|
|
|
\sa swipe
|
|
*/
|
|
|
|
/*!
|
|
\qmlproperty real QtQuick.Controls::SwipeDelegate::swipe.position
|
|
\qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.complete
|
|
\qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.enabled
|
|
\qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.left
|
|
\qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.behind
|
|
\qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.right
|
|
\qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.leftItem
|
|
\qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.behindItem
|
|
\qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.rightItem
|
|
\qmlproperty Transition QtQuick.Controls::SwipeDelegate::swipe.transition
|
|
|
|
\table
|
|
\header
|
|
\li Name
|
|
\li Description
|
|
\row
|
|
\li position
|
|
\li This read-only property holds the position of the swipe relative to either
|
|
side of the control. When this value reaches either
|
|
\c -1.0 (left side) or \c 1.0 (right side) and the mouse button is
|
|
released, \c complete will be \c true.
|
|
\row
|
|
\li complete
|
|
\li This read-only property holds whether the control is fully exposed after
|
|
having been swiped to the left or right.
|
|
|
|
When complete is \c true, any interactive items declared in \c left,
|
|
\c right, or \c behind will receive mouse events.
|
|
\row
|
|
\li enabled
|
|
\li This property determines whether or not the control can be swiped.
|
|
|
|
This property was added in \l{QtQuick.Controls} 2.2.
|
|
\row
|
|
\li left
|
|
\li This property holds the left delegate.
|
|
|
|
The left delegate sits behind both \l {Control::}{contentItem} and
|
|
\l {Control::}{background}. When the SwipeDelegate is swiped to the right,
|
|
this item will be gradually revealed.
|
|
|
|
\include qquickswipedelegate-interaction.qdocinc
|
|
\row
|
|
\li behind
|
|
\li This property holds the delegate that is shown when the
|
|
SwipeDelegate is swiped to both the left and right.
|
|
|
|
As with the \c left and \c right delegates, it sits behind both
|
|
\l {Control::}{contentItem} and \l {Control::}{background}. However, a
|
|
SwipeDelegate whose \c behind has been set can be continuously swiped
|
|
from either side, and will always show the same item.
|
|
|
|
\include qquickswipedelegate-interaction.qdocinc
|
|
\row
|
|
\li right
|
|
\li This property holds the right delegate.
|
|
|
|
The right delegate sits behind both \l {Control::}{contentItem} and
|
|
\l {Control::}{background}. When the SwipeDelegate is swiped to the left,
|
|
this item will be gradually revealed.
|
|
|
|
\include qquickswipedelegate-interaction.qdocinc
|
|
\row
|
|
\li leftItem
|
|
\li This read-only property holds the item instantiated from the \c left component.
|
|
|
|
If \c left has not been set, or the position hasn't changed since
|
|
creation of the SwipeDelegate, this property will be \c null.
|
|
\row
|
|
\li behindItem
|
|
\li This read-only property holds the item instantiated from the \c behind component.
|
|
|
|
If \c behind has not been set, or the position hasn't changed since
|
|
creation of the SwipeDelegate, this property will be \c null.
|
|
\row
|
|
\li rightItem
|
|
\li This read-only property holds the item instantiated from the \c right component.
|
|
|
|
If \c right has not been set, or the position hasn't changed since
|
|
creation of the SwipeDelegate, this property will be \c null.
|
|
\row
|
|
\li transition
|
|
\li This property holds the transition that is applied when a swipe is released,
|
|
or \l swipe.open() or \l swipe.close() is called.
|
|
|
|
\snippet qtquickcontrols-swipedelegate-transition.qml 1
|
|
|
|
This property was added in \l{QtQuick.Controls} 2.2.
|
|
\endtable
|
|
|
|
\sa {Control::}{contentItem}, {Control::}{background}, swipe.open(), swipe.close()
|
|
*/
|
|
QQuickSwipe *QQuickSwipeDelegate::swipe() const
|
|
{
|
|
Q_D(const QQuickSwipeDelegate);
|
|
return const_cast<QQuickSwipe*>(&d->swipe);
|
|
}
|
|
|
|
QQuickSwipeDelegateAttached *QQuickSwipeDelegate::qmlAttachedProperties(QObject *object)
|
|
{
|
|
return new QQuickSwipeDelegateAttached(object);
|
|
}
|
|
|
|
static bool isChildOrGrandchildOf(QQuickItem *child, QQuickItem *item)
|
|
{
|
|
return item && (child == item || item->isAncestorOf(child));
|
|
}
|
|
|
|
bool QQuickSwipeDelegate::childMouseEventFilter(QQuickItem *child, QEvent *event)
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
// The contentItem is, by default, usually a non-interactive item like Text, and
|
|
// the same applies to the background. This means that simply stacking the left/right/behind
|
|
// items before these items won't allow us to get mouse events when the control is not currently exposed
|
|
// but has been previously. Therefore, we instead call setFiltersChildMouseEvents(true) in the constructor
|
|
// and filter out child events only when the child is the left/right/behind item.
|
|
const QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
|
|
if (!isChildOrGrandchildOf(child, swipePrivate->leftItem) && !isChildOrGrandchildOf(child, swipePrivate->behindItem)
|
|
&& !isChildOrGrandchildOf(child, swipePrivate->rightItem)) {
|
|
return false;
|
|
}
|
|
|
|
switch (event->type()) {
|
|
case QEvent::MouseButtonPress: {
|
|
return d->handleMousePressEvent(child, static_cast<QMouseEvent *>(event));
|
|
} case QEvent::MouseMove: {
|
|
return d->handleMouseMoveEvent(child, static_cast<QMouseEvent *>(event));
|
|
} case QEvent::MouseButtonRelease: {
|
|
// Make sure that the control gets release events if it has created child
|
|
// items that are stealing events from it.
|
|
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
|
|
QQuickItemDelegate::mouseReleaseEvent(mouseEvent);
|
|
return d->handleMouseReleaseEvent(child, mouseEvent);
|
|
} case QEvent::UngrabMouse: {
|
|
// If the mouse was pressed over e.g. rightItem and then dragged down,
|
|
// the ListView would eventually grab the mouse, at which point we must
|
|
// clear the pressed flag so that it doesn't stay pressed after the release.
|
|
Attached *attached = attachedObject(child);
|
|
if (attached)
|
|
attached->setPressed(false);
|
|
return false;
|
|
} default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We only override this to set positionBeforePress;
|
|
// otherwise, it's the same as the base class implementation.
|
|
void QQuickSwipeDelegate::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
QQuickItemDelegate::mousePressEvent(event);
|
|
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
|
|
if (!swipePrivate->enabled)
|
|
return;
|
|
|
|
swipePrivate->positionBeforePress = swipePrivate->position;
|
|
swipePrivate->velocityCalculator.startMeasuring(event->position().toPoint(), event->timestamp());
|
|
|
|
if (swipePrivate->complete) {
|
|
d->pressedItem = d->getPressedItem(d->swipe.rightItem(), event);
|
|
if (!d->pressedItem)
|
|
d->pressedItem = d->getPressedItem(d->swipe.leftItem(), event);
|
|
if (d->pressedItem)
|
|
d->handleMousePressEvent(d->pressedItem, event);
|
|
}
|
|
}
|
|
|
|
void QQuickSwipeDelegate::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
if (filtersChildMouseEvents())
|
|
d->handleMouseMoveEvent(this, event);
|
|
else
|
|
QQuickItemDelegate::mouseMoveEvent(event);
|
|
if (d->pressedItem)
|
|
d->handleMouseMoveEvent(d->pressedItem, event);
|
|
}
|
|
|
|
void QQuickSwipeDelegate::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
if (!filtersChildMouseEvents() || !d->handleMouseReleaseEvent(this, event))
|
|
QQuickItemDelegate::mouseReleaseEvent(event);
|
|
|
|
if (d->pressedItem) {
|
|
if (d->pressedItem->acceptedMouseButtons())
|
|
d->handleMouseReleaseEvent(d->pressedItem, event);
|
|
d->pressedItem = nullptr;
|
|
}
|
|
}
|
|
|
|
void QQuickSwipeDelegate::mouseUngrabEvent()
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
setPressed(false);
|
|
|
|
auto item = d->swipe.rightItem();
|
|
if (item) {
|
|
if (auto control = qmlobject_cast<QQuickControl *>(item))
|
|
QQuickControlPrivate::get(control)->handleUngrab();
|
|
Attached *attached = attachedObject(item);
|
|
if (attached)
|
|
attached->setPressed(false);
|
|
} else {
|
|
item = d->swipe.leftItem();
|
|
if (item) {
|
|
if (auto control = qmlobject_cast<QQuickControl *>(item))
|
|
QQuickControlPrivate::get(control)->handleUngrab();
|
|
Attached *attached = attachedObject(item);
|
|
if (attached)
|
|
attached->setPressed(false);
|
|
}
|
|
}
|
|
|
|
d->pressedItem = nullptr;
|
|
}
|
|
|
|
void QQuickSwipeDelegate::touchEvent(QTouchEvent *event)
|
|
{
|
|
// Don't allow QQuickControl accept the touch event, because QQuickSwipeDelegate
|
|
// is still based on synthesized mouse events
|
|
event->ignore();
|
|
}
|
|
|
|
void QQuickSwipeDelegate::componentComplete()
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
QQuickItemDelegate::componentComplete();
|
|
QQuickSwipePrivate::get(&d->swipe)->reposition(DontAnimatePosition);
|
|
}
|
|
|
|
void QQuickSwipeDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
|
{
|
|
Q_D(QQuickSwipeDelegate);
|
|
QQuickControl::geometryChange(newGeometry, oldGeometry);
|
|
|
|
if (isComponentComplete() && !qFuzzyCompare(newGeometry.width(), oldGeometry.width())) {
|
|
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe);
|
|
swipePrivate->reposition(DontAnimatePosition);
|
|
}
|
|
}
|
|
|
|
QFont QQuickSwipeDelegate::defaultFont() const
|
|
{
|
|
return QQuickTheme::font(QQuickTheme::ListView);
|
|
}
|
|
|
|
#if QT_CONFIG(accessibility)
|
|
QAccessible::Role QQuickSwipeDelegate::accessibleRole() const
|
|
{
|
|
return QAccessible::ListItem;
|
|
}
|
|
#endif
|
|
|
|
class QQuickSwipeDelegateAttachedPrivate : public QObjectPrivate
|
|
{
|
|
Q_DECLARE_PUBLIC(QQuickSwipeDelegateAttached)
|
|
|
|
public:
|
|
// True when left/right/behind is non-interactive and is pressed.
|
|
bool pressed = false;
|
|
};
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.1 (Qt 5.8)
|
|
\qmlattachedsignal QtQuick.Controls::SwipeDelegate::clicked()
|
|
|
|
This signal can be attached to a non-interactive item declared in
|
|
\c swipe.left, \c swipe.right, or \c swipe.behind, in order to react to
|
|
clicks. Items can only be clicked when \c swipe.complete is \c true.
|
|
|
|
For interactive controls (such as \l Button) declared in these
|
|
items, use their respective \c clicked() signal instead.
|
|
|
|
To respond to clicks on the SwipeDelegate itself, use its
|
|
\l {AbstractButton::}{clicked()} signal.
|
|
|
|
\note See the documentation for \l pressed for information on
|
|
how to use the event-related properties correctly.
|
|
|
|
\sa pressed
|
|
*/
|
|
|
|
QQuickSwipeDelegateAttached::QQuickSwipeDelegateAttached(QObject *object)
|
|
: QObject(*(new QQuickSwipeDelegateAttachedPrivate), object)
|
|
{
|
|
QQuickItem *item = qobject_cast<QQuickItem *>(object);
|
|
if (item) {
|
|
// This allows us to be notified when an otherwise non-interactive item
|
|
// is pressed and clicked. The alternative is much more more complex:
|
|
// iterating through children that contain the event pos and finding
|
|
// the first one with an attached object.
|
|
item->setAcceptedMouseButtons(Qt::AllButtons);
|
|
} else {
|
|
qWarning() << "SwipeDelegate attached property must be attached to an object deriving from Item";
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\since QtQuick.Controls 2.1 (Qt 5.8)
|
|
\qmlattachedproperty bool QtQuick.Controls::SwipeDelegate::pressed
|
|
\readonly
|
|
|
|
This property can be attached to a non-interactive item declared in
|
|
\c swipe.left, \c swipe.right, or \c swipe.behind, in order to detect if it
|
|
is pressed. Items can only be pressed when \c swipe.complete is \c true.
|
|
|
|
For example:
|
|
|
|
\code
|
|
swipe.right: Label {
|
|
anchors.right: parent.right
|
|
height: parent.height
|
|
text: "Action"
|
|
color: "white"
|
|
padding: 12
|
|
background: Rectangle {
|
|
color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
It is possible to have multiple items which individually receive mouse and
|
|
touch events. For example, to have two actions in the \c swipe.right item,
|
|
use the following code:
|
|
|
|
\code
|
|
swipe.right: Row {
|
|
anchors.right: parent.right
|
|
height: parent.height
|
|
|
|
Label {
|
|
id: moveLabel
|
|
text: qsTr("Move")
|
|
color: "white"
|
|
verticalAlignment: Label.AlignVCenter
|
|
padding: 12
|
|
height: parent.height
|
|
|
|
SwipeDelegate.onClicked: console.log("Moving...")
|
|
|
|
background: Rectangle {
|
|
color: moveLabel.SwipeDelegate.pressed ? Qt.darker("#ffbf47", 1.1) : "#ffbf47"
|
|
}
|
|
}
|
|
Label {
|
|
id: deleteLabel
|
|
text: qsTr("Delete")
|
|
color: "white"
|
|
verticalAlignment: Label.AlignVCenter
|
|
padding: 12
|
|
height: parent.height
|
|
|
|
SwipeDelegate.onClicked: console.log("Deleting...")
|
|
|
|
background: Rectangle {
|
|
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
|
|
}
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
Note how the \c color assignment in each \l {Control::}{background} item
|
|
qualifies the attached property with the \c id of the label. This
|
|
is important; using the attached properties on an item causes that item
|
|
to accept events. Suppose we had left out the \c id in the previous example:
|
|
|
|
\code
|
|
color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
|
|
\endcode
|
|
|
|
The \l Rectangle background item is a child of the label, so it naturally
|
|
receives events before it. In practice, this means that the background
|
|
color will change, but the \c onClicked handler in the label will never
|
|
get called.
|
|
|
|
For interactive controls (such as \l Button) declared in these
|
|
items, use their respective \c pressed property instead.
|
|
|
|
For presses on the SwipeDelegate itself, use its
|
|
\l {AbstractButton::}{pressed} property.
|
|
|
|
\sa clicked()
|
|
*/
|
|
bool QQuickSwipeDelegateAttached::isPressed() const
|
|
{
|
|
Q_D(const QQuickSwipeDelegateAttached);
|
|
return d->pressed;
|
|
}
|
|
|
|
void QQuickSwipeDelegateAttached::setPressed(bool pressed)
|
|
{
|
|
Q_D(QQuickSwipeDelegateAttached);
|
|
if (pressed == d->pressed)
|
|
return;
|
|
|
|
d->pressed = pressed;
|
|
emit pressedChanged();
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qquickswipe_p.cpp"
|
|
#include "moc_qquickswipedelegate_p.cpp"
|