summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Arve Saether <jan-arve.saether@theqtcompany.com>2015-03-04 16:22:00 +0100
committerJan Arve Sæther <jan-arve.saether@theqtcompany.com>2015-04-13 11:33:54 +0000
commit9bf07e57bee62950ac44f67fb9ea4c31b71995c0 (patch)
tree35d8539d3f62a6a778f1b330941106048f71f0fd
parent8b936debb8e60a573e9af895018ae5ab362e3c16 (diff)
downloadqtquickcontrols-9bf07e57bee62950ac44f67fb9ea4c31b71995c0.tar.gz
Say hello to StackLayout
Task-number: QTBUG-44077 Change-Id: I85dadc0950617728bca83d5c72ed5a086352b57d Reviewed-by: J-P Nurmi <jpnurmi@theqtcompany.com>
-rw-r--r--examples/quick/controls/basiclayouts/main.qml25
-rw-r--r--src/layouts/layouts.pro2
-rw-r--r--src/layouts/plugin.cpp2
-rw-r--r--src/layouts/qquicklayout.cpp3
-rw-r--r--src/layouts/qquickstacklayout.cpp333
-rw-r--r--src/layouts/qquickstacklayout_p.h105
-rw-r--r--tests/auto/controls/data/tst_stacklayout.qml448
7 files changed, 914 insertions, 4 deletions
diff --git a/examples/quick/controls/basiclayouts/main.qml b/examples/quick/controls/basiclayouts/main.qml
index bc5ca214..b6694b45 100644
--- a/examples/quick/controls/basiclayouts/main.qml
+++ b/examples/quick/controls/basiclayouts/main.qml
@@ -40,7 +40,7 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
-import QtQuick.Layouts 1.0
+import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
@@ -108,5 +108,28 @@ ApplicationWindow {
Layout.fillHeight: true
Layout.fillWidth: true
}
+ GroupBox {
+ id: stackBox
+ title: "Stack layout"
+ implicitWidth: 200
+ implicitHeight: 60
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ StackLayout {
+ id: stackLayout
+ anchors.fill: parent
+
+ function advance() { currentIndex = (currentIndex + 1) % count }
+
+ Repeater {
+ id: stackRepeater
+ model: 5
+ Rectangle {
+ color: Qt.hsla((0.5 + index)/stackRepeater.count, 0.3, 0.7, 1)
+ Button { anchors.centerIn: parent; text: "Page " + (index + 1); onClicked: { stackLayout.advance() } }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/layouts/layouts.pro b/src/layouts/layouts.pro
index 3ef18f85..f7a73b7e 100644
--- a/src/layouts/layouts.pro
+++ b/src/layouts/layouts.pro
@@ -10,12 +10,14 @@ QMAKE_DOCS = $$PWD/doc/qtquicklayouts.qdocconf
SOURCES += plugin.cpp \
qquicklayout.cpp \
qquicklinearlayout.cpp \
+ qquickstacklayout.cpp \
qquickgridlayoutengine.cpp \
qquicklayoutstyleinfo.cpp
HEADERS += \
qquicklayout_p.h \
qquicklinearlayout_p.h \
+ qquickstacklayout_p.h \
qquickgridlayoutengine_p.h \
qquicklayoutstyleinfo_p.h
diff --git a/src/layouts/plugin.cpp b/src/layouts/plugin.cpp
index fa72fa8d..6a670539 100644
--- a/src/layouts/plugin.cpp
+++ b/src/layouts/plugin.cpp
@@ -37,6 +37,7 @@
#include <QtQml/qqmlextensionplugin.h>
#include "qquicklinearlayout_p.h"
+#include "qquickstacklayout_p.h"
QT_BEGIN_NAMESPACE
@@ -54,6 +55,7 @@ public:
qmlRegisterType<QQuickRowLayout>(uri, 1, 0, "RowLayout");
qmlRegisterType<QQuickColumnLayout>(uri, 1, 0, "ColumnLayout");
qmlRegisterType<QQuickGridLayout>(uri, 1, 0, "GridLayout");
+ qmlRegisterType<QQuickStackLayout>(uri, 1, 3, "StackLayout");
qmlRegisterUncreatableType<QQuickLayout>(uri, 1, 0, "Layout",
QStringLiteral("Do not create objects of type Layout"));
qmlRegisterUncreatableType<QQuickLayout>(uri, 1, 2, "Layout",
diff --git a/src/layouts/qquicklayout.cpp b/src/layouts/qquicklayout.cpp
index 6fbb2ead..4a80bead 100644
--- a/src/layouts/qquicklayout.cpp
+++ b/src/layouts/qquicklayout.cpp
@@ -685,9 +685,6 @@ QQuickItem *QQuickLayoutAttached::item() const
}
-
-
-
QQuickLayout::QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent)
: QQuickItem(dd, parent),
m_dirty(false)
diff --git a/src/layouts/qquickstacklayout.cpp b/src/layouts/qquickstacklayout.cpp
new file mode 100644
index 00000000..3ada3b1e
--- /dev/null
+++ b/src/layouts/qquickstacklayout.cpp
@@ -0,0 +1,333 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Layouts 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 "qquickstacklayout_p.h"
+#include <limits>
+
+/*!
+ \qmltype StackLayout
+ \instantiates QQuickStackLayout
+ \inherits Item
+ \inqmlmodule QtQuick.Layouts
+ \ingroup layouts
+ \brief The StackLayout class provides a stack of items where
+ only one item is visible at a time.
+
+ The current visible item can be modified by setting the \l currentIndex property.
+ The index corresponds to the order of the StackLayout's children.
+
+ In contrast to most other layouts, child Items' \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} properties
+ defaults to \c true. As a consequence, child items are by default filled to match the size of the StackLayout as long as their
+ \l{Layout::maximumWidth}{Layout.maximumWidth} or \l{Layout::maximumHeight}{Layout.maximumHeight} does not prevent it.
+
+ Items are added to the layout by reparenting the item to the layout. Similarly, removal is done by reparenting the item from the layout.
+ Both of these operations will affect the layouts \l count property.
+
+ The following code will create a StackLayout where only the 'plum' rectangle is visible.
+ \code
+ StackLayout {
+ id: layout
+ anchors.fill: parent
+ currentIndex: 1
+ Rectangle {
+ color: 'teal'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ Rectangle {
+ color: 'plum'
+ implicitWidth: 300
+ implicitHeight: 200
+ }
+ }
+ \endcode
+
+ Items in a StackLayout support these attached properties:
+ \list
+ \li \l{Layout::minimumWidth}{Layout.minimumWidth}
+ \li \l{Layout::minimumHeight}{Layout.minimumHeight}
+ \li \l{Layout::preferredWidth}{Layout.preferredWidth}
+ \li \l{Layout::preferredHeight}{Layout.preferredHeight}
+ \li \l{Layout::maximumWidth}{Layout.maximumWidth}
+ \li \l{Layout::maximumHeight}{Layout.maximumHeight}
+ \li \l{Layout::fillWidth}{Layout.fillWidth}
+ \li \l{Layout::fillHeight}{Layout.fillHeight}
+ \endlist
+
+ Read more about attached properties \l{QML Object Attributes}{here}.
+ \sa ColumnLayout
+ \sa GridLayout
+ \sa RowLayout
+ \sa StackView
+*/
+
+QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) :
+ QQuickLayout(*new QQuickStackLayoutPrivate, parent)
+{
+}
+
+/*!
+ \qmlproperty int StackLayout::count
+
+ This property holds the number of items that belongs to the layout.
+
+ Only items that are children of the StackLayout will be candidates for layouting.
+*/
+int QQuickStackLayout::count() const
+{
+ Q_D(const QQuickStackLayout);
+ return d->count;
+}
+
+/*!
+ \qmlproperty int StackLayout::currentIndex
+
+ This property holds the current index of which of the StackLayout's items that should be visible.
+ By default it will be -1 for an empty layout, otherwise the default is 0 (referring to the first item).
+*/
+int QQuickStackLayout::currentIndex() const
+{
+ Q_D(const QQuickStackLayout);
+ return d->currentIndex;
+}
+
+void QQuickStackLayout::setCurrentIndex(int index)
+{
+ Q_D(QQuickStackLayout);
+ if (index != d->currentIndex) {
+ QQuickItem *prev = itemAt(d->currentIndex);
+ QQuickItem *next = itemAt(index);
+ d->currentIndex = index;
+ d->explicitCurrentIndex = true;
+ if (prev)
+ prev->setVisible(false);
+ if (next)
+ next->setVisible(true);
+
+ if (isComponentComplete()) {
+ rearrange(QSizeF(width(), height()));
+ emit currentIndexChanged();
+ }
+ }
+}
+
+void QQuickStackLayout::componentComplete()
+{
+ QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true)
+
+ updateLayoutItems();
+
+ QQuickItem *par = parentItem();
+ if (qobject_cast<QQuickLayout*>(par))
+ return;
+
+ rearrange(QSizeF(width(), height()));
+}
+
+QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const
+{
+ QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
+ if (!askingFor.isValid()) {
+ QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
+ QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
+ QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
+
+ minS = QSizeF(0,0);
+ prefS = QSizeF(0,0);
+ maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
+
+ const int count = itemCount();
+ m_cachedItemSizeHints.resize(count);
+ for (int i = 0; i < count; ++i) {
+ SizeHints &hints = m_cachedItemSizeHints[i];
+ QQuickStackLayout::collectItemSizeHints(itemAt(i), hints.array);
+ minS = minS.expandedTo(hints.min());
+ prefS = prefS.expandedTo(hints.pref());
+ //maxS = maxS.boundedTo(hints.max()); // Can be resized to be larger than any of its items.
+ // This is the same as QStackLayout does it.
+ // Not sure how descent makes sense here...
+ }
+ }
+ return askingFor;
+}
+
+int QQuickStackLayout::indexOf(QQuickItem *childItem) const
+{
+ if (childItem) {
+ int indexOfItem = 0;
+ foreach (QQuickItem *item, childItems()) {
+ if (shouldIgnoreItem(item))
+ continue;
+ if (childItem == item)
+ return indexOfItem;
+ ++indexOfItem;
+ }
+ }
+ return -1;
+}
+
+QQuickItem *QQuickStackLayout::itemAt(int index) const
+{
+ foreach (QQuickItem *item, childItems()) {
+ if (shouldIgnoreItem(item))
+ continue;
+ if (index == 0)
+ return item;
+ --index;
+ }
+ return 0;
+}
+
+int QQuickStackLayout::itemCount() const
+{
+ int count = 0;
+ foreach (QQuickItem *item, childItems()) {
+ if (shouldIgnoreItem(item))
+ continue;
+ ++count;
+ }
+ return count;
+}
+
+void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*align*/)
+{
+ // ### Do we have to respect alignment?
+}
+
+void QQuickStackLayout::invalidate(QQuickItem *childItem)
+{
+ Q_D(QQuickStackLayout);
+ if (d->m_ignoredItems.contains(childItem)) {
+ // If an invalid item gets a valid size, it should be included, as it was added to the layout
+ updateLayoutItems();
+ return;
+ }
+
+ const int indexOfChild = indexOf(childItem);
+ if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) {
+ m_cachedItemSizeHints[indexOfChild].min() = QSizeF();
+ m_cachedItemSizeHints[indexOfChild].pref() = QSizeF();
+ m_cachedItemSizeHints[indexOfChild].max() = QSizeF();
+ }
+
+ for (int i = 0; i < Qt::NSizeHints; ++i)
+ m_cachedSizeHints[i] = QSizeF();
+ QQuickLayout::invalidate(this);
+
+ QQuickLayoutAttached *info = attachedLayoutObject(this);
+
+ const QSizeF min = sizeHint(Qt::MinimumSize);
+ const QSizeF pref = sizeHint(Qt::PreferredSize);
+ const QSizeF max = sizeHint(Qt::MaximumSize);
+
+ const bool old = info->setChangesNotificationEnabled(false);
+ info->setMinimumImplicitSize(min);
+ info->setMaximumImplicitSize(max);
+ info->setChangesNotificationEnabled(old);
+ if (pref.width() == implicitWidth() && pref.height() == implicitHeight()) {
+ // In case setImplicitSize does not emit implicit{Width|Height}Changed
+ if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
+ parentLayout->invalidate(this);
+ } else {
+ setImplicitSize(pref.width(), pref.height());
+ }
+}
+
+void QQuickStackLayout::updateLayoutItems()
+{
+ Q_D(QQuickStackLayout);
+ d->m_ignoredItems.clear();
+ const int count = itemCount();
+ int oldIndex = d->currentIndex;
+ if (!d->explicitCurrentIndex)
+ d->currentIndex = (count > 0 ? 0 : -1);
+
+ if (d->currentIndex != oldIndex)
+ emit currentIndexChanged();
+
+ if (count != d->count) {
+ d->count = count;
+ emit countChanged();
+ }
+ for (int i = 0; i < count; ++i)
+ itemAt(i)->setVisible(d->currentIndex == i);
+
+ invalidate();
+}
+
+void QQuickStackLayout::rearrange(const QSizeF &newSize)
+{
+ Q_D(QQuickStackLayout);
+ if (newSize.isNull() || !newSize.isValid())
+ return;
+ (void)sizeHint(Qt::PreferredSize); // Make sure m_cachedItemSizeHints are valid
+
+ if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.count())
+ return;
+ QQuickStackLayout::SizeHints &hints = m_cachedItemSizeHints[d->currentIndex];
+ QQuickItem *item = itemAt(d->currentIndex);
+ Q_ASSERT(item);
+ item->setPosition(QPointF(0,0)); // ### respect alignment?
+ item->setSize(newSize.expandedTo(hints.min()).boundedTo(hints.max()));
+ QQuickLayout::rearrange(newSize);
+}
+
+void QQuickStackLayout::collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints)
+{
+ QQuickLayoutAttached *info = 0;
+ QQuickLayout::effectiveSizeHints_helper(item, sizeHints, &info, true);
+ if (!info)
+ return;
+ if (info->isFillWidthSet() && !info->fillWidth()) {
+ const qreal pref = sizeHints[Qt::PreferredSize].width();
+ sizeHints[Qt::MinimumSize].setWidth(pref);
+ sizeHints[Qt::MaximumSize].setWidth(pref);
+ }
+
+ if (info->isFillHeightSet() && !info->fillHeight()) {
+ const qreal pref = sizeHints[Qt::PreferredSize].height();
+ sizeHints[Qt::MinimumSize].setHeight(pref);
+ sizeHints[Qt::MaximumSize].setHeight(pref);
+ }
+}
+
+bool QQuickStackLayout::shouldIgnoreItem(QQuickItem *item) const
+{
+ const bool ignored = QQuickItemPrivate::get(item)->isTransparentForPositioner();
+ if (ignored)
+ d_func()->m_ignoredItems << item;
+ return ignored;
+}
diff --git a/src/layouts/qquickstacklayout_p.h b/src/layouts/qquickstacklayout_p.h
new file mode 100644
index 00000000..92121400
--- /dev/null
+++ b/src/layouts/qquickstacklayout_p.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Layouts 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 QQUICKSTACKLAYOUT_H
+#define QQUICKSTACKLAYOUT_H
+
+#include <qquicklayout_p.h>
+
+class QQuickStackLayoutPrivate;
+
+class QQuickStackLayout : public QQuickLayout
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+
+public:
+ explicit QQuickStackLayout(QQuickItem *parent = 0);
+ int count() const;
+ int currentIndex() const;
+ void setCurrentIndex(int index);
+
+ void componentComplete() Q_DECL_OVERRIDE;
+ QSizeF sizeHint(Qt::SizeHint whichSizeHint) const Q_DECL_OVERRIDE;
+ void setAlignment(QQuickItem *item, Qt::Alignment align) Q_DECL_OVERRIDE;
+ void invalidate(QQuickItem *childItem = 0) Q_DECL_OVERRIDE;
+ void updateLayoutItems() Q_DECL_OVERRIDE;
+ void rearrange(const QSizeF &) Q_DECL_OVERRIDE;
+
+ // iterator
+ Q_INVOKABLE QQuickItem *itemAt(int index) const Q_DECL_OVERRIDE;
+ int itemCount() const Q_DECL_OVERRIDE;
+ int indexOf(QQuickItem *item) const;
+
+
+
+signals:
+ void currentIndexChanged();
+ void countChanged();
+
+public slots:
+
+private:
+ static void collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints);
+ bool shouldIgnoreItem(QQuickItem *item) const;
+ Q_DECLARE_PRIVATE(QQuickStackLayout)
+
+ QList<QQuickItem*> m_items;
+
+ typedef struct {
+ inline QSizeF &min() { return array[Qt::MinimumSize]; }
+ inline QSizeF &pref() { return array[Qt::PreferredSize]; }
+ inline QSizeF &max() { return array[Qt::MaximumSize]; }
+ QSizeF array[Qt::NSizeHints];
+ } SizeHints;
+
+ mutable QVector<SizeHints> m_cachedItemSizeHints;
+ mutable QSizeF m_cachedSizeHints[Qt::NSizeHints];
+};
+
+class QQuickStackLayoutPrivate : public QQuickLayoutPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickStackLayout)
+public:
+ QQuickStackLayoutPrivate() : count(0), currentIndex(-1), explicitCurrentIndex(false) {}
+private:
+ int count;
+ int currentIndex;
+ bool explicitCurrentIndex;
+};
+
+#endif // QQUICKSTACKLAYOUT_H
diff --git a/tests/auto/controls/data/tst_stacklayout.qml b/tests/auto/controls/data/tst_stacklayout.qml
new file mode 100644
index 00000000..ba468a24
--- /dev/null
+++ b/tests/auto/controls/data/tst_stacklayout.qml
@@ -0,0 +1,448 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite 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
+import QtTest 1.0
+import QtQuick.Layouts 1.3
+
+Item {
+ id: container
+ width: 200
+ height: 200
+ TestCase {
+ id: testCase
+ name: "Tests_StackLayout"
+ when: windowShown
+ width: 200
+ height: 200
+
+ function geometry(item) {
+ return [item.x, item.y, item.width, item.height]
+ }
+
+ Component {
+ id: countGeometryChanges_Component
+ StackLayout {
+ id: stack
+ property alias col: _col
+ property alias row: _row
+ width: 100
+ ColumnLayout {
+ id: _col
+ property alias r1: _r1
+ property alias r2: _r2
+ property alias r3: _r3
+ spacing: 0
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ Rectangle {
+ id: _r1
+ implicitWidth: 20
+ implicitHeight: 20
+ Layout.fillWidth: true
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ }
+ Rectangle {
+ id: _r2
+ implicitWidth: 50
+ implicitHeight: 50
+ Layout.fillWidth: true
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ }
+ Rectangle {
+ id: _r3
+ implicitWidth: 40
+ implicitHeight: 40
+ Layout.fillWidth: true
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ }
+ }
+ RowLayout {
+ id: _row
+ property alias r5: _r5
+ spacing: 0
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ Rectangle {
+ id: _r5
+ implicitWidth: 100
+ implicitHeight: 100
+ Layout.fillWidth: true
+ property int counter : 0
+ onWidthChanged: { ++counter; }
+ }
+ }
+ }
+ }
+
+ function test_countGeometryChanges() {
+
+ var stack = countGeometryChanges_Component.createObject(container)
+ compare(stack.currentIndex, 0)
+ compare(stack.col.width, 100)
+ compare(stack.col.height, 110)
+ compare(stack.row.width, 100)
+ compare(stack.row.height, 100)
+ verify(stack.col.r1.counter <= 2)
+ compare(stack.col.r2.counter, 1)
+ verify(stack.col.r3.counter <= 2)
+ verify(stack.col.counter <= 2)
+ compare(stack.row.counter, 1) // not visible, will only receive the initial geometry change
+ compare(stack.row.r5.counter, 0)
+ stack.destroy()
+ }
+
+ Component {
+ id: layoutItem_Component
+ Rectangle {
+ implicitWidth: 20
+ implicitHeight: 20
+ }
+ }
+
+ Component {
+ id: emtpy_StackLayout_Component
+ StackLayout {
+ property int num_onCountChanged: 0
+ property int num_onCurrentIndexChanged: 0
+ onCountChanged: { ++num_onCountChanged; }
+ onCurrentIndexChanged: { ++num_onCurrentIndexChanged; }
+ }
+ }
+
+ function test_addAndRemoveItems()
+ {
+ var stack = emtpy_StackLayout_Component.createObject(container)
+ stack.currentIndex = 2
+ compare(stack.implicitWidth, 0)
+ compare(stack.implicitHeight, 0)
+
+ var rect0 = layoutItem_Component.createObject(stack)
+ compare(stack.implicitWidth, 20)
+ compare(stack.implicitHeight, 20)
+ compare(rect0.visible, false)
+
+ var rect1 = layoutItem_Component.createObject(stack)
+ rect1.Layout.preferredWidth = 30
+ rect1.Layout.preferredHeight = 10
+ compare(stack.implicitWidth, 30)
+ compare(stack.implicitHeight, 20)
+ compare(rect0.visible, false)
+ compare(rect1.visible, false)
+
+ var rect2 = layoutItem_Component.createObject(stack)
+ rect2.x = 42 // ### items in a stacklayout will have their x and y positions discarded.
+ rect2.y = 42
+ rect2.Layout.preferredWidth = 80
+ rect2.Layout.preferredHeight = 30
+ rect2.Layout.fillWidth = true
+ compare(stack.implicitWidth, 80)
+ compare(stack.implicitHeight, 30)
+ compare(rect0.visible, false)
+ compare(rect1.visible, false)
+ compare(rect2.visible, true)
+ compare(geometry(rect2), geometry(stack))
+
+ rect2.destroy()
+ wait(0) // this will hopefully effectuate the destruction of the object
+ compare(stack.implicitWidth, 30)
+ compare(stack.implicitHeight, 20)
+
+ rect0.destroy()
+ wait(0)
+ compare(stack.implicitWidth, 30)
+ compare(stack.implicitHeight, 10)
+
+ rect1.destroy()
+ wait(0)
+ compare(stack.implicitWidth, 0)
+ compare(stack.implicitHeight, 0)
+
+ stack.destroy()
+ }
+
+ function test_sizeHint_data() {
+ return [
+ { tag: "propagateNone", layoutHints: [10, 20, 30], childHints: [11, 21, 31], expected:[10, 20, Number.POSITIVE_INFINITY]},
+ { tag: "propagateMinimumWidth", layoutHints: [-1, 20, 30], childHints: [10, 21, 31], expected:[10, 20, Number.POSITIVE_INFINITY]},
+ { tag: "propagatePreferredWidth", layoutHints: [10, -1, 30], childHints: [11, 20, 31], expected:[10, 20, Number.POSITIVE_INFINITY]},
+ { tag: "propagateMaximumWidth", layoutHints: [10, 20, -1], childHints: [11, 21, 30], expected:[10, 20, Number.POSITIVE_INFINITY]},
+ { tag: "propagateAll", layoutHints: [-1, -1, -1], childHints: [10, 20, 30], expected:[10, 20, Number.POSITIVE_INFINITY]},
+ { tag: "propagateCrazy", layoutHints: [-1, -1, -1], childHints: [40, 21, 30], expected:[30, 30, Number.POSITIVE_INFINITY]},
+ { tag: "expandMinToExplicitPref", layoutHints: [-1, 1, -1], childHints: [11, 21, 31], expected:[ 1, 1, Number.POSITIVE_INFINITY]},
+ { tag: "expandMaxToExplicitPref", layoutHints: [-1, 99, -1], childHints: [11, 21, 31], expected:[11, 99, Number.POSITIVE_INFINITY]},
+ { tag: "expandAllToExplicitMin", layoutHints: [99, -1, -1], childHints: [11, 21, 31], expected:[99, 99, Number.POSITIVE_INFINITY]},
+ { tag: "expandPrefToExplicitMin", layoutHints: [24, -1, -1], childHints: [11, 21, 31], expected:[24, 24, Number.POSITIVE_INFINITY]},
+ { tag: "boundPrefToExplicitMax", layoutHints: [-1, -1, 19], childHints: [11, 21, 31], expected:[11, 19, Number.POSITIVE_INFINITY]},
+ { tag: "boundAllToExplicitMax", layoutHints: [-1, -1, 9], childHints: [11, 21, 31], expected:[ 9, 9, Number.POSITIVE_INFINITY]},
+ ];
+ }
+
+ function itemSizeHints(item) {
+ return [item.Layout.minimumWidth, item.implicitWidth, item.Layout.maximumWidth]
+ }
+ Component {
+ id: stacklayout_sizeHint_Component
+ StackLayout {
+ property int implicitWidthChangedCount : 0
+ onImplicitWidthChanged: { ++implicitWidthChangedCount }
+ ColumnLayout {
+ Rectangle {
+ id: r1
+ color: "red"
+ Layout.minimumWidth: 1
+ Layout.preferredWidth: 2
+ Layout.maximumWidth: 3
+
+ Layout.minimumHeight: 20
+ Layout.preferredHeight: 20
+ Layout.maximumHeight: 20
+ Layout.fillWidth: true
+ }
+ }
+ }
+ }
+
+ function test_sizeHint(data) {
+ var layout = stacklayout_sizeHint_Component.createObject(container)
+
+ var col = layout.children[0]
+ col.Layout.minimumWidth = data.layoutHints[0]
+ col.Layout.preferredWidth = data.layoutHints[1]
+ col.Layout.maximumWidth = data.layoutHints[2]
+
+ var child = col.children[0]
+ if (data.implicitWidth !== undefined) {
+ child.implicitWidth = data.implicitWidth
+ }
+ child.Layout.minimumWidth = data.childHints[0]
+ child.Layout.preferredWidth = data.childHints[1]
+ child.Layout.maximumWidth = data.childHints[2]
+
+ var effectiveSizeHintResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth]
+ compare(effectiveSizeHintResult, data.expected)
+ layout.destroy()
+ }
+
+ Component {
+ id: stacklayout_addIgnoredItem_Component
+ StackLayout {
+ Repeater {
+ id: rep
+ model: 1
+ Rectangle {
+ id: r
+ }
+ }
+ }
+ }
+
+ // Items with no size information is ignored.
+ function test_addIgnoredItem()
+ {
+ var stack = stacklayout_addIgnoredItem_Component.createObject(container)
+ compare(stack.count, 1)
+ compare(stack.implicitWidth, 0)
+ compare(stack.implicitHeight, 0)
+ var r = stack.children[0]
+ r.Layout.preferredWidth = 20
+ r.Layout.preferredHeight = 30
+ compare(stack.count, 1)
+ compare(stack.implicitWidth, 20)
+ compare(stack.implicitHeight, 30)
+ stack.destroy();
+ }
+
+ function test_dontCrashWhenAnchoredToAWindow() {
+ var test_layoutStr =
+ 'import QtQuick 2.2; \
+ import QtQuick.Window 2.1; \
+ import QtQuick.Layouts 1.3; \
+ Window { \
+ visible: true; \
+ width: stack.implicitWidth; \
+ height: stack.implicitHeight; \
+ StackLayout { \
+ id: stack; \
+ currentIndex: 0; \
+ anchors.fill: parent; \
+ Rectangle { \
+ color: "red"; \
+ implicitWidth: 300; \
+ implicitHeight: 200; \
+ } \
+ } \
+ } '
+
+ var lay = Qt.createQmlObject(test_layoutStr, container, '');
+ tryCompare(lay, 'width', 300);
+ lay.destroy()
+ }
+
+ Component {
+ id: test_dontCrashWhenChildIsResizedToNull_Component
+ StackLayout {
+ property alias rect : _rect
+ Rectangle {
+ id: _rect;
+ color: "red"
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ }
+ }
+
+ function test_dontCrashWhenChildIsResizedToNull() {
+ var layout = test_dontCrashWhenChildIsResizedToNull_Component.createObject(container)
+ layout.rect.width = 0
+ layout.width = 222 // trigger a rearrange with a valid size
+ layout.height = 222
+ }
+
+ Component {
+ id: test_currentIndex_Component
+ StackLayout {
+ currentIndex: 1
+ Text {
+ text: "0"
+ }
+ Text {
+ text: "1"
+ }
+ }
+ }
+
+ function test_currentIndex() {
+ var layout = test_currentIndex_Component.createObject(container)
+ var c0 = layout.children[0]
+ var c1 = layout.children[1]
+ compare(layout.currentIndex, 1)
+ tryCompare(layout, 'visible', true)
+ compare(c0.visible, false)
+ compare(c1.visible, true)
+ layout.currentIndex = 0
+ compare(c0.visible, true)
+ compare(c1.visible, false)
+ var c2 = layoutItem_Component.createObject(layout)
+ compare(c2.visible, false)
+
+ /*
+ * destroy the current item and check if visibility advances to next
+ */
+ c0.destroy()
+ tryCompare(c1, 'visible', true)
+ compare(c2.visible, false)
+ c1.destroy()
+ tryCompare(c2, 'visible', true)
+ c2.destroy()
+ tryCompare(layout, 'currentIndex', 0)
+
+ layout.destroy()
+
+ /*
+ * Test the default/implicit value of currentIndex, either -1 (if empty) or 0:
+ */
+ layout = emtpy_StackLayout_Component.createObject(container)
+ tryCompare(layout, 'visible', true)
+ compare(layout.currentIndex, -1)
+ compare(layout.num_onCurrentIndexChanged, 0)
+ // make it non-empty
+ c0 = layoutItem_Component.createObject(layout)
+ compare(layout.currentIndex, 0)
+ compare(layout.num_onCurrentIndexChanged, 1)
+ compare(c0.visible, true)
+ // make it empty again
+ c0.destroy()
+ wait(0)
+ compare(layout.currentIndex, -1)
+ //tryCompare(layout, 'currentIndex', -1)
+ compare(layout.num_onCurrentIndexChanged, 2)
+
+ /*
+ * Check that explicit value doesn't change,
+ * and that no items are visible if the index is invalid/out of range
+ */
+ layout.currentIndex = 2
+ compare(layout.currentIndex, 2)
+ compare(layout.num_onCurrentIndexChanged, 3)
+ c0 = layoutItem_Component.createObject(layout)
+ compare(layout.currentIndex, 2)
+ compare(c0.visible, false)
+
+ c1 = layoutItem_Component.createObject(layout)
+ compare(layout.currentIndex, 2)
+ compare(c0.visible, false)
+ compare(c1.visible, false)
+
+ c2 = layoutItem_Component.createObject(layout)
+ compare(layout.currentIndex, 2)
+ compare(c0.visible, false)
+ compare(c1.visible, false)
+ compare(c2.visible, true)
+
+ c2.destroy()
+ wait(0)
+ compare(layout.currentIndex, 2)
+ compare(c0.visible, false)
+ compare(c1.visible, false)
+ c1.destroy()
+ wait(0)
+ compare(layout.currentIndex, 2)
+ compare(c0.visible, false)
+ c0.destroy()
+ wait(0)
+ compare(layout.currentIndex, 2)
+ compare(layout.num_onCurrentIndexChanged, 3)
+ }
+
+ function test_count() {
+ var layout = emtpy_StackLayout_Component.createObject(container)
+ tryCompare(layout, 'visible', true)
+ compare(layout.count, 0)
+ compare(layout.currentIndex, -1)
+ compare(layout.num_onCountChanged, 0)
+ compare(layout.num_onCurrentIndexChanged, 0)
+ var c0 = layoutItem_Component.createObject(layout)
+ compare(layout.count, 1)
+ compare(layout.currentIndex, 0)
+ compare(layout.num_onCurrentIndexChanged, 1)
+ compare(layout.num_onCountChanged, 1)
+ }
+ }
+}