diff options
author | J-P Nurmi <jpnurmi@theqtcompany.com> | 2015-03-28 21:51:10 +0100 |
---|---|---|
committer | J-P Nurmi <jpnurmi@theqtcompany.com> | 2015-04-09 21:54:23 +0000 |
commit | 1315f9d97170d7f1bb8ef28ab95e0fafc3e72b68 (patch) | |
tree | d7d7b58282ecdd07da4b6ef5305e1d091c623c05 | |
parent | b898b8c2317cd2c7349a60eba0c9bfea395e614c (diff) |
Implement StackView in C++
Change-Id: Ia5387aa16325453c676a2542f80c827d4c069ca9
Reviewed-by: J-P Nurmi <jpnurmi@theqtcompany.com>
-rw-r--r-- | src/controls/controls.pri | 2 | ||||
-rw-r--r-- | src/controls/qquickabstractstackview.cpp | 565 | ||||
-rw-r--r-- | src/controls/qquickabstractstackview_p.cpp | 507 | ||||
-rw-r--r-- | src/controls/qquickabstractstackview_p.h | 84 | ||||
-rw-r--r-- | src/controls/qquickabstractstackview_p_p.h | 158 | ||||
-rw-r--r-- | src/imports/controls/StackView.js | 63 | ||||
-rw-r--r-- | src/imports/controls/StackView.qml | 914 | ||||
-rw-r--r-- | src/imports/controls/StackViewDelegate.qml | 142 | ||||
-rw-r--r-- | src/imports/controls/StackViewTransition.qml | 56 | ||||
-rw-r--r-- | src/imports/controls/controls.pro | 3 | ||||
-rw-r--r-- | src/imports/controls/qmldir | 2 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_stackview.qml | 230 | ||||
-rw-r--r-- | tests/benchmarks/creationtime/tst_creationtime.cpp | 1 |
13 files changed, 1282 insertions, 1445 deletions
diff --git a/src/controls/controls.pri b/src/controls/controls.pri index b1d2a0ea..aea9b06f 100644 --- a/src/controls/controls.pri +++ b/src/controls/controls.pri @@ -20,6 +20,7 @@ HEADERS += \ $$PWD/qquickabstractscrollindicator_p.h \ $$PWD/qquickabstractslider_p.h \ $$PWD/qquickabstractstackview_p.h \ + $$PWD/qquickabstractstackview_p_p.h \ $$PWD/qquickabstractswitch_p.h \ $$PWD/qquickabstracttabbar_p.h \ $$PWD/qquickabstracttabbutton_p.h \ @@ -49,6 +50,7 @@ SOURCES += \ $$PWD/qquickabstractscrollindicator.cpp \ $$PWD/qquickabstractslider.cpp \ $$PWD/qquickabstractstackview.cpp \ + $$PWD/qquickabstractstackview_p.cpp \ $$PWD/qquickabstractswitch.cpp \ $$PWD/qquickabstracttabbar.cpp \ $$PWD/qquickabstracttabbutton.cpp \ diff --git a/src/controls/qquickabstractstackview.cpp b/src/controls/qquickabstractstackview.cpp index a849bd94..e5496697 100644 --- a/src/controls/qquickabstractstackview.cpp +++ b/src/controls/qquickabstractstackview.cpp @@ -35,7 +35,10 @@ ****************************************************************************/ #include "qquickabstractstackview_p.h" -#include "qquickabstractcontainer_p_p.h" +#include "qquickabstractstackview_p_p.h" + +#include <QtQml/qjsvalue.h> +#include <QtQml/qqmlengine.h> QT_BEGIN_NAMESPACE @@ -50,243 +53,407 @@ QT_BEGIN_NAMESPACE TODO */ -class QQuickStackElement +QQuickAbstractStackView::QQuickAbstractStackView(QQuickItem *parent) : + QQuickAbstractContainer(*(new QQuickAbstractStackViewPrivate), parent) { - QQuickStackElement() : ownItem(false), item(Q_NULLPTR), ownComponent(false), component(Q_NULLPTR) { } - -public: - static QQuickStackElement *fromString(const QString &str, QQmlEngine *engine, QObject *parent) - { - QQuickStackElement *element = new QQuickStackElement; - element->component = new QQmlComponent(engine, QUrl(str), parent); - element->ownComponent = true; - return element; - } + setFlag(ItemIsFocusScope); +} - static QQuickStackElement *fromObject(QObject *object) - { - QQuickStackElement *element = new QQuickStackElement; - element->component = qobject_cast<QQmlComponent *>(object); - element->item = qobject_cast<QQuickItem *>(object); - return element; +QQuickAbstractStackView::~QQuickAbstractStackView() +{ + Q_D(QQuickAbstractStackView); + if (d->transitioner) { + d->transitioner->setChangeListener(Q_NULLPTR); + delete d->transitioner; } + qDeleteAll(d->elements); +} - bool ownItem; - QQuickItem *item; - - bool ownComponent; - QQmlComponent *component; -}; +QQuickStackAttached *QQuickAbstractStackView::qmlAttachedProperties(QObject *object) +{ + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!item) { + qmlInfo(object) << "StackView must be attached to an Item"; + return Q_NULLPTR; + } + return new QQuickStackAttached(item); +} -class QQuickAbstractStackViewPrivate : public QQuickAbstractContainerPrivate +/*! + \qmlproperty bool QtQuickControls2::StackView::busy + \readonly + This property holds whether a transition is running. +*/ +bool QQuickAbstractStackView::busy() const { - Q_DECLARE_PUBLIC(QQuickAbstractStackView) + Q_D(const QQuickAbstractStackView); + return d->transitioner && !d->transitioner->runningJobs.isEmpty(); +} -public: - QQuickAbstractStackViewPrivate() : busy(false), depth(0), currentItem(Q_NULLPTR) { } +/*! + \qmlproperty int QtQuickControls2::StackView::depth + \readonly + This property holds the number of items currently pushed onto the stack. +*/ +int QQuickAbstractStackView::depth() const +{ + Q_D(const QQuickAbstractStackView); + return d->elements.count(); +} - void setBusy(bool busy); - void setDepth(int depth); - void setCurrentItem(QQuickItem *item); +/*! + \qmlproperty Item QtQuickControls2::StackView::currentItem + \readonly + This property holds the current top-most item in the stack. +*/ +QQuickItem *QQuickAbstractStackView::currentItem() const +{ + Q_D(const QQuickAbstractStackView); + return d->currentItem; +} - QList<QQuickStackElement *> createElements(const QV4::ScopedValue &value); - QQuickItem *pushElements(const QList<QQuickStackElement *> &elements, QQuickAbstractStackView::Operation operation); +/*! + \qmlmethod Item QtQuickControls2::StackView::get(index, behavior = DontLoad) - bool busy; - int depth; - QVariant initialItem; - QQuickItem *currentItem; - QStack<QQuickStackElement *> elements; -}; + Supported behavior values: + \li StackView.DontLoad + \li StackView.ForceLoad -void QQuickAbstractStackViewPrivate::setBusy(bool value) + TODO +*/ +QQuickItem *QQuickAbstractStackView::get(int index, LoadBehavior behavior) { - Q_Q(QQuickAbstractStackView); - if (busy != value) { - busy = value; - emit q->busyChanged(); + Q_D(QQuickAbstractStackView); + QQuickStackElement *element = d->elements.value(index); + if (element) { + if (behavior == ForceLoad) + element->load(this); + return element->item; } + return Q_NULLPTR; } -void QQuickAbstractStackViewPrivate::setDepth(int value) +/*! + \qmlmethod Item QtQuickControls2::StackView::find(callback, behavior = DontLoad) + + Supported behavior values: + \li StackView.DontLoad + \li StackView.ForceLoad + + TODO +*/ +QQuickItem *QQuickAbstractStackView::find(const QJSValue &callback, LoadBehavior behavior) { - Q_Q(QQuickAbstractStackView); - if (depth != value) { - depth = value; - emit q->depthChanged(); + Q_D(QQuickAbstractStackView); + QJSValue func(callback); + QQmlEngine *engine = qmlEngine(this); + if (!engine || !func.isCallable()) // TODO: warning? + return Q_NULLPTR; + + for (int i = d->elements.count() - 1; i >= 0; --i) { + QQuickStackElement *element = d->elements.at(i); + if (behavior == ForceLoad) + element->load(this); + if (element->item) { + QJSValue rv = func.call(QJSValueList() << engine->newQObject(element->item) << i); + if (rv.toBool()) + return element->item; + } } + + return Q_NULLPTR; } -void QQuickAbstractStackViewPrivate::setCurrentItem(QQuickItem *item) +/*! + \qmlmethod Item QtQuickControls2::StackView::push(item, properties, operation) + + TODO +*/ +void QQuickAbstractStackView::push(QQmlV4Function *args) { - Q_Q(QQuickAbstractStackView); - if (currentItem != item) { - currentItem = item; - emit q->currentItemChanged(); + Q_D(QQuickAbstractStackView); + if (args->length() <= 0) { + qmlInfo(this) << "push: missing arguments"; + args->setReturnValue(QV4::Encode::null()); + return; } -} -QList<QQuickStackElement *> QQuickAbstractStackViewPrivate::createElements(const QV4::ScopedValue &value) -{ - Q_Q(QQuickAbstractStackView); - QList<QQuickStackElement *> elements; - if (QV4::String *s = value->asString()) { - qDebug() << "### STRING:" << s->toQString(); - elements += QQuickStackElement::fromString(s->toQString(), qmlEngine(q), q); - } else if (QV4::ArrayObject *a = value->asArrayObject()) { - int len = a->getLength(); - qDebug() << "### ARRAY:" << len; - for (int i = 0; i < len; ++i) { - QV4::Scope scope(a->engine()); - QV4::ScopedValue v(scope, a->getIndexed(i)); - elements += createElements(v); - } - } else if (const QV4::QObjectWrapper *o =value->as<QV4::QObjectWrapper>()) { - qDebug() << "### QOBJECT:" << o->object(); - elements += QQuickStackElement::fromObject(o->object()); + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : Transition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QList<QQuickStackElement *> elements = d->parseElements(args); + if (elements.isEmpty()) { + qmlInfo(this) << "push: nothing to push"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QQuickStackElement *exit = Q_NULLPTR; + if (!d->elements.isEmpty()) + exit = d->elements.top(); + + if (d->pushElements(elements)) { + emit depthChanged(); + QQuickStackElement *enter = d->elements.top(); + d->pushTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + if (d->currentItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem)); + args->setReturnValue(rv->asReturnedValue()); } else { - qDebug("### UNKNOWN"); + args->setReturnValue(QV4::Encode::null()); } - return elements; } -QQuickItem *QQuickAbstractStackViewPrivate::pushElements(const QList<QQuickStackElement *> &elems, QQuickAbstractStackView::Operation operation) +/*! + \qmlmethod Item QtQuickControls2::StackView::pop(item = null, operation = Transition) + + TODO +*/ +void QQuickAbstractStackView::pop(QQmlV4Function *args) { - Q_Q(QQuickAbstractStackView); - Q_UNUSED(operation); // TODO - if (!elems.isEmpty()) { - foreach (QQuickStackElement *elem, elems) { - elements.push(elem); + Q_D(QQuickAbstractStackView); + int argc = args->length(); + if (d->elements.count() <= 1 || argc > 2) { + if (argc > 2) + qmlInfo(this) << "pop: too many arguments"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QQuickStackElement *exit = d->elements.pop(); + QQuickStackElement *enter = d->elements.top(); + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + if (argc > 0) { + QV4::ScopedValue value(scope, (*args)[0]); + if (value->isNull()) { + enter = d->elements.value(0); + } else if (!value->isUndefined() && !value->isInt32()) { + enter = d->findElement(value); + if (!enter) { + qmlInfo(this) << "pop: unknown argument: " << value->toQString(); // TODO: safe? + args->setReturnValue(QV4::Encode::null()); + d->elements.push(exit); // restore + return; + } } - emit q->depthChanged(); - // TODO: load - return elems.last()->item; } - return Q_NULLPTR; + + Operation operation = Transition; + if (argc > 0) { + QV4::ScopedValue lastArg(scope, (*args)[argc - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + } + + QQuickItem *previousItem = Q_NULLPTR; + + if (d->popElements(enter)) { + if (exit) + previousItem = exit->item; + emit depthChanged(); + d->popTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + if (previousItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, previousItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } } -QQuickAbstractStackView::QQuickAbstractStackView(QQuickItem *parent) : - QQuickAbstractContainer(*(new QQuickAbstractStackViewPrivate), parent) +/*! + \qmlmethod Item QtQuickControls2::StackView::push(item, properties, operation = Transition) + + TODO +*/ +void QQuickAbstractStackView::replace(QQmlV4Function *args) { - setFlag(ItemIsFocusScope); + Q_D(QQuickAbstractStackView); + if (args->length() <= 0) { + qmlInfo(this) << "replace: missing arguments"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : Transition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QQuickStackElement *target = Q_NULLPTR; + QV4::ScopedValue firstArg(scope, (*args)[0]); + if (firstArg->isNull()) + target = d->elements.value(0); + else if (!firstArg->isInt32()) + target = d->findElement(firstArg); + + QList<QQuickStackElement *> elements = d->parseElements(args, target ? 1 : 0); + if (elements.isEmpty()) { + qmlInfo(this) << "replace: nothing to push"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + int depth = d->elements.count(); + QQuickStackElement* exit = Q_NULLPTR; + if (!d->elements.isEmpty()) + exit = d->elements.pop(); + + if (d->replaceElements(target, elements)) { + if (depth != d->elements.count()) + emit depthChanged(); + QQuickStackElement *enter = d->elements.top(); + d->replaceTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + if (d->currentItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } } -QQuickAbstractStackView::~QQuickAbstractStackView() +/*! + \qmlmethod Item QtQuickControls2::StackView::clear() + + TODO +*/ +void QQuickAbstractStackView::clear() { Q_D(QQuickAbstractStackView); + d->setCurrentItem(Q_NULLPTR); qDeleteAll(d->elements); + d->elements.clear(); + emit depthChanged(); } /*! - \qmlproperty bool QtQuickControls2::StackView::busy - \readonly - \c true if a transition is running, and \c false otherwise. + \qmlproperty var QtQuickControls2::StackView::initialItem + + This property holds the initial item. + + \sa push() */ -bool QQuickAbstractStackView::busy() const +QVariant QQuickAbstractStackView::initialItem() const { Q_D(const QQuickAbstractStackView); - return d->busy; + return d->initialItem; } -// TODO: remove -void QQuickAbstractStackView::setBusy(bool busy) +void QQuickAbstractStackView::setInitialItem(const QVariant &item) { Q_D(QQuickAbstractStackView); - d->setBusy(busy); + d->initialItem = item; } /*! - \qmlproperty int QtQuickControls2::StackView::depth - \readonly - The number of items currently pushed onto the stack. + \qmlproperty Transition QtQuickControls2::StackView::popEnter + + TODO */ -int QQuickAbstractStackView::depth() const +QQuickTransition *QQuickAbstractStackView::popEnter() const { Q_D(const QQuickAbstractStackView); - return d->depth; + if (d->transitioner) + return d->transitioner->removeDisplacedTransition; + return Q_NULLPTR; } -// TODO: remove -void QQuickAbstractStackView::setDepth(int depth) +void QQuickAbstractStackView::setPopEnter(QQuickTransition *enter) { Q_D(QQuickAbstractStackView); - d->setDepth(depth); + d->ensureTransitioner(); + if (d->transitioner->removeDisplacedTransition != enter) { + d->transitioner->removeDisplacedTransition = enter; + emit popEnterChanged(); + } } /*! - \qmlproperty Item QtQuickControls2::StackView::currentItem - \readonly - The currently top-most item in the stack. + \qmlproperty Transition QtQuickControls2::StackView::popExit + + TODO */ -QQuickItem *QQuickAbstractStackView::currentItem() const +QQuickTransition *QQuickAbstractStackView::popExit() const { Q_D(const QQuickAbstractStackView); - return d->currentItem; -} - -// TODO: remove -void QQuickAbstractStackView::setCurrentItem(QQuickItem *item) -{ - Q_D(QQuickAbstractStackView); - d->setCurrentItem(item); + if (d->transitioner) + return d->transitioner->removeTransition; + return Q_NULLPTR; } -QQuickItem *QQuickAbstractStackView::qpush(QQmlV4Function *args) +void QQuickAbstractStackView::setPopExit(QQuickTransition *exit) { Q_D(QQuickAbstractStackView); - QV4::ExecutionEngine *v4 = args->v4engine(); - QV4::Scope scope(v4); - - Operation operation = d->elements.isEmpty() ? Immediate : Transition; - QList<QQuickStackElement *> elements; - for (int i = 0; i < args->length(); ++i) { - QV4::ScopedValue value(scope, (*args)[i]); - if (value->isInt32()) - operation = static_cast<Operation>(value->toInt32()); - else - elements += d->createElements(value); + d->ensureTransitioner(); + if (d->transitioner->removeTransition != exit) { + d->transitioner->removeTransition = exit; + emit popExitChanged(); } - return d->pushElements(elements, operation); } -QQuickItem *QQuickAbstractStackView::qpop(QQmlV4Function *args) +/*! + \qmlproperty Transition QtQuickControls2::StackView::pushEnter + + TODO +*/ +QQuickTransition *QQuickAbstractStackView::pushEnter() const { - Q_UNUSED(args); // TODO + Q_D(const QQuickAbstractStackView); + if (d->transitioner) + return d->transitioner->addTransition; return Q_NULLPTR; } -void QQuickAbstractStackView::qclear() +void QQuickAbstractStackView::setPushEnter(QQuickTransition *enter) { - // TODO + Q_D(QQuickAbstractStackView); + d->ensureTransitioner(); + if (d->transitioner->addTransition != enter) { + d->transitioner->addTransition = enter; + emit pushEnterChanged(); + } } /*! - \qmlproperty var QtQuickControls2::StackView::initialItem - - The first \l item that should be shown when the StackView is created. - \a initialItem can take same value as the first argument to \l{StackView::push()} - {StackView.push()}. Note that this is just a convenience for writing - \c{Component.onCompleted: stackView.push(myInitialItem)} - - Examples: + \qmlproperty Transition QtQuickControls2::StackView::pushExit - \list - \li initialItem: Qt.resolvedUrl("MyItem.qml") - \li initialItem: myItem - \li initialItem: {"item" : Qt.resolvedUrl("MyRectangle.qml"), "properties" : {"color" : "red"}} - \endlist - \sa push + TODO */ -QVariant QQuickAbstractStackView::initialItem() const +QQuickTransition *QQuickAbstractStackView::pushExit() const { Q_D(const QQuickAbstractStackView); - return d->initialItem; + if (d->transitioner) + return d->transitioner->addDisplacedTransition; + return Q_NULLPTR; } -void QQuickAbstractStackView::setInitialItem(const QVariant &item) +void QQuickAbstractStackView::setPushExit(QQuickTransition *exit) { Q_D(QQuickAbstractStackView); - d->initialItem = item; + d->ensureTransitioner(); + if (d->transitioner->addDisplacedTransition != exit) { + d->transitioner->addDisplacedTransition = exit; + emit pushExitChanged(); + } } void QQuickAbstractStackView::componentComplete() @@ -294,59 +461,93 @@ void QQuickAbstractStackView::componentComplete() QQuickAbstractContainer::componentComplete(); Q_D(QQuickAbstractStackView); + QQuickStackElement *element = Q_NULLPTR; if (QObject *o = d->initialItem.value<QObject *>()) - d->pushElements(QList<QQuickStackElement *>() << QQuickStackElement::fromObject(o), Immediate); + element = QQuickStackElement::fromObject(o, this); else if (d->initialItem.canConvert<QString>()) - d->pushElements(QList<QQuickStackElement *>() << QQuickStackElement::fromString(d->initialItem.toString(), qmlEngine(this), this), Immediate); + element = QQuickStackElement::fromString(d->initialItem.toString(), this); + if (d->pushElement(element)) { + emit depthChanged(); + d->setCurrentItem(element->item); + } } -/*! - \qmltype Stack - \inherits QtObject - \instantiates QQuickStackAttached - \inqmlmodule QtQuick.Controls - \ingroup navigation - \brief TODO +void QQuickAbstractStackView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickAbstractContainer::geometryChanged(newGeometry, oldGeometry); - TODO -*/ + Q_D(QQuickAbstractStackView); + foreach (QQuickStackElement *element, d->elements) { + if (element->item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(element->item); + if (!p->widthValid) { + element->item->setWidth(newGeometry.width()); + p->widthValid = false; + } + if (!p->heightValid) { + element->item->setHeight(newGeometry.height()); + p->heightValid = false; + } + } + } +} -class QQuickStackAttachedPrivate : public QObjectPrivate +void QQuickStackAttachedPrivate::init() { -public: - QQuickStackAttachedPrivate() : status(QQuickStackAttached::Inactive) { } - - QQuickStackAttached::Status status; -}; + QQuickItem *item = qobject_cast<QQuickItem *>(parent); + if (item) { + QQuickAbstractStackView *view = qobject_cast<QQuickAbstractStackView *>(item->parentItem()); + if (view) { + element = QQuickAbstractStackViewPrivate::get(view)->findElement(item); + if (element) + initialized = true; + } + } +} -QQuickStackAttached::QQuickStackAttached(QObject *parent) : - QObject(*(new QQuickStackAttachedPrivate), parent) +void QQuickStackAttachedPrivate::reset() { + Q_Q(QQuickStackAttached); + int oldIndex = element ? element->index : -1; + QQuickAbstractStackView::Status oldStatus = element ? element->status : QQuickAbstractStackView::Inactive; + + element = Q_NULLPTR; + + if (oldIndex != -1) + emit q->indexChanged(); + if (oldStatus != QQuickAbstractStackView::Inactive) + emit q->statusChanged(); } -QQuickStackAttached *QQuickStackAttached::qmlAttachedProperties(QObject *object) +QQuickStackAttached::QQuickStackAttached(QQuickItem *parent) : + QObject(*(new QQuickStackAttachedPrivate), parent) { - return new QQuickStackAttached(object); } /*! - \qmlattachedproperty enumeration QtQuickControls2::Stack::status + \qmlattachedproperty int QtQuickControls2::StackView::index TODO */ -QQuickStackAttached::Status QQuickStackAttached::status() const +int QQuickStackAttached::index() const { Q_D(const QQuickStackAttached); - return d->status; + if (!d->initialized) + const_cast<QQuickStackAttachedPrivate *>(d)->init(); + return d->element ? d->element->index : -1; } -void QQuickStackAttached::setStatus(Status status) +/*! + \qmlattachedproperty enumeration QtQuickControls2::StackView::status + + TODO +*/ +QQuickAbstractStackView::Status QQuickStackAttached::status() const { - Q_D(QQuickStackAttached); - if (d->status != status) { - d->status = status; - emit statusChanged(); - } + Q_D(const QQuickStackAttached); + if (!d->initialized) + const_cast<QQuickStackAttachedPrivate *>(d)->init(); + return d->element ? d->element->status : QQuickAbstractStackView::Inactive; } QT_END_NAMESPACE diff --git a/src/controls/qquickabstractstackview_p.cpp b/src/controls/qquickabstractstackview_p.cpp new file mode 100644 index 00000000..76061461 --- /dev/null +++ b/src/controls/qquickabstractstackview_p.cpp @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickabstractstackview_p_p.h" + +#include <QtQml/qqmllist.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlincubator.h> +#include <QtQml/private/qqmlcomponent_p.h> +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuick/private/qquicktransition_p.h> +#include <QtQuick/private/qquickitemviewtransition_p.h> + +QT_BEGIN_NAMESPACE + +static QQuickStackAttached *attachedStackObject(QQuickItem *item) +{ + return qobject_cast<QQuickStackAttached *>(qmlAttachedPropertiesObject<QQuickAbstractStackView>(item, false)); +} + +class QQuickStackIncubator : public QQmlIncubator +{ +public: + QQuickStackIncubator(QQuickStackElement *element) : QQmlIncubator(Synchronous), element(element) { } + +protected: + void setInitialState(QObject *object) Q_DECL_OVERRIDE { element->initItem(object); } + +private: + QQuickStackElement *element; +}; + +QQuickStackElement::QQuickStackElement() : QQuickItemViewTransitionableItem(Q_NULLPTR), + index(-1), removal(false), ownItem(false), ownComponent(false), + context(Q_NULLPTR), component(Q_NULLPTR), incubator(Q_NULLPTR), view(Q_NULLPTR), + status(QQuickAbstractStackView::Inactive) +{ +} + +QQuickStackElement::~QQuickStackElement() +{ + if (item) { + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + + QQuickStackAttached *attached = attachedStackObject(item); + if (attached) + QQuickStackAttachedPrivate::get(attached)->reset(); + } + + if (ownComponent) + delete component; + + if (ownItem && item) { + item->setParentItem(Q_NULLPTR); + item->deleteLater(); + item = Q_NULLPTR; + } else if (item) { + item->setVisible(false); + item->setParentItem(originalParent); + } + + delete context; + delete incubator; +} + +QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickAbstractStackView *view) +{ + QQuickStackElement *element = new QQuickStackElement; + element->component = new QQmlComponent(qmlEngine(view), QUrl(str), view); + element->ownComponent = true; + return element; +} + +QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickAbstractStackView *view) +{ + QQuickStackElement *element = new QQuickStackElement; + element->component = qobject_cast<QQmlComponent *>(object); + if (!element->component) { + element->component = new QQmlComponent(qmlEngine(view), view); + element->ownComponent = true; + } + element->item = qobject_cast<QQuickItem *>(object); + if (element->item) { + element->originalParent = element->item->parentItem(); + element->item->setParentItem(view); + QQuickItemPrivate::get(element->item)->addItemChangeListener(element, QQuickItemPrivate::Destroyed); + } + return element; +} + +bool QQuickStackElement::load(QQuickAbstractStackView *parent) +{ + view = parent; + if (!item) { + ownItem = true; + + QQmlContext *creationContext = component->creationContext(); + if (!creationContext) + creationContext = qmlContext(parent); + context = new QQmlContext(creationContext); + context->setContextObject(parent); + + delete incubator; + incubator = new QQuickStackIncubator(this); + component->create(*incubator, context); + } + initProperties(); + return item; +} + +void QQuickStackElement::initItem(QObject *object) +{ + item = qmlobject_cast<QQuickItem *>(object); + if (item) { + QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership); + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid) { + item->setWidth(view->width()); + p->widthValid = false; + } + if (!p->heightValid) { + item->setHeight(view->height()); + p->heightValid = false; + } + item->setParentItem(view); + p->addItemChangeListener(this, QQuickItemPrivate::Destroyed); + } + initProperties(); +} + +void QQuickStackElement::initProperties() +{ + if (!properties.isUndefined()) { + QQmlComponentPrivate *d = QQmlComponentPrivate::get(component); + Q_ASSERT(d && d->engine); + QV4::ExecutionEngine *v4 = qmlGlobal.engine(); + Q_ASSERT(v4); + QV4::Scope scope(v4); + QV4::ScopedValue ipv(scope, properties.value()); + d->initializeObjectWithInitialProperties(*qmlGlobal.valueRef(), ipv, item); + properties.clear(); + } +} + +void QQuickStackElement::setIndex(int value) +{ + if (index != value) { + index = value; + QQuickStackAttached *attached = attachedStackObject(item); + if (attached) + emit attached->indexChanged(); + } +} + +void QQuickStackElement::setStatus(QQuickAbstractStackView::Status value) +{ + if (status != value) { + status = value; + QQuickStackAttached *attached = attachedStackObject(item); + if (attached) + emit attached->statusChanged(); + } +} + +void QQuickStackElement::transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget) +{ + if (transitioner) + transitioner->transitionNextReposition(this, type, asTarget); +} + +bool QQuickStackElement::prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds) +{ + if (transitioner) { + // TODO: add force argument to QQuickItemViewTransitionableItem::prepareTransition()? + nextTransitionToSet = true; + nextTransitionFromSet = true; + nextTransitionFrom += QPointF(1, 1); + return QQuickItemViewTransitionableItem::prepareTransition(transitioner, index, viewBounds); + } + return false; +} + +void QQuickStackElement::startTransition(QQuickItemViewTransitioner *transitioner) +{ + if (transitioner) + QQuickItemViewTransitionableItem::startTransition(transitioner, index); +} + +void QQuickStackElement::itemDestroyed(QQuickItem *) +{ + item = Q_NULLPTR; +} + +QQuickAbstractStackViewPrivate::QQuickAbstractStackViewPrivate() : currentItem(Q_NULLPTR), transitioner(Q_NULLPTR) +{ +} + +void QQuickAbstractStackViewPrivate::setCurrentItem(QQuickItem *item) +{ + Q_Q(QQuickAbstractStackView); + if (currentItem != item) { + currentItem = item; + if (item) + item->setVisible(true); + emit q->currentItemChanged(); + } +} + +static bool initProperties(QQuickStackElement *element, const QV4::Value &props, QQmlV4Function *args) +{ + if (props.isObject()) { + const QV4::QObjectWrapper *wrapper = props.as<QV4::QObjectWrapper>(); + if (!wrapper) { + QV4::ExecutionEngine *v4 = args->v4engine(); + element->properties.set(v4, props); + element->qmlGlobal.set(v4, args->qmlGlobal()); + return true; + } + } + return false; +} + +QList<QQuickStackElement *> QQuickAbstractStackViewPrivate::parseElements(QQmlV4Function *args, int from) +{ + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + QList<QQuickStackElement *> elements; + + int argc = args->length(); + for (int i = from; i < argc; ++i) { + QV4::ScopedValue arg(scope, (*args)[i]); + if (QV4::ArrayObject *array = arg->asArrayObject()) { + int len = array->getLength(); + for (int j = 0; j < len; ++j) { + QV4::ScopedValue value(scope, array->getIndexed(j)); + QQuickStackElement *element = createElement(value); + if (element) { + if (j < len - 1) { + QV4::ScopedValue props(scope, array->getIndexed(j + 1)); + if (initProperties(element, props, args)) + ++j; + } + elements += element; + } + } + } else { + QQuickStackElement *element = createElement(arg); + if (element) { + if (i < argc - 1) { + QV4::ScopedValue props(scope, (*args)[i + 1]); + if (initProperties(element, props, args)) + ++i; + } + elements += element; + } + } + } + return elements; +} + +QQuickStackElement *QQuickAbstractStackViewPrivate::findElement(QQuickItem *item) const +{ + if (item) { + foreach (QQuickStackElement *e, elements) { + if (e->item == item) + return e; + } + } + return Q_NULLPTR; +} + +QQuickStackElement *QQuickAbstractStackViewPrivate::findElement(const QV4::Value &value) const +{ + if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>()) + return findElement(qobject_cast<QQuickItem *>(o->object())); + return Q_NULLPTR; +} + +QQuickStackElement *QQuickAbstractStackViewPrivate::createElement(const QV4::Value &value) +{ + Q_Q(QQuickAbstractStackView); + if (QV4::String *s = value.asString()) + return QQuickStackElement::fromString(s->toQString(), q); + if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>()) + return QQuickStackElement::fromObject(o->object(), q); + return Q_NULLPTR; +} + +bool QQuickAbstractStackViewPrivate::pushElements(const QList<QQuickStackElement *> &elems) +{ + Q_Q(QQuickAbstractStackView); + if (!elems.isEmpty()) { + foreach (QQuickStackElement *e, elems) { + e->setIndex(elements.count()); + elements += e; + } + return elements.top()->load(q); + } + return false; +} + +bool QQuickAbstractStackViewPrivate::pushElement(QQuickStackElement *element) +{ + if (element) + return pushElements(QList<QQuickStackElement *>() << element); + return false; +} + +bool QQuickAbstractStackViewPrivate::popElements(QQuickStackElement *element) +{ + Q_Q(QQuickAbstractStackView); + if (elements.count() > 1) { + while (elements.count() > 1 && elements.top() != element) { + delete elements.pop(); + if (!element) + break; + } + } + return elements.top()->load(q); +} + +bool QQuickAbstractStackViewPrivate::replaceElements(QQuickStackElement *target, const QList<QQuickStackElement *> &elems) +{ + if (target) { + while (!elements.isEmpty()) { + QQuickStackElement* top = elements.pop(); + delete top; + if (top == target) + break; + } + } + return pushElements(elems); +} + +void QQuickAbstractStackViewPrivate::ensureTransitioner() +{ + if (!transitioner) { + transitioner = new QQuickItemViewTransitioner; + transitioner->setChangeListener(this); + } +} + +void QQuickAbstractStackViewPrivate::popTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate) +{ + Q_Q(QQuickAbstractStackView); + if (exit) { + exit->setStatus(QQuickAbstractStackView::Deactivating); + exit->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, true); + } + if (enter) { + enter->setStatus(QQuickAbstractStackView::Activating); + enter->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false); + } + + if (exit && exit->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(exit, transitioner->removeTransition); + else + exit->startTransition(transitioner); + } + if (enter && enter->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(enter, transitioner->removeDisplacedTransition); + else + enter->startTransition(transitioner); + } + + if (!immediate) + emit q->busyChanged(); + + if (transitioner) + transitioner->resetTargetLists(); +} + +void QQuickAbstractStackViewPrivate::pushTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate) +{ + Q_Q(QQuickAbstractStackView); + if (enter) { + enter->setStatus(QQuickAbstractStackView::Activating); + enter->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); + } + if (exit) { + exit->setStatus(QQuickAbstractStackView::Deactivating); + exit->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false); + } + + if (enter && enter->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(enter, transitioner->addTransition); + else + enter->startTransition(transitioner); + } + if (exit && exit->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(exit, transitioner->addDisplacedTransition); + else + exit->startTransition(transitioner); + } + + if (!immediate) + emit q->busyChanged(); + + if (transitioner) + transitioner->resetTargetLists(); +} + +void QQuickAbstractStackViewPrivate::replaceTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate) +{ + Q_Q(QQuickAbstractStackView); + if (enter) { + enter->setStatus(QQuickAbstractStackView::Activating); + enter->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); + } + if (exit) { + exit->removal = true; + exit->setStatus(QQuickAbstractStackView::Deactivating); + exit->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false); + } + + if (enter && enter->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(enter, transitioner->addTransition); + else + enter->startTransition(transitioner); + } + if (exit && exit->prepareTransition(transitioner, viewBounds)) { + if (immediate) + completeTransition(exit, transitioner->addDisplacedTransition); + else + exit->startTransition(transitioner); + } + + if (!immediate) + emit q->busyChanged(); + + if (transitioner) + transitioner->resetTargetLists(); +} + +void QQuickAbstractStackViewPrivate::completeTransition(QQuickStackElement *element, QQuickTransition *transition) +{ + if (transition) { + // TODO: add a proper way to complete a transition + QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations(); + int count = animations.count(&animations); + for (int i = 0; i < count; ++i) { + QQuickAbstractAnimation *anim = animations.at(&animations, i); + anim->complete(); + } + viewItemTransitionFinished(element); + } +} + +void QQuickAbstractStackViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitionableItem *transitionable) +{ + Q_Q(QQuickAbstractStackView); + + QQuickStackElement *element = static_cast<QQuickStackElement *>(transitionable); + if (element->status == QQuickAbstractStackView::Activating) { + element->setStatus(QQuickAbstractStackView::Active); + } else if (element->status == QQuickAbstractStackView::Deactivating) { + element->setStatus(QQuickAbstractStackView::Inactive); + element->item->setVisible(false); + if (element->removal || element->isPendingRemoval()) + delete element; + } + + if (transitioner->runningJobs.isEmpty()) + emit q->busyChanged(); +} + +QT_END_NAMESPACE diff --git a/src/controls/qquickabstractstackview_p.h b/src/controls/qquickabstractstackview_p.h index 5bb09464..d15a7be4 100644 --- a/src/controls/qquickabstractstackview_p.h +++ b/src/controls/qquickabstractstackview_p.h @@ -53,51 +53,90 @@ QT_BEGIN_NAMESPACE class QQmlV4Function; +class QQuickTransition; +class QQuickStackElement; +class QQuickStackAttached; class QQuickAbstractStackViewPrivate; class Q_QUICKCONTROLS_EXPORT QQuickAbstractStackView : public QQuickAbstractContainer { Q_OBJECT - Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged FINAL) // TODO: hide setBusy - Q_PROPERTY(int depth READ depth WRITE setDepth NOTIFY depthChanged FINAL) // TODO: hide setDepth - Q_PROPERTY(QQuickItem *currentItem READ currentItem WRITE setCurrentItem NOTIFY currentItemChanged FINAL) // TODO: hide setCurrentItem + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged FINAL) + Q_PROPERTY(int depth READ depth NOTIFY depthChanged FINAL) + Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL) Q_PROPERTY(QVariant initialItem READ initialItem WRITE setInitialItem FINAL) + Q_PROPERTY(QQuickTransition *popEnter READ popEnter WRITE setPopEnter NOTIFY popEnterChanged FINAL) + Q_PROPERTY(QQuickTransition *popExit READ popExit WRITE setPopExit NOTIFY popExitChanged FINAL) + Q_PROPERTY(QQuickTransition *pushEnter READ pushEnter WRITE setPushEnter NOTIFY pushEnterChanged FINAL) + Q_PROPERTY(QQuickTransition *pushExit READ pushExit WRITE setPushExit NOTIFY pushExitChanged FINAL) public: explicit QQuickAbstractStackView(QQuickItem *parent = Q_NULLPTR); ~QQuickAbstractStackView(); - bool busy() const; - void setBusy(bool busy); // TODO: hide + static QQuickStackAttached *qmlAttachedProperties(QObject *object); + bool busy() const; int depth() const; - void setDepth(int depth); // TODO: hide - QQuickItem *currentItem() const; - void setCurrentItem(QQuickItem *item); // TODO: hide + + enum Status { + Inactive = 0, + Deactivating = 1, + Activating = 2, + Active = 3 + }; + Q_ENUM(Status) QVariant initialItem() const; void setInitialItem(const QVariant &item); + QQuickTransition *popEnter() const; + void setPopEnter(QQuickTransition *enter); + + QQuickTransition *popExit() const; + void setPopExit(QQuickTransition *exit); + + QQuickTransition *pushEnter() const; + void setPushEnter(QQuickTransition *enter); + + QQuickTransition *pushExit() const; + void setPushExit(QQuickTransition *exit); + + enum LoadBehavior { + DontLoad, + ForceLoad + }; + Q_ENUM(LoadBehavior) + + Q_INVOKABLE QQuickItem *get(int index, LoadBehavior behavior = DontLoad); + Q_INVOKABLE QQuickItem *find(const QJSValue &callback, LoadBehavior behavior = DontLoad); + enum Operation { Transition, Immediate }; Q_ENUM(Operation) - Q_INVOKABLE QQuickItem *qpush(QQmlV4Function *args); - Q_INVOKABLE QQuickItem *qpop(QQmlV4Function *args); + Q_INVOKABLE void push(QQmlV4Function *args); + Q_INVOKABLE void pop(QQmlV4Function *args); + Q_INVOKABLE void replace(QQmlV4Function *args); public Q_SLOTS: - void qclear(); + void clear(); Q_SIGNALS: void busyChanged(); void depthChanged(); void currentItemChanged(); + void popEnterChanged(); + void popExitChanged(); + void pushEnterChanged(); + void pushExitChanged(); protected: void componentComplete() Q_DECL_OVERRIDE; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; private: Q_DISABLE_COPY(QQuickAbstractStackView) @@ -109,25 +148,17 @@ class QQuickStackAttachedPrivate; class Q_QUICKCONTROLS_EXPORT QQuickStackAttached : public QObject { Q_OBJECT - Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged) + Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) + Q_PROPERTY(QQuickAbstractStackView::Status status READ status NOTIFY statusChanged FINAL) public: - explicit QQuickStackAttached(QObject *parent = Q_NULLPTR); - - static QQuickStackAttached *qmlAttachedProperties(QObject *object); - - enum Status { - Inactive = 0, - Deactivating = 1, - Activating = 2, - Active = 3 - }; - Q_ENUM(Status) + explicit QQuickStackAttached(QQuickItem *parent = Q_NULLPTR); - Status status() const; - void setStatus(Status status); + int index() const; + QQuickAbstractStackView::Status status() const; Q_SIGNALS: + void indexChanged(); void statusChanged(); private: @@ -137,6 +168,7 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPEINFO(QQuickStackAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPEINFO(QQuickAbstractStackView, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QQuickAbstractStackView) #endif // QQUICKABSTRACTSTACKVIEW_P_H diff --git a/src/controls/qquickabstractstackview_p_p.h b/src/controls/qquickabstractstackview_p_p.h new file mode 100644 index 00000000..5cda34f9 --- /dev/null +++ b/src/controls/qquickabstractstackview_p_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKABSTRACTSTACKVIEW_P_P_H +#define QQUICKABSTRACTSTACKVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickControls/private/qquickabstractstackview_p.h> +#include <QtQuickControls/private/qquickabstractcontainer_p_p.h> +#include <QtQuick/private/qquickitemviewtransition_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQml/private/qv4persistent_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlContext; +class QQmlComponent; +class QQmlIncubator; + +class QQuickStackElement : public QQuickItemViewTransitionableItem, public QQuickItemChangeListener +{ + QQuickStackElement(); + +public: + ~QQuickStackElement(); + + static QQuickStackElement *fromString(const QString &str, QQuickAbstractStackView *view); + static QQuickStackElement *fromObject(QObject *object, QQuickAbstractStackView *view); + + bool load(QQuickAbstractStackView *parent); + void initItem(QObject *object); + void initProperties(); + + void setIndex(int index); + void setStatus(QQuickAbstractStackView::Status status); + + void transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget); + bool prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds); + void startTransition(QQuickItemViewTransitioner *transitioner); + + void itemDestroyed(QQuickItem *item) Q_DECL_OVERRIDE; + + int index; + bool removal; + bool ownItem; + bool ownComponent; + QQmlContext *context; + QQmlComponent *component; + QQmlIncubator *incubator; + QQuickAbstractStackView *view; + QPointer<QQuickItem> originalParent; + QQuickAbstractStackView::Status status; + QV4::PersistentValue properties; + QV4::PersistentValue qmlGlobal; +}; + +class QQuickAbstractStackViewPrivate : public QQuickAbstractContainerPrivate, public QQuickItemViewTransitionChangeListener +{ + Q_DECLARE_PUBLIC(QQuickAbstractStackView) + +public: + QQuickAbstractStackViewPrivate(); + + static QQuickAbstractStackViewPrivate *get(QQuickAbstractStackView *view) + { + return view->d_func(); + } + + void setCurrentItem(QQuickItem *item); + + QList<QQuickStackElement *> parseElements(QQmlV4Function *args, int from = 0); + QQuickStackElement *findElement(QQuickItem *item) const; + QQuickStackElement *findElement(const QV4::Value &value) const; + QQuickStackElement *createElement(const QV4::Value &value); + bool pushElements(const QList<QQuickStackElement *> &elements); + bool pushElement(QQuickStackElement *element); + bool popElements(QQuickStackElement *element); + bool replaceElements(QQuickStackElement *element, const QList<QQuickStackElement *> &elements); + + void ensureTransitioner(); + void popTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate); + void pushTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate); + void replaceTransition(QQuickStackElement *enter, QQuickStackElement *exit, const QRectF &viewBounds, bool immediate); + void completeTransition(QQuickStackElement *element, QQuickTransition *transition); + + void viewItemTransitionFinished(QQuickItemViewTransitionableItem *item) Q_DECL_OVERRIDE; + + QVariant initialItem; + QQuickItem *currentItem; + QStack<QQuickStackElement *> elements; + QQuickItemViewTransitioner *transitioner; +}; + +class QQuickStackAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickStackAttached) + +public: + QQuickStackAttachedPrivate() : initialized(false), element(Q_NULLPTR) { } + + static QQuickStackAttachedPrivate *get(QQuickStackAttached *attached) + { + return attached->d_func(); + } + + void init(); + void reset(); + + bool initialized; + QQuickStackElement *element; +}; + +QT_END_NAMESPACE + +#endif // QQUICKABSTRACTSTACKVIEW_P_P_H diff --git a/src/imports/controls/StackView.js b/src/imports/controls/StackView.js deleted file mode 100644 index 37e2066d..00000000 --- a/src/imports/controls/StackView.js +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL3$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPLv3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or later as published by the Free -** Software Foundation and appearing in the file LICENSE.GPL included in -** the packaging of this file. Please review the following information to -** ensure the GNU General Public License version 2.0 requirements will be -** met: http://www.gnu.org/licenses/gpl-2.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -var stackView = []; - -function push(p) -{ - if (!p) - return - stackView.push(p) - __depth++ - return p -} - -function pop() -{ - if (stackView.length === 0) - return null - var p = stackView.pop() - __depth-- - return p -} - -function current() -{ - if (stackView.length === 0) - return null - return stackView[stackView.length-1] -} - diff --git a/src/imports/controls/StackView.qml b/src/imports/controls/StackView.qml index 2c75f063..396778b7 100644 --- a/src/imports/controls/StackView.qml +++ b/src/imports/controls/StackView.qml @@ -34,921 +34,25 @@ ** ****************************************************************************/ -import QtQuick 2.2 +import QtQuick 2.4 import QtQuick.Controls 2.0 -import "StackView.js" as JSArray - -/*! - \qmltype StackView - \inherits Item - \ingroup views - \inqmlmodule QtQuick.Controls - \since 5.1 - - \brief Provides a stack-based navigation model. - - \image stackview.png - - StackView implements a stack-based navigation model, which can be used - with a set of interlinked information pages. Items are pushed onto the stack - as the user navigates deeper into the material, and popped off again when he - chooses to go back. - - The \l{Qt Quick Controls - Touch Gallery}{touch gallery} example is a good - starting point to understand how StackView works. The following snippet - from the example shows how it can be used: - - \qml - StackView { - id: stack - initialItem: view - - Component { - id: view - - MouseArea { - Text { - text: stack.depth - anchors.centerIn: parent - } - onClicked: stack.push(view) - } - } - } - \endqml - - \section1 Using StackView in an Application - Using the StackView in the application is typically a simple matter of adding - the StackView as a child of a Window. The stack is usually anchored to the - edges of the window, except at the top or bottom where it might be anchored - to a status bar, or some other similar UI component. The stack can then be - used by invoking its navigation methods. The first item to show in the StackView - is commonly loaded assigning it to \l initialItem. - - \note Items pushed onto the stack view have \l{Supported Attached Properties}{Stack attached properties}. - - \section1 Basic Navigation - There are three primary navigation operations in StackView: push(), pop(), and - replace (replace by specifying argument \c replace to push()). - These correspond to classic stack operations where "push" adds an item to the - top of a stack, "pop" removes the top item from the stack, and "replace" is like a - pop followed by a push in that it replaces the topmost item on the stack with - a new item (but the applied transtition might be different). The topmost item - in the stack corresponds to the one that is \l{StackView::currentItem} {currently} - visible on the screen. That means that "push" is the logical equivalent of navigating - forward or deeper into the application, "pop" is the equivalent of navigating back, - and "replace" is the equivalent of replacing the current item. - - Sometimes it is necessary to go back more than a single step in the stack, for - example, to return to a "main" item or some kind of section item in the application. - For this use case, it is possible to specify an item as a parameter for pop(). - This is called an "unwind" operation as the stack gets unwound to the specified item. - If the item is not found then the stack unwinds until there is only a single item in - the stack, which becomes the current item. To explicitly unwind to the bottom - of the stack it is recommended to use \l{pop()} {pop(null)}, though technically any - non-existent item will do. - - Given the stack [A, B, C]: - - \list - \li \l{push()}{push(D)} => [A, B, C, D] - "push" transition animation between C and D - \li pop() => [A, B] - "pop" transition animation between C and B - \li \l{push()}{push(D, replace)} => [A, B, D] - "replace" transition between C and D - \li \l{pop()}{pop(A)} => [A] - "pop" transition between C and A - \endlist - - \note Note that when the stack is empty, a push() will not perform a - transition animation because there is nothing to transition from (typically during - application start-up). A pop() on a stack with depth 1 or 0 is a no-operation. - If removing all items from the stack is needed, a separate function clear() is - available. - - Calling push() returns the item that was pushed onto the stack. - Calling pop() returns the item that was popped off the stack. When pop() is - called in an unwind operation the top-most item (the first item that was - popped, which will also be the one transitioning out) is returned. - - \section1 Deep Linking - \e{Deep linking} means launching an application into a particular state. For example, - a newspaper application could be launched into showing a particular article, - bypassing the front item (and possibly a section item) that would normally have - to be navigated through to get to the article concerned. In terms of StackView, deep - linking means the ability to modify the state of the stack, so much so that it is - possible to push a set of items to the top of the stack, or to completely reset - the stack to a given state. - - The API for deep linking in StackView is the same as for basic navigation. Pushing - an array instead of a single item, will involve that all the items in that array will - be pushed onto the stack. The transition animation, however, will be conducted as - if only the last item in the array was pushed onto the stack. The normal semantics - of push() apply for deep linking, meaning that push() adds whatever is pushed onto - the stack. Note also that only the last item of the array will be loaded. - The rest will be lazy-loaded as needed when entering the screen upon subsequent - calls to pop (or when requesting the item by using \a get). - - This gives us the following result, given the stack [A, B, C]: - - \list - \li \l{push()}{push([D, E, F])} => [A, B, C, D, E, F] - "push" transition animation between C and F - \li \l{push()}{push([D, E, F], replace)} => [A, B, D, E, F] - "replace" transition animation between C and F - \li clear(); \l{push()}{push([D, E, F])} => [D, E, F] - no transition animation (since the stack was empty) - \endlist - - \section1 Pushing items - - An item pushed onto the StackView can be either an Item, a URL, a string - with a URL, or a Component. To push it, assign it to a property "item" - inside a property list, and send it as an argument to \l{StackView::push}{push}: - - \code - stackView.push({item: yourItem}) - \endcode - - The list can contain several properties that control how the item should be pushed: - \list - \li \c item: this property is required, and holds the item to be pushed. - \li \c properties: a list of QML properties to be assigned to the item upon push. These - properties will be copied into the item at load time, or when the item will become - the current item (normally upon push). - \li \c immediate: set this property to \c true to skip transition effects. When pushing - an array, this property only needs to be set on the first element to make the - whole operation immediate. - \li \c replace: set this property to replace the current item on the stack. When pushing - an array, you only need to set this property on the first element to replace - as many elements on the stack as inside the array. - \li \c destroyOnPop: set this boolean to true if StackView needs to destroy the item when - it is popped off the stack. By default (if \a destroyOnPop is not specified), StackView - will destroy items pushed as components or URLs. Items not destroyed will be re-parented - back to the original parents they had before being pushed onto the stack and hidden. - If you need to set this property, do it with care, so that items are not leaked. - \endlist - - If the only argument needed is "item", the following short-hand notation can be applied: - - \code - stackView.push(yourItem) - \endcode - - You can push several items in one go by using an array of property lists. This is - optimizing compared to pushing items one by one, since StackView then can load only the - last item in the list. The rest will be loaded as they are about to become - the current item (which happens when the stack is popped). The following example shows how - to push an array of items: - - \code - stackView.push([{item: yourItem1}, {item: yourItem2}]) - \endcode - - If an inline item is pushed, the item is temporarily re-parented into the StackView. When the item - is later popped off, it gets re-parented back to its original owner again. - If, however, an item is pushed as a component or a URL, the actual item will be created as an - item from that component. This happens automatically when the item is about to become the current - item in the stack. Ownership of the item will then normally be taken by the StackView. It will as - such automatically destroy the item when it is later popped off. The component that declared the item, by - contrast, remains in the ownership of the application and is not destroyed by the stack. - This can be overridden by explicitly setting \c{destroyOnPop} in the list of arguments given to push. - - If the \c properties to be pushed are specified, they will be copied into the item at loading time - (in case of a component or URL), or when the item will become the current item (in case of an inline - item). The following example shows how this can be done: - - \code - stackView.push({item: someItem, properties: {fgcolor: "red", bgcolor: "blue"}}) - \endcode - - - \note Note that if an item is declared inside another item, and if that parent gets destroyed, - (even if a component was used), that child item will also be destroyed. - This follows normal Qt parent-child destruction rules, but sometimes comes as a surprise - for developers. - - \section1 Lifecycle - An item's lifecycle in the StackView can have the following transitions: - \list 1 - \li instantiation - \li inactive - \li activating - \li active - \li deactivating - \li inactive - \li destruction - \endlist - - It can move any number of times between inactive and active. When an item is activated, - it's visible on the screen and is considered to be the current item. An item - in a StackView that is not visible is not activated, even if the item is currently the - top-most item in the stack. When the stack becomes visible, the item that is top-most gets - activated. Likewise if the stack is then hidden, the topmost item would be deactivated. - Popping the item off the top of the stack at this point would not result in further - deactivation since the item is not active. - - There is an attached \l{Stack::status}{Stack.status} property that tracks the lifecycle. The - status values list is an enumeration with values \c Stack.Inactive, \c Stack.Activating, - \c Stack.Active and \c Stack.Deactivating. Combined with the normal \c Component.onComplete and - \c Component.onDestruction signals the entire lifecycle is thus: - - \list - \li Created: Component.onCompleted() - \li Activating: Stack.onStatusChanged (Stack.status is Stack.Activating) - \li Acivated: Stack.onStatusChanged (Stack.status is Stack.Active) - \li Deactivating: Stack.onStatusChanged (Stack.status is Stack.Deactivating) - \li Deactivated: Stack.onStatusChanged (Stack.status is Stack.Inactive) - \li Destruction: Component.onDestruction() - \endlist - - \section1 Finding items - Sometimes it is necessary to search for an item, for example, in order to unwind the stack to - an item to which the application does not have a reference. This is facilitated using a - function find() in StackView. The find() function takes a callback function as its - only argument. The callback gets invoked for each item in the stack (starting at the top). - If the callback returns true, then it signals that a match has been found and the find() - function returns that item. If the callback fails to return true (no match is found), - then find() returns \c null. - - The code below searches for an item in the stack that has a name "order_id" and then unwinds to - that item. Note that since find() returns \c {null} if no item is found, and since pop unwinds to - the bottom of the stack if null is given as the target item, the code works well even in - case no matching item is found. - - \code - stackView.pop(stackView.find(function(item) { - return item.name == "order_id"; - })); - \endcode - - You can also get to an item in the stack using \l {get()}{get(index)}. You should use - this function if your item depends on another item in the stack, as the function will - ensure that the item at the given index gets loaded before it is returned. - - \code - previousItem = stackView.get(myItem.Stack.index - 1)); - \endcode - - \section1 Transitions - - A transition is performed whenever a item is pushed or popped, and consists of - two items: enterItem and exitItem. The StackView itself will never move items - around, but instead delegate the job to an external animation set provided - by the style or the application developer. How items should visually enter and leave the stack - (and the geometry they should end up with) is therefore completely controlled from the outside. - - When the transition starts, the StackView will search for a transition that - matches the operation executed. There are three transitions to choose - from: pushTransition, popTransition, and replaceTransition. Each implements how - enterItem should animate in, and exitItem out. The transitions are - collected inside a StackViewDelegate object assigned to - \l {StackView::delegate}{delegate}. By default, popTransition and - replaceTransition will be the same as pushTransition, unless you set them - to something else. - - A simple fade transition could be implemented as: - - \qml - StackView { - delegate: StackViewDelegate { - function transitionFinished(properties) - { - properties.exitItem.opacity = 1 - } - - pushTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "opacity" - from: 0 - to: 1 - } - PropertyAnimation { - target: exitItem - property: "opacity" - from: 1 - to: 0 - } - } - } - } - \endqml - - PushTransition needs to inherit from StackViewTransition, which is a ParallelAnimation that - contains the properties \c enterItem and \c exitItem. You set the target of your - inner animations to those items. Since the same items instance can be pushed several - times to a StackView, you should always override - \l {StackViewDelegate::transitionFinished()}{StackViewDelegate.transitionFinished()}. - Implement this function to reset any properties animated on the exitItem so that later - transitions can expect the items to be in a default state. - - A more complex example could look like the following. Here, the items are lying on the side before - being rotated to an upright position: - - \qml - StackView { - delegate: StackViewDelegate { - function transitionFinished(properties) - { - properties.exitItem.x = 0 - properties.exitItem.rotation = 0 - } - - pushTransition: StackViewTransition { - SequentialAnimation { - ScriptAction { - script: enterItem.rotation = 90 - } - PropertyAnimation { - target: enterItem - property: "x" - from: enterItem.width - to: 0 - } - PropertyAnimation { - target: enterItem - property: "rotation" - from: 90 - to: 0 - } - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: -exitItem.width - } - } - } - } - \endqml - - \section2 Advanced usage - - When the StackView needs a new transition, it first calls - \l {StackViewDelegate::getTransition()}{StackViewDelegate.getTransition()}. - The base implementation of this function just looks for a property named \c properties.name inside - itself (root), which is how it finds \c {property Component pushTransition} in the examples above. - - \code - function getTransition(properties) - { - return root[properties.name] - } - \endcode - - You can override this function for your delegate if you need extra logic to decide which - transition to return. You could for example introspect the items, and return different animations - depending on the their internal state. StackView will expect you to return a Component that - contains a StackViewTransition, or a StackViewTransition directly. The former is easier, as StackView will - then create the transition and later destroy it when it's done, while avoiding any sideeffects - caused by the transition being alive long after it has run. Returning a StackViewTransition directly - can be useful if you need to write some sort of transition caching for performance reasons. - As an optimization, you can also return \c null to signal that you just want to show/hide the items - immediately without creating or running any transitions. You can also override this function if - you need to alter the items in any way before the transition starts. - - \c properties contains the properties that will be assigned to the StackViewTransition before - it runs. In fact, you can add more properties to this object during the call - if you need to initialize additional properties of your custom StackViewTransition when the returned - component is instantiated. - - The following example shows how you can decide which animation to use during runtime : - - \qml - StackViewDelegate { - function getTransition(properties) - { - return (properties.enterItem.Stack.index % 2) ? horizontalTransition : verticalTransition - } - - function transitionFinished(properties) - { - properties.exitItem.x = 0 - properties.exitItem.y = 0 - } - - property Component horizontalTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "x" - from: target.width - to: 0 - duration: 300 - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: target.width - duration: 300 - } - } - - property Component verticalTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "y" - from: target.height - to: 0 - duration: 300 - } - PropertyAnimation { - target: exitItem - property: "y" - from: 0 - to: target.height - duration: 300 - } - } - } - \endqml - - \section1 Supported Attached Properties - - Items in a StackView support these attached properties: - \list - \li \l{Stack::index}{Stack.index} - Contains the index of the item inside the StackView - \li \l{Stack::view}{Stack.view} - Contains the StackView the item is in - \li \l{Stack::status}{Stack.status} - Contains the status of the item - \endlist -*/ AbstractStackView { id: root - depth: root.__depth - currentItem: root.__currentItem - initialItem: null - busy: __currentTransition !== null - - /*! The transitions to use when pushing or popping items. - For better understanding on how to apply custom transitions, read \l{Transitions}. - \sa {Stack::transitions}{Stack.transitions} */ - property StackViewDelegate delegate: StackViewDelegate {} - - /*! Pushes an item onto the stack. The function takes a property list as argument, which - should contain one or more of the following properties: - \list - \li \c item: this property is required, and holds the item you want to push. - \li \c properties: a list of QML properties that should be assigned - to the item upon push. These properties will be copied into the item when it is - loaded (in case of a component or URL), or when it becomes the current item for the - first time (normally upon push). - \li \c immediate: set this property to \c true to skip transition effects. When pushing - an array, you only need to set this property on the first element to make the - whole operation immediate. - \li \c replace: set this property to replace the current item on the stack. When pushing - an array, you only need to set this property on the first element to replace - as many elements on the stack as inside the array. - \li \c destroyOnPop: set this property to specify if the item needs to be destroyed - when its popped off the stack. By default (if \a destroyOnPop is not specified), - StackView will destroy items pushed as components or URLs. Items - not destroyed will be re-parented to the original parents they had before being - pushed onto the stack, and hidden. If you need to set this property, do it with - care, so that items are not leaked. - \endlist - - You can also push an array of items (property lists) if you need to push several items - in one go. A transition will then only occur between the current item and the last - item in the list. Loading the other items will be deferred until needed. - - Examples: - \list - \li stackView.push({item:anItem}) - \li stackView.push({item:aURL, immediate: true, replace: true}) - \li stackView.push({item:aRectangle, properties:{color:"red"}}) - \li stackView.push({item:aComponent, properties:{color:"red"}}) - \li stackView.push({item:aComponent.createObject(), destroyOnPop:true}) - \li stackView.push([{item:anitem, immediate:true}, {item:aURL}]) - \endlist - - \note Note: if the only argument needed is "item", you can apply the following short- - hand notation: \c{stackView.push(anItem)}. - - Returns the item that became current. - - \sa initialItem - \sa {Pushing items} - */ - function push(item) { - // Note: we support two different APIs in this function; The old meego API, and - // the new "property list" API. Hence the reason for hiding the fact that you - // can pass more arguments than shown in the signature: - if (__recursionGuard(true)) - return - var properties = arguments[1] - var immediate = arguments[2] - var replace = arguments[3] - var arrayPushed = (item instanceof Array) - var firstItem = arrayPushed ? item[0] : item - immediate = (immediate || JSArray.stackView.length === 0) - - if (firstItem && firstItem.item && firstItem.hasOwnProperty("x") === false) { - // Property list API used: - immediate = immediate || firstItem.immediate - replace = replace || firstItem.replace - } - - // Create, and push, a new javascript object, called "element", onto the stack. - // This element contains all the information necessary to construct the item, and - // will, after loaded, also contain the loaded item: - if (arrayPushed) { - if (item.length === 0) - return - var outElement = replace ? JSArray.pop() : JSArray.current() - for (var i=0; i<item.length; ++i) - JSArray.push({itemComponent:item[i], loaded: false, index: __depth, properties: properties}); - } else { - outElement = replace ? JSArray.pop() : JSArray.current() - JSArray.push({itemComponent:item, loaded: false, index: __depth, properties: properties}) - } - - var currentElement = JSArray.current() - var transition = { - inElement: currentElement, - outElement: outElement, - immediate: immediate, - replace: replace, - push: true - } - __performTransition(transition) - __recursionGuard(false) - return __currentItem + popEnter: Transition { + NumberAnimation { property: "x"; from: -root.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } - /*! Pops one or more items off the stack. The function takes a property list as argument - which can contain one or more of the following properties: - \list - \li \c item: if specified, all items down to (but not including) \a item will be - popped off. If \a item is \c null, all items down to (but not including) the - first item will be popped. If not specified, only the current item will be - popped. - \li \c immediate: set this property to \c true to skip transition effects. - \endlist - - Examples: - \list - \li stackView.pop() - \li stackView.pop({item:someItem, immediate: true}) - \li stackView.pop({immediate: true}) - \li stackView.pop(null) - \endlist - - \note Note: If the only argument needed is "item", you can apply the following short- - hand notation: \c{stackView.pop(anItem)}. - - Returns the item that was popped off - \sa clear() - */ - function pop(item) { - if (__depth <= 1) - return null - if (item && item.hasOwnProperty("x") === false) { - // Property list API used: - var immediate = (item.immediate === true) - item = item.item - } else { - immediate = (arguments[1] === true) - } - - if (item === __currentItem) - return - - if (__recursionGuard(true)) - return - - var outElement = JSArray.pop() - var inElement = JSArray.current() - - if (__depth > 1 && item !== undefined && item !== inElement.item) { - // Pop from the top until we find 'item', and return the corresponding - // element. Skip all non-loaded items (except the first), since no one - // has any references to such items anyway: - while (__depth > 1 && !JSArray.current().loaded) - JSArray.pop() - inElement = JSArray.current() - while (__depth > 1 && item !== inElement.item) { - JSArray.pop() - __cleanup(inElement) - while (__depth > 1 && !JSArray.current().loaded) - JSArray.pop() - inElement = JSArray.current() - } - } - - var transition = { - inElement: inElement, - outElement: outElement, - immediate: immediate, - replace: false, - push: false - } - __performTransition(transition) - __recursionGuard(false) - return outElement.item; - } - - /*! Remove all items from the stack. No animations will be applied. */ - function clear() { - if (__recursionGuard(true)) - return - if (__currentTransition) - __currentTransition.animation.complete() - __currentItem = null - var count = __depth - for (var i=0; i<count; ++i) { - var element = JSArray.pop() - if (element.item) - __cleanup(element); - } - __recursionGuard(false) - } - - /*! Search for a specific item inside the stack. \a func will - be called for each item in the stack (with the item as argument) - until the function returns true. Return value will be the item found. For - example: - find(function(item, index) { return item.isTheOne }) - Set \a onlySearchLoadedItems to \c true to not load items that are - not loaded into memory */ - function find(func, onlySearchLoadedItems) { - for (var i=__depth-1; i>=0; --i) { - var element = JSArray.stackView[i]; - if (onlySearchLoadedItems !== true) - __loadElement(element) - else if (!element.item) - continue - if (func(element.item, i)) - return element.item - } - return null; - } - - /*! Returns the item at position \a index in - the stack. If \a dontLoad is true, the - item will not be forced to load (and \c null - will be returned if not yet loaded) */ - function get(index, dontLoad) - { - if (index < 0 || index >= JSArray.stackView.length) - return null - var element = JSArray.stackView[index] - if (dontLoad !== true) { - __loadElement(element) - return element.item - } else if (element.item) { - return element.item - } else { - return null - } - } - - /*! Immediately completes any ongoing transition. - /sa Animation.complete - */ - function completeTransition() - { - if (__recursionGuard(true)) - return - if (__currentTransition) - __currentTransition.animation.complete() - __recursionGuard(false) - } - - /********* DEPRECATED API *********/ - - /*! \internal - \deprecated Use Push() instead */ - function replace(item, properties, immediate) { - push(item, properties, immediate, true) + popExit: Transition { + NumberAnimation { property: "x"; from: 0; to: root.width; duration: 400; easing.type: Easing.OutCubic } } - /********* PRIVATE API *********/ - - /*! \internal The currently top-most item on the stack. */ - property Item __currentItem: null - /*! \internal The number of items currently pushed onto the stack. */ - property int __depth: 0 - /*! \internal Stores the transition info while a transition is ongoing */ - property var __currentTransition: null - /*! \internal Stops the user from pushing items while preparing a transition */ - property bool __guard: false - - Component.onCompleted: { - if (initialItem) - push(initialItem) - } - - Component.onDestruction: { - if (__currentTransition) - __currentTransition.animation.complete() - __currentItem = null - } - - /*! \internal */ - function __recursionGuard(use) - { - if (use && __guard) { - console.warn("Warning: StackView: You cannot push/pop recursively!") - console.trace() - return true - } - __guard = use - } - - /*! \internal */ - function __loadElement(element) - { - if (element.loaded) { - if (!element.item) { - element.item = invalidItemReplacement.createObject(root) - element.item.text = "\nError: The item has been deleted outside StackView!" - } - return - } - if (!element.itemComponent) { - element.item = invalidItemReplacement.createObject(root) - element.item.text = "\nError: Invalid item (item was 'null'). " - + "This might indicate that the item was deleted outside StackView!" - return - } - - var comp = __resolveComponent(element.itemComponent, element) - - // Assign properties to item: - if (!element.properties) - element.properties = {} - - if (comp.hasOwnProperty("createObject")) { - if (comp.status === Component.Error) { - element.item = invalidItemReplacement.createObject(root) - element.item.text = "\nError: Could not load: " + comp.errorString() - } else { - element.item = comp.createObject(root, element.properties) - // Destroy items we create unless the user specified something else: - if (!element.hasOwnProperty("destroyOnPop")) - element.destroyOnPop = true - } - } else { - // comp is already an Item, so just re-parent it into the StackView: - element.item = comp - element.originalParent = parent - element.item.parent = root - for (var prop in element.properties) { - if (element.item.hasOwnProperty(prop)) - element.item[prop] = element.properties[prop]; - } - // Do not destroy items we didn't create, unless the user specified something else: - if (!element.hasOwnProperty("destroyOnPop")) - element.destroyOnPop = false - } - -//TODO element.item.Stack.__index = element.index -//TODO element.item.Stack.__view = root - // Let item fill all available space by default: - element.item.width = Qt.binding(function() { return root.width }) - element.item.height = Qt.binding(function() { return root.height }) - element.loaded = true - } - - /*! \internal */ - function __resolveComponent(unknownObjectType, element) - { - // We need this extra resolve function since we don't really - // know what kind of object the user pushed. So we try to - // figure it out by inspecting the object: - if (unknownObjectType.hasOwnProperty("createObject")) { - return unknownObjectType - } else if (typeof unknownObjectType == "string") { - return Qt.createComponent(unknownObjectType) - } else if (unknownObjectType.hasOwnProperty("x")) { - return unknownObjectType - } else if (unknownObjectType.hasOwnProperty("item")) { - // INVARIANT: user pushed a JS-object - element.properties = unknownObjectType.properties - if (!unknownObjectType.item) - unknownObjectType.item = invalidItemReplacement - if (unknownObjectType.hasOwnProperty("destroyOnPop")) - element.destroyOnPop = unknownObjectType.destroyOnPop - return __resolveComponent(unknownObjectType.item, element) - } else { - // We cannot determine the type, so assume its a URL: - return Qt.createComponent(unknownObjectType) - } - } - - /*! \internal */ - function __cleanup(element) { - // INVARIANT: element has been removed from JSArray. Destroy its - // item, or re-parent it back to the parent it had before it was pushed: - var item = element.item - if (element.destroyOnPop) { - item.destroy() - } else { - // Mark the item as no longer part of the StackView. It - // might reenter on pop if pushed several times: - item.visible = false - __setStatus(item, Stack.Inactive) -//TODO item.Stack.__view = null -//TODO item.Stack.__index = -1 - if (element.originalParent) - item.parent = element.originalParent - } - } - - /*! \internal */ - function __setStatus(item, status) { - item.Stack.status = status - } - - /*! \internal */ - function __performTransition(transition) - { - // Animate item in "outElement" out, and item in "inElement" in. Set a guard to protect - // the user from pushing new items on signals that will fire while preparing for the transition - // (e.g Stack.onCompleted, Stack.onStatusChanged, Stack.onIndexChanged etc). Otherwise, we will enter - // this function several times, which causes the items to be updated half-way. - if (__currentTransition) - __currentTransition.animation.complete() - __loadElement(transition.inElement) - - transition.name = transition.replace ? "replaceTransition" : (transition.push ? "pushTransition" : "popTransition") - var enterItem = transition.inElement.item - transition.enterItem = enterItem - - // Since an item can be pushed several times, we need to update its properties: - enterItem.parent = root -//TODO enterItem.Stack.__view = root -//TODO enterItem.Stack.__index = transition.inElement.index - __currentItem = enterItem - - if (!transition.outElement) { - // A transition consists of two items, but we got just one. So just show the item: - enterItem.visible = true - __setStatus(enterItem, Stack.Activating) - __setStatus(enterItem, Stack.Active) - return - } - - var exitItem = transition.outElement.item - transition.exitItem = exitItem - if (enterItem === exitItem) - return - - if (root.delegate) { - transition.properties = { - "name":transition.name, - "enterItem":transition.enterItem, - "exitItem":transition.exitItem, - "immediate":transition.immediate } - var anim = root.delegate.getTransition(transition.properties) - if (anim.createObject) { - anim = anim.createObject(null, transition.properties) - anim.runningChanged.connect(function(){ if (anim.running === false) anim.destroy() }) - } - transition.animation = anim - } - - if (!transition.animation) { - console.warn("Warning: StackView: no", transition.name, "found!") - return - } - if (enterItem.anchors.fill || exitItem.anchors.fill) - console.warn("Warning: StackView: cannot transition an item that is anchored!") - - __currentTransition = transition - __setStatus(exitItem, Stack.Deactivating) - enterItem.visible = true - __setStatus(enterItem, Stack.Activating) - transition.animation.runningChanged.connect(animationFinished) - transition.animation.start() - // NB! For empty animations, "animationFinished" is already - // executed at this point, leaving __animation === null: - if (transition.immediate === true && transition.animation) - transition.animation.complete() - } - - /*! \internal */ - function animationFinished() - { - if (!__currentTransition || __currentTransition.animation.running) - return - - __currentTransition.animation.runningChanged.disconnect(animationFinished) - __currentTransition.exitItem.visible = false - __setStatus(__currentTransition.exitItem, Stack.Inactive); - __setStatus(__currentTransition.enterItem, Stack.Active); - __currentTransition.properties.animation = __currentTransition.animation - root.delegate.transitionFinished(__currentTransition.properties) - - if (!__currentTransition.push || __currentTransition.replace) - __cleanup(__currentTransition.outElement) - - __currentTransition = null + pushEnter: Transition { + NumberAnimation { property: "x"; from: root.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } - /*! \internal */ - property Component invalidItemReplacement: Component { - Text { - width: parent.width - height: parent.height - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - } + pushExit: Transition { + NumberAnimation { property: "x"; from: 0; to: -root.width; duration: 400; easing.type: Easing.OutCubic } } } diff --git a/src/imports/controls/StackViewDelegate.qml b/src/imports/controls/StackViewDelegate.qml deleted file mode 100644 index 2d4355bd..00000000 --- a/src/imports/controls/StackViewDelegate.qml +++ /dev/null @@ -1,142 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL3$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPLv3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or later as published by the Free -** Software Foundation and appearing in the file LICENSE.GPL included in -** the packaging of this file. Please review the following information to -** ensure the GNU General Public License version 2.0 requirements will be -** met: http://www.gnu.org/licenses/gpl-2.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 - -/*! - \qmltype StackViewDelegate - \inqmlmodule QtQuick.Controls - \since 5.1 - - \brief A delegate used by StackView for loading transitions. - - See the documentation for the \l {StackView} component. - -*/ -QtObject { - id: root - - property bool horizontal: true - - function getTransition(properties) - { - return root[horizontal ? "horizontalSlide" : "verticalSlide"][properties.name] - } - - function transitionFinished(properties) - { - properties.exitItem.x = 0 - properties.exitItem.y = 0 - } - - property QtObject horizontalSlide: QtObject { - property Component pushTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "x" - from: target.width - to: 0 - duration: 400 - easing.type: Easing.OutCubic - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: -target.width - duration: 400 - easing.type: Easing.OutCubic - } - } - - property Component popTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "x" - from: -target.width - to: 0 - duration: 400 - easing.type: Easing.OutCubic - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: target.width - duration: 400 - easing.type: Easing.OutCubic - } - } - property Component replaceTransition: pushTransition - } - - property QtObject verticalSlide: QtObject { - property Component pushTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "y" - from: target.height - to: 0 - duration: 300 - } - PropertyAnimation { - target: exitItem - property: "y" - from: 0 - to: -target.height - duration: 300 - } - } - - property Component popTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "y" - from: -target.height - to: 0 - duration: 300 - } - PropertyAnimation { - target: exitItem - property: "y" - from: 0 - to: target.height - duration: 300 - } - property Component replaceTransition: pushTransition - } - } -} diff --git a/src/imports/controls/StackViewTransition.qml b/src/imports/controls/StackViewTransition.qml deleted file mode 100644 index f057791c..00000000 --- a/src/imports/controls/StackViewTransition.qml +++ /dev/null @@ -1,56 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL3$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPLv3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or later as published by the Free -** Software Foundation and appearing in the file LICENSE.GPL included in -** the packaging of this file. Please review the following information to -** ensure the GNU General Public License version 2.0 requirements will be -** met: http://www.gnu.org/licenses/gpl-2.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 - -ParallelAnimation { - id: root - /*! The name of the animation that is running. Can be one of the following: - \list - \li 'PushTransition' - \li 'PopTransition' - \li 'ReplaceTransition' - \endlist - */ - property string name - /*! The page that is transitioning in. */ - property Item enterItem - /*! The page that is transitioning out */ - property Item exitItem - /*! Set to \c true if the transition is told to - fast-forward directly to its end-state */ - property bool immediate -} diff --git a/src/imports/controls/controls.pro b/src/imports/controls/controls.pro index 44e2a5e6..48c8cd48 100644 --- a/src/imports/controls/controls.pro +++ b/src/imports/controls/controls.pro @@ -22,10 +22,7 @@ QML_FILES = \ ScrollBar.qml \ ScrollIndicator.qml \ Slider.qml \ - StackView.js \ StackView.qml \ - StackViewDelegate.qml \ - StackViewTransition.qml \ Switch.qml \ TabBar.qml \ TabButton.qml \ diff --git a/src/imports/controls/qmldir b/src/imports/controls/qmldir index b54df22c..eaedbbb5 100644 --- a/src/imports/controls/qmldir +++ b/src/imports/controls/qmldir @@ -15,8 +15,6 @@ ScrollBar 2.0 ScrollBar.qml ScrollIndicator 2.0 ScrollIndicator.qml Slider 2.0 Slider.qml StackView 2.0 StackView.qml -StackViewDelegate 2.0 StackViewDelegate.qml -StackViewTransition 2.0 StackViewTransition.qml Switch 2.0 Switch.qml TabBar 2.0 TabBar.qml TabButton 2.0 TabButton.qml diff --git a/tests/auto/controls/data/tst_stackview.qml b/tests/auto/controls/data/tst_stackview.qml index ed826ad2..3e87853d 100644 --- a/tests/auto/controls/data/tst_stackview.qml +++ b/tests/auto/controls/data/tst_stackview.qml @@ -50,7 +50,7 @@ TestCase { when: windowShown name: "StackView" - Item { id: item } + Item { id: item } TextField { id: textField } Component { id: component; Item { } } @@ -62,11 +62,14 @@ TestCase { function test_defaults() { var control = stackView.createObject(testCase) verify(control) - verify(control.delegate) + verify(control.pushEnter) + verify(control.pushExit) + verify(control.popEnter) + verify(control.popExit) compare(control.depth, 0) compare(control.busy, false) compare(control.currentItem, null) - compare(control.initialItem, null) + compare(control.initialItem, undefined) control.destroy() } @@ -85,7 +88,7 @@ TestCase { compare(control.currentItem, item) control.push(component) verify(control.currentItem !== item) - control.pop({immediate: true}) + control.pop(AbstractStackView.Immediate) compare(control.currentItem, item) control.destroy() } @@ -108,23 +111,44 @@ TestCase { var control = stackView.createObject(testCase) var item1 = component.createObject(control) - compare(item1.Stack.status, Stack.Inactive) + compare(item1.AbstractStackView.status, AbstractStackView.Inactive) control.push(item1) - compare(item1.Stack.status, Stack.Active) + compare(item1.AbstractStackView.status, AbstractStackView.Active) var item2 = component.createObject(control) - compare(item2.Stack.status, Stack.Inactive) + compare(item2.AbstractStackView.status, AbstractStackView.Inactive) control.push(item2) - compare(item2.Stack.status, Stack.Activating) - compare(item1.Stack.status, Stack.Deactivating) - tryCompare(item2.Stack, "status", Stack.Active) - tryCompare(item1.Stack, "status", Stack.Inactive) + compare(item2.AbstractStackView.status, AbstractStackView.Activating) + compare(item1.AbstractStackView.status, AbstractStackView.Deactivating) + tryCompare(item2.AbstractStackView, "status", AbstractStackView.Active) + tryCompare(item1.AbstractStackView, "status", AbstractStackView.Inactive) control.pop() - compare(item2.Stack.status, Stack.Deactivating) - compare(item1.Stack.status, Stack.Activating) - tryCompare(item2.Stack, "status", Stack.Inactive) - tryCompare(item1.Stack, "status", Stack.Active) + compare(item2.AbstractStackView.status, AbstractStackView.Deactivating) + compare(item1.AbstractStackView.status, AbstractStackView.Activating) + tryCompare(item2.AbstractStackView, "status", AbstractStackView.Inactive) + tryCompare(item1.AbstractStackView, "status", AbstractStackView.Active) + + control.destroy() + } + + function test_index() { + var control = stackView.createObject(testCase) + + var item1 = component.createObject(control) + compare(item1.AbstractStackView.index, -1) + control.push(item1, AbstractStackView.Immediate) + compare(item1.AbstractStackView.index, 0) + + var item2 = component.createObject(control) + compare(item2.AbstractStackView.index, -1) + control.push(item2, AbstractStackView.Immediate) + compare(item2.AbstractStackView.index, 1) + compare(item1.AbstractStackView.index, 0) + + control.pop(AbstractStackView.Immediate) + compare(item2.AbstractStackView.index, -1) + compare(item1.AbstractStackView.index, 0) control.destroy() } @@ -132,17 +156,17 @@ TestCase { function test_depth() { var control = stackView.createObject(testCase) compare(control.depth, 0) - control.push(item) + control.push(item, AbstractStackView.Immediate) compare(control.depth, 1) - control.push(item) + control.push(item, AbstractStackView.Immediate) compare(control.depth, 2) - control.pop() + control.pop(AbstractStackView.Immediate) compare(control.depth, 1) - control.push(component) + control.push(component, AbstractStackView.Immediate) compare(control.depth, 2) - control.pop() + control.pop(AbstractStackView.Immediate) compare(control.depth, 1) - control.pop() // ignored + control.pop(AbstractStackView.Immediate) // ignored compare(control.depth, 1) control.clear() compare(control.depth, 0) @@ -165,12 +189,12 @@ TestCase { control.forceActiveFocus() verify(control.activeFocus) - control.push({item: textField, immediate: true}) + control.push(textField, AbstractStackView.Immediate) compare(control.currentItem, textField) textField.forceActiveFocus() verify(textField.activeFocus) - control.pop({immediate: true}) + control.pop(AbstractStackView.Immediate) compare(control.currentItem, item) verify(control.activeFocus) verify(!textField.activeFocus) @@ -185,9 +209,9 @@ TestCase { var item2 = component.createObject(control, {objectName: "2"}) var item3 = component.createObject(control, {objectName: "3"}) - control.push(item1, {immediate: true}) - control.push(item2, {immediate: true}) - control.push(item3, {immediate: true}) + control.push(item1, AbstractStackView.Immediate) + control.push(item2, AbstractStackView.Immediate) + control.push(item3, AbstractStackView.Immediate) compare(control.find(function(item, index) { return index === 0 }), item1) compare(control.find(function(item) { return item.objectName === "1" }), item1) @@ -207,82 +231,156 @@ TestCase { function test_get() { var control = stackView.createObject(testCase) - control.push([item, component, component]) + control.push([item, component, component], AbstractStackView.Immediate) - verify(!control.get(0, true)) // dontLoad=true - compare(control.get(0, false), item) // dontLoad=false + verify(control.get(0, AbstractStackView.DontLoad)) + compare(control.get(0, AbstractStackView.ForceLoad), item) - verify(!control.get(1, true)) // dontLoad=true - verify(control.get(1, false)) // dontLoad=false + verify(!control.get(1, AbstractStackView.DontLoad)) - verify(control.get(2, true)) // dontLoad=true - verify(control.get(2, false)) // dontLoad=false + verify(control.get(2, AbstractStackView.DontLoad)) + verify(control.get(2, AbstractStackView.ForceLoad)) control.destroy() } - function test_pushpop() { + function test_push() { var control = stackView.createObject(testCase) + // missing arguments ### TODO: TestCase.ignoreWarning() + compare(control.push(), null) + + // nothing to push ### TODO: TestCase.ignoreWarning() + compare(control.push(AbstractStackView.Immediate), null) + + // push(item) var item1 = component.createObject(control, {objectName:"1"}) - compare(control.push(item1), item1) + compare(control.push(item1, AbstractStackView.Immediate), item1) compare(control.depth, 1) compare(control.currentItem, item1) + // push([item]) var item2 = component.createObject(control, {objectName:"2"}) - compare(control.push({item: item2}), item2) + compare(control.push([item2], AbstractStackView.Immediate), item2) compare(control.depth, 2) compare(control.currentItem, item2) - var item3 = component.createObject(control, {objectName:"3"}) - compare(control.push(item3, {}, true), item3) + // push(item, {properties}) + var item3 = component.createObject(control) + compare(control.push(item3, {objectName:"3"}, AbstractStackView.Immediate), item3) + compare(item3.objectName, "3") compare(control.depth, 3) compare(control.currentItem, item3) - var item4 = component.createObject(control, {objectName:"4"}) - compare(control.push(item4, {}, true, true), item4) - compare(control.depth, 3) + // push([item, {properties}]) + var item4 = component.createObject(control) + compare(control.push([item4, {objectName:"4"}], AbstractStackView.Immediate), item4) + compare(item4.objectName, "4") + compare(control.depth, 4) compare(control.currentItem, item4) - var item5 = component.createObject(control, {objectName:"5"}) - compare(control.push({item:item5, immediate:true, replace:true}), item5) - compare(control.depth, 3) + // push(component, {properties}) + var item5 = control.push(component, {objectName:"5"}, AbstractStackView.Immediate) + compare(item5.objectName, "5") + compare(control.depth, 5) compare(control.currentItem, item5) - var item6 = control.push(component, {objectName:"6"}) - compare(control.depth, 4) + // push([component, {properties}]) + var item6 = control.push([component, {objectName:"6"}], AbstractStackView.Immediate) + compare(item6.objectName, "6") + compare(control.depth, 6) compare(control.currentItem, item6) - var item7 = control.push({item:component, properties:{objectName:"7"}}) - compare(control.depth, 5) - compare(control.currentItem, item7) + control.destroy() + } + + function test_pop() { + var control = stackView.createObject(testCase) - var item8 = component.createObject(control, {objectName:"8"}) - var item9 = component.createObject(control, {objectName:"9"}) - compare(control.push([component, {item:component, properties:{objectName:"?"}}, {item:item8}, item9]), item9) - compare(control.depth, 9) - compare(control.currentItem, item9) + var items = [] + for (var i = 0; i < 7; ++i) + items.push(component.createObject(control, {objectName:i})) - compare(control.pop(), item9) - compare(control.depth, 8) - compare(control.currentItem, item8) + control.push(items, AbstractStackView.Immediate) - compare(control.pop(), item8) - compare(control.depth, 7) + // too many arguments ### TODO: TestCase.ignoreWarning() + compare(control.pop(1, 2, 3), null) - verify(control.pop({immediate:true})) - verify(control.pop({immediate:false})) - compare(control.depth, 5) - compare(control.currentItem, item7) + // pop the top most item + compare(control.pop(AbstractStackView.Immediate), items[6]) + compare(control.depth, 6) + compare(control.currentItem, items[5]) - compare(control.pop(item5), item7) + // pop down to (but not including) the Nth item + compare(control.pop(items[3], AbstractStackView.Immediate), items[5]) + compare(control.depth, 4) + compare(control.currentItem, items[3]) + + // pop the top most item + compare(control.pop(undefined, AbstractStackView.Immediate), items[3]) compare(control.depth, 3) - compare(control.currentItem, item5) + compare(control.currentItem, items[2]) - control.pop(null) + // don't pop non-existent item + compare(control.pop(testCase, AbstractStackView.Immediate), null) + compare(control.depth, 3) + compare(control.currentItem, items[2]) + + // pop all items down to (but not including) the 1st item + control.pop(null, AbstractStackView.Immediate) + compare(control.depth, 1) + compare(control.currentItem, items[0]) + + control.destroy() + } + + function test_replace() { + var control = stackView.createObject(testCase) + + // missing arguments ### TODO: TestCase.ignoreWarning() + compare(control.replace(), null) + + // nothing to push ### TODO: TestCase.ignoreWarning() + compare(control.replace(AbstractStackView.Immediate), null) + + // replace(item) + var item1 = component.createObject(control, {objectName:"1"}) + compare(control.replace(item1, AbstractStackView.Immediate), item1) compare(control.depth, 1) compare(control.currentItem, item1) + // replace([item]) + var item2 = component.createObject(control, {objectName:"2"}) + compare(control.replace([item2], AbstractStackView.Immediate), item2) + compare(control.depth, 1) + compare(control.currentItem, item2) + + // replace(item, {properties}) + var item3 = component.createObject(control) + compare(control.replace(item3, {objectName:"3"}, AbstractStackView.Immediate), item3) + compare(item3.objectName, "3") + compare(control.depth, 1) + compare(control.currentItem, item3) + + // replace([item, {properties}]) + var item4 = component.createObject(control) + compare(control.replace([item4, {objectName:"4"}], AbstractStackView.Immediate), item4) + compare(item4.objectName, "4") + compare(control.depth, 1) + compare(control.currentItem, item4) + + // replace(component, {properties}) + var item5 = control.replace(component, {objectName:"5"}, AbstractStackView.Immediate) + compare(item5.objectName, "5") + compare(control.depth, 1) + compare(control.currentItem, item5) + + // replace([component, {properties}]) + var item6 = control.replace([component, {objectName:"6"}], AbstractStackView.Immediate) + compare(item6.objectName, "6") + compare(control.depth, 1) + compare(control.currentItem, item6) + control.destroy() } } diff --git a/tests/benchmarks/creationtime/tst_creationtime.cpp b/tests/benchmarks/creationtime/tst_creationtime.cpp index a6b19d6c..3b051907 100644 --- a/tests/benchmarks/creationtime/tst_creationtime.cpp +++ b/tests/benchmarks/creationtime/tst_creationtime.cpp @@ -105,6 +105,7 @@ void tst_CreationTime::testControls_data() QTest::newRow("ScrollIndicator") << QByteArray("ScrollIndicator"); #endif QTest::newRow("Slider") << QByteArray("Slider"); + QTest::newRow("StackView") << QByteArray("StackView"); QTest::newRow("Switch") << QByteArray("Switch"); #ifndef QT_QUICK_CONTROLS_V1 QTest::newRow("TabBar") << QByteArray("TabBar"); |