aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@theqtcompany.com>2015-07-16 12:00:55 +0200
committerMitch Curtis <mitch.curtis@theqtcompany.com>2015-08-06 11:43:48 +0000
commit293fc5e8f7df1b60a07d2e7e489e57059bb021bc (patch)
tree7ea3921797c2541c379cc91eb1f0c4f30d575fbc /src
parent1448ee0d1c02280aa33424f992b2f26d74615a43 (diff)
Add support for ListView to Tumbler.
This enables creation of non-wrapping Tumblers. Change-Id: I0e21b860b84c456c0651923e87217cafc42c69b7 Reviewed-by: Mitch Curtis <mitch.curtis@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r--src/extras/doc/images/qtquickextras2-tumbler-wrap.gifbin0 -> 28883 bytes
-rw-r--r--src/extras/qquicktumbler.cpp174
-rw-r--r--src/extras/qquicktumbler_p.h4
3 files changed, 146 insertions, 32 deletions
diff --git a/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif b/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif
new file mode 100644
index 00000000..c7624599
--- /dev/null
+++ b/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif
Binary files differ
diff --git a/src/extras/qquicktumbler.cpp b/src/extras/qquicktumbler.cpp
index c5e32947..f278c9ca 100644
--- a/src/extras/qquicktumbler.cpp
+++ b/src/extras/qquicktumbler.cpp
@@ -49,7 +49,21 @@ QT_BEGIN_NAMESPACE
\ingroup containers
\brief A spinnable wheel of items that can be selected.
- TODO
+ \code
+ Tumbler {
+ model: 5
+ }
+ \endcode
+
+ \section1 Non-wrapping Tumbler
+
+ The default contentItem of Tumbler is a \l PathView, which wraps when it
+ reaches the top and bottom. To achieve a non-wrapping Tumbler, use ListView
+ as the contentItem:
+
+ \snippet tst_tumbler.qml contentItem
+
+ \image qtquickextras2-tumbler-wrap.gif
*/
class QQuickTumblerPrivate : public QQuickControlPrivate, public QQuickItemChangeListener
@@ -63,6 +77,10 @@ public:
{
}
+ ~QQuickTumblerPrivate()
+ {
+ }
+
QVariant model;
QQmlComponent *delegate;
int visibleItemCount;
@@ -84,11 +102,55 @@ static QList<QQuickItem *> contentItemChildItems(QQuickItem *contentItem)
return flickable ? flickable->contentItem()->childItems() : contentItem->childItems();
}
+namespace {
+ static inline qreal delegateHeight(const QQuickItem *contentItem, qreal topPadding, qreal bottomPadding, int visibleItemCount)
+ {
+ // TODO: can we/do we want to support spacing?
+ return (contentItem->height()/* - qMax(0, itemCount - 1) * spacing*/ - topPadding - bottomPadding) / visibleItemCount;
+ }
+
+ enum ContentItemType {
+ UnsupportedContentItemType,
+ PathViewContentItem,
+ ListViewContentItem
+ };
+
+ static inline QQuickItem *actualContentItem(QQuickItem *rootContentItem, ContentItemType contentType)
+ {
+ if (contentType == PathViewContentItem)
+ return rootContentItem;
+ else if (contentType == ListViewContentItem)
+ return qobject_cast<QQuickFlickable*>(rootContentItem)->contentItem();
+
+ return Q_NULLPTR;
+ }
+
+ static inline ContentItemType contentItemType(QQuickItem *rootContentItem)
+ {
+ if (rootContentItem->inherits("QQuickPathView"))
+ return PathViewContentItem;
+ else if (rootContentItem->inherits("QQuickListView"))
+ return ListViewContentItem;
+
+ return UnsupportedContentItemType;
+ }
+
+ static inline ContentItemType contentItemTypeFromDelegate(QQuickItem *delegateItem)
+ {
+ if (delegateItem->parentItem()->inherits("QQuickPathView")) {
+ return PathViewContentItem;
+ } else if (delegateItem->parentItem()->parentItem()
+ && delegateItem->parentItem()->parentItem()->inherits("QQuickListView")) {
+ return ListViewContentItem;
+ }
+
+ return UnsupportedContentItemType;
+ }
+}
+
void QQuickTumblerPrivate::updateItemHeights()
{
- // TODO: can we/do we want to support spacing?
- const qreal itemHeight = (contentItem->height()/* - qMax(0, itemCount - 1) * spacing*/
- - topPadding - bottomPadding) / visibleItemCount;
+ const qreal itemHeight = delegateHeight(contentItem, topPadding, bottomPadding, visibleItemCount);
foreach (QQuickItem *childItem, contentItemChildItems(contentItem))
childItem->setHeight(itemHeight);
}
@@ -114,6 +176,11 @@ void QQuickTumblerPrivate::itemChildRemoved(QQuickItem *, QQuickItem *)
QQuickTumbler::QQuickTumbler(QQuickItem *parent) :
QQuickControl(*(new QQuickTumblerPrivate), parent)
{
+ setActiveFocusOnTab(true);
+}
+
+QQuickTumbler::~QQuickTumbler()
+{
}
/*!
@@ -199,7 +266,7 @@ void QQuickTumbler::setDelegate(QQmlComponent *delegate)
\qmlproperty int QtQuickExtras2::Tumbler::visibleItemCount
This property holds the number of items visible in the tumbler. It must be
- an odd number.
+ an odd number, as the current item is always vertically centered.
*/
int QQuickTumbler::visibleItemCount() const
{
@@ -221,7 +288,7 @@ QQuickTumblerAttached *QQuickTumbler::qmlAttachedProperties(QObject *object)
{
QQuickItem *delegateItem = qobject_cast<QQuickItem *>(object);
if (!delegateItem) {
- qWarning() << "Attached properties of Tumbler must be accessed from within a delegate item";
+ qWarning() << "Tumbler: attached properties of Tumbler must be accessed from within a delegate item";
return Q_NULLPTR;
}
@@ -264,17 +331,26 @@ void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
disconnect(oldItem, SIGNAL(currentItemChanged()), this, SIGNAL(currentItemChanged()));
disconnect(oldItem, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
- QQuickItemPrivate *oldItemPrivate = QQuickItemPrivate::get(oldItem);
- oldItemPrivate->removeItemChangeListener(d, QQuickItemPrivate::Children);
+ ContentItemType oldContentItemType = contentItemType(oldItem);
+ QQuickItem *actualOldContentItem = actualContentItem(oldItem, oldContentItemType);
+ QQuickItemPrivate *actualContentItemPrivate = QQuickItemPrivate::get(actualOldContentItem);
+ actualContentItemPrivate->removeItemChangeListener(d, QQuickItemPrivate::Children);
}
if (newItem) {
+ ContentItemType contentType = contentItemType(newItem);
+ if (contentType == UnsupportedContentItemType) {
+ qWarning() << "Tumbler: contentItems other than PathView and ListView are not supported";
+ return;
+ }
+
connect(newItem, SIGNAL(currentIndexChanged()), this, SIGNAL(currentIndexChanged()));
connect(newItem, SIGNAL(currentItemChanged()), this, SIGNAL(currentItemChanged()));
connect(newItem, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
- QQuickItemPrivate *newItemPrivate = QQuickItemPrivate::get(newItem);
- newItemPrivate->addItemChangeListener(d, QQuickItemPrivate::Children);
+ QQuickItem *actualNewContentItem = actualContentItem(newItem, contentType);
+ QQuickItemPrivate *actualContentItemPrivate = QQuickItemPrivate::get(actualNewContentItem);
+ actualContentItemPrivate->addItemChangeListener(d, QQuickItemPrivate::Children);
// If the previous currentIndex is -1, it means we had no contentItem previously.
if (previousCurrentIndex != -1) {
@@ -311,23 +387,28 @@ public:
displacement(1)
{
if (!delegateItem->parentItem()) {
- qWarning() << "Attached properties of Tumbler must be accessed from within a delegate item that has a parent";
+ qWarning() << "Tumbler: attached properties must be accessed from within a delegate item that has a parent";
return;
}
QVariant indexContextProperty = qmlContext(delegateItem)->contextProperty(QStringLiteral("index"));
if (!indexContextProperty.isValid()) {
- qWarning() << "Attempting to access attached property on item without an \"index\" property";
+ qWarning() << "Tumbler: attempting to access attached property on item without an \"index\" property";
return;
}
index = indexContextProperty.toInt();
- if (!delegateItem->parentItem()->inherits("QQuickPathView")) {
- qWarning() << "contentItems other than PathView are not currently supported";
+ const ContentItemType contentItemType = contentItemTypeFromDelegate(delegateItem);
+ if (contentItemType == UnsupportedContentItemType)
return;
- }
- tumbler = qobject_cast<QQuickTumbler* >(delegateItem->parentItem()->parentItem());
+ // ListView has an "additional" content item.
+ tumbler = qobject_cast<QQuickTumbler* >(contentItemType == PathViewContentItem
+ ? delegateItem->parentItem()->parentItem() : delegateItem->parentItem()->parentItem()->parentItem());
+ Q_ASSERT(tumbler);
+ }
+
+ ~QQuickTumblerAttachedPrivate() {
}
void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE;
@@ -354,7 +435,7 @@ void QQuickTumblerAttachedPrivate::itemChildAdded(QQuickItem *, QQuickItem *)
_q_calculateDisplacement();
}
-void QQuickTumblerAttachedPrivate::itemChildRemoved(QQuickItem *, QQuickItem *child)
+void QQuickTumblerAttachedPrivate::itemChildRemoved(QQuickItem *item, QQuickItem *child)
{
_q_calculateDisplacement();
@@ -363,7 +444,9 @@ void QQuickTumblerAttachedPrivate::itemChildRemoved(QQuickItem *, QQuickItem *ch
// that our properties are attached to. If we don't remove the change
// listener, the contentItem will attempt to notify a destroyed
// listener, causing a crash.
- QQuickItemPrivate *p = QQuickItemPrivate::get(tumbler->contentItem());
+
+ // item is the "actual content item" of Tumbler's contentItem, i.e. a PathView or ListView.contentItem
+ QQuickItemPrivate *p = QQuickItemPrivate::get(item);
p->removeItemChangeListener(this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Children);
}
}
@@ -371,20 +454,35 @@ void QQuickTumblerAttachedPrivate::itemChildRemoved(QQuickItem *, QQuickItem *ch
void QQuickTumblerAttachedPrivate::_q_calculateDisplacement()
{
const int previousDisplacement = displacement;
+ displacement = 0;
- displacement = 1;
+ // This can happen in tests, so it may happen in normal usage too.
+ if (tumbler->count() == 0)
+ return;
- // TODO: ListView has no offset property, need to try using contentY instead.
- if (tumbler && tumbler->contentItem()->inherits("QQuickListView"))
+ ContentItemType contentType = contentItemType(tumbler->contentItem());
+ if (contentType == UnsupportedContentItemType)
return;
- qreal offset = tumbler->contentItem()->property("offset").toReal();
- displacement = tumbler->count() - index - offset;
- int halfVisibleItems = tumbler->visibleItemCount() / 2 + 1;
- if (displacement > halfVisibleItems)
- displacement -= tumbler->count();
- else if (displacement < -halfVisibleItems)
- displacement += tumbler->count();
+ qreal offset = 0;
+
+ if (contentType == PathViewContentItem) {
+ offset = tumbler->contentItem()->property("offset").toReal();
+
+ displacement = tumbler->count() - index - offset;
+ int halfVisibleItems = tumbler->visibleItemCount() / 2 + 1;
+ if (displacement > halfVisibleItems)
+ displacement -= tumbler->count();
+ else if (displacement < -halfVisibleItems)
+ displacement += tumbler->count();
+ } else {
+ const qreal contentY = tumbler->contentItem()->property("contentY").toReal();
+ const qreal delegateH = delegateHeight(tumbler->contentItem(), tumbler->topPadding(), tumbler->bottomPadding(), tumbler->visibleItemCount());
+ const qreal preferredHighlightBegin = tumbler->contentItem()->property("preferredHighlightBegin").toReal();
+ // Tumbler's displacement goes from negative at the top to positive towards the bottom, so we must switch this around.
+ const qreal reverseDisplacement = (contentY + preferredHighlightBegin) / delegateH;
+ displacement = reverseDisplacement - index;
+ }
Q_Q(QQuickTumblerAttached);
if (displacement != previousDisplacement)
@@ -396,15 +494,27 @@ QQuickTumblerAttached::QQuickTumblerAttached(QQuickItem *delegateItem) :
{
Q_D(QQuickTumblerAttached);
if (d->tumbler) {
- // TODO: in case of listview, listen to contentItem of contentItem
- QQuickItemPrivate *p = QQuickItemPrivate::get(d->tumbler->contentItem());
+ QQuickItem *rootContentItem = d->tumbler->contentItem();
+ const ContentItemType contentType = contentItemType(rootContentItem);
+ QQuickItemPrivate *p = QQuickItemPrivate::get(actualContentItem(rootContentItem, contentType));
p->addItemChangeListener(d, QQuickItemPrivate::Geometry | QQuickItemPrivate::Children);
- // TODO: in case of ListView, listen to contentY changed
- connect(d->tumbler->contentItem(), SIGNAL(offsetChanged()), this, SLOT(_q_calculateDisplacement()));
+ const char *contentItemSignal = contentType == PathViewContentItem
+ ? SIGNAL(offsetChanged()) : SIGNAL(contentYChanged());
+ connect(d->tumbler->contentItem(), contentItemSignal, this, SLOT(_q_calculateDisplacement()));
}
}
+QQuickTumblerAttached::~QQuickTumblerAttached()
+{
+}
+
+QQuickTumbler *QQuickTumblerAttached::tumbler() const
+{
+ Q_D(const QQuickTumblerAttached);
+ return d->tumbler;
+}
+
qreal QQuickTumblerAttached::displacement() const
{
Q_D(const QQuickTumblerAttached);
diff --git a/src/extras/qquicktumbler_p.h b/src/extras/qquicktumbler_p.h
index a2e493e5..4dc29ba9 100644
--- a/src/extras/qquicktumbler_p.h
+++ b/src/extras/qquicktumbler_p.h
@@ -70,6 +70,7 @@ class Q_QUICKEXTRAS_EXPORT QQuickTumbler : public QQuickControl
public:
explicit QQuickTumbler(QQuickItem *parent = Q_NULLPTR);
+ ~QQuickTumbler();
QVariant model() const;
void setModel(const QVariant &model);
@@ -114,11 +115,14 @@ class QQuickTumblerAttachedPrivate;
class Q_QUICKEXTRAS_EXPORT QQuickTumblerAttached : public QObject
{
Q_OBJECT
+ Q_PROPERTY(QQuickTumbler *tumbler READ tumbler CONSTANT)
Q_PROPERTY(qreal displacement READ displacement NOTIFY displacementChanged FINAL)
public:
explicit QQuickTumblerAttached(QQuickItem *delegateItem);
+ ~QQuickTumblerAttached();
+ QQuickTumbler *tumbler() const;
qreal displacement() const;
Q_SIGNALS: