Support margins in Flickable.
authorMartin Jones <martin.jones@nokia.com>
Fri, 23 Sep 2011 06:13:51 +0000 (16:13 +1000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 26 Sep 2011 01:24:42 +0000 (03:24 +0200)
It is sometimes desireable to leave a margin/add decoration around the
content of a Flickable.  This adds topMargin, leftMargin, bottomMargin
rightMargin, xOrigin and yOrigin properties to Flickable.

Task-number: QTBUG-21362
Change-Id: Ia24ea4c63e7a8de683b68100baac782c6f3a66bb
Reviewed-on: http://codereview.qt-project.org/5445
Reviewed-by: Bea Lam <bea.lam@nokia.com>

13 files changed:
doc/src/declarative/whatsnew.qdoc
src/declarative/items/qsgflickable.cpp
src/declarative/items/qsgflickable_p.h
src/declarative/items/qsgflickable_p_p.h
src/declarative/items/qsgitemview.cpp
src/declarative/items/qsgitemview_p.h
src/declarative/items/qsgitemview_p_p.h
tests/auto/declarative/qsgflickable/data/margins.qml [new file with mode: 0644]
tests/auto/declarative/qsgflickable/tst_qsgflickable.cpp
tests/auto/declarative/qsggridview/data/margins.qml [new file with mode: 0644]
tests/auto/declarative/qsggridview/tst_qsggridview.cpp
tests/auto/declarative/qsglistview/data/margins.qml [new file with mode: 0644]
tests/auto/declarative/qsglistview/tst_qsglistview.cpp

index da00160..ea575a6 100644 (file)
@@ -94,6 +94,7 @@ The Binding element can now be used as a value source, and will also restore any
 set binding when its \e when clause becomes false.
 
 Flickable: added dragging, draggingHorizontally and draggingVerically properties.
+Added topMargin, bottomMargin, leftMargin, rightMargin, xOrigin, yOrigin properties.
 
 Image has two new properties: horizontalAlignment and verticalAlignment. It also has a new value for
 fillMode (Image.Pad) that does not transform the image.
index 36d7db2..13cb966 100644 (file)
@@ -423,6 +423,16 @@ void QSGFlickablePrivate::updateBeginningEnd()
         atBoundaryChange = true;
     }
 
+    if (vData.extentsChanged) {
+        vData.extentsChanged = false;
+        emit q->yOriginChanged();
+    }
+
+    if (hData.extentsChanged) {
+        hData.extentsChanged = false;
+        emit q->xOriginChanged();
+    }
+
     if (atBoundaryChange)
         emit q->isAtBoundaryChanged();
 
@@ -594,6 +604,7 @@ qreal QSGFlickable::contentX() const
 void QSGFlickable::setContentX(qreal pos)
 {
     Q_D(QSGFlickable);
+    d->hData.explicitValue = true;
     d->timeline.reset(d->hData.move);
     d->vTime = d->timeline.time();
     movementXEnding();
@@ -612,6 +623,7 @@ qreal QSGFlickable::contentY() const
 void QSGFlickable::setContentY(qreal pos)
 {
     Q_D(QSGFlickable);
+    d->vData.explicitValue = true;
     d->timeline.reset(d->vData.move);
     d->vTime = d->timeline.time();
     movementYEnding();
@@ -1137,23 +1149,37 @@ void QSGFlickable::timerEvent(QTimerEvent *event)
 
 qreal QSGFlickable::minYExtent() const
 {
-    return 0.0;
+    Q_D(const QSGFlickable);
+    return d->vData.startMargin;
 }
 
 qreal QSGFlickable::minXExtent() const
 {
-    return 0.0;
+    Q_D(const QSGFlickable);
+    return d->hData.startMargin;
 }
 
 /* returns -ve */
 qreal QSGFlickable::maxXExtent() const
 {
-    return width() - vWidth();
+    Q_D(const QSGFlickable);
+    return width() - vWidth() - d->hData.endMargin;
 }
 /* returns -ve */
 qreal QSGFlickable::maxYExtent() const
 {
-    return height() - vHeight();
+    Q_D(const QSGFlickable);
+    return height() - vHeight() - d->vData.endMargin;
+}
+
+void QSGFlickable::componentComplete()
+{
+    Q_D(QSGFlickable);
+    QSGItem::componentComplete();
+    if (!d->hData.explicitValue && d->hData.startMargin != 0.)
+        setContentX(-minXExtent());
+    if (!d->vData.explicitValue && d->vData.startMargin != 0.)
+        setContentY(-minYExtent());
 }
 
 void QSGFlickable::viewportMoved()
@@ -1372,6 +1398,7 @@ void QSGFlickable::setContentWidth(qreal w)
         d->contentItem->setWidth(width());
     else
         d->contentItem->setWidth(w);
+    d->hData.markExtentsDirty();
     // Make sure that we're entirely in view.
     if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
         d->fixupMode = QSGFlickablePrivate::Immediate;
@@ -1400,6 +1427,7 @@ void QSGFlickable::setContentHeight(qreal h)
         d->contentItem->setHeight(height());
     else
         d->contentItem->setHeight(h);
+    d->vData.markExtentsDirty();
     // Make sure that we're entirely in view.
     if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
         d->fixupMode = QSGFlickablePrivate::Immediate;
@@ -1413,8 +1441,124 @@ void QSGFlickable::setContentHeight(qreal h)
 }
 
 /*!
+    \qmlproperty real QtQuick2::Flickable::topMargin
+    \qmlproperty real QtQuick2::Flickable::leftMargin
+    \qmlproperty real QtQuick2::Flickable::bottomMargin
+    \qmlproperty real QtQuick2::Flickable::rightMargin
+
+    These properties hold the margins around the content.  This space is reserved
+    in addition to the contentWidth and contentHeight.
+*/
+
+
+qreal QSGFlickable::topMargin() const
+{
+    Q_D(const QSGFlickable);
+    return d->vData.startMargin;
+}
+
+void QSGFlickable::setTopMargin(qreal m)
+{
+    Q_D(QSGFlickable);
+    if (d->vData.startMargin == m)
+        return;
+    d->vData.startMargin = m;
+    d->vData.markExtentsDirty();
+    if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
+        d->fixupMode = QSGFlickablePrivate::Immediate;
+        d->fixupY();
+    }
+    emit topMarginChanged();
+    d->updateBeginningEnd();
+}
+
+qreal QSGFlickable::bottomMargin() const
+{
+    Q_D(const QSGFlickable);
+    return d->vData.endMargin;
+}
+
+void QSGFlickable::setBottomMargin(qreal m)
+{
+    Q_D(QSGFlickable);
+    if (d->vData.endMargin == m)
+        return;
+    d->vData.endMargin = m;
+    d->vData.markExtentsDirty();
+    if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
+        d->fixupMode = QSGFlickablePrivate::Immediate;
+        d->fixupY();
+    }
+    emit bottomMarginChanged();
+    d->updateBeginningEnd();
+}
+
+qreal QSGFlickable::leftMargin() const
+{
+    Q_D(const QSGFlickable);
+    return d->hData.startMargin;
+}
+
+void QSGFlickable::setLeftMargin(qreal m)
+{
+    Q_D(QSGFlickable);
+    if (d->hData.startMargin == m)
+        return;
+    d->hData.startMargin = m;
+    d->hData.markExtentsDirty();
+    if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
+        d->fixupMode = QSGFlickablePrivate::Immediate;
+        d->fixupX();
+    }
+    emit leftMarginChanged();
+    d->updateBeginningEnd();
+}
+
+qreal QSGFlickable::rightMargin() const
+{
+    Q_D(const QSGFlickable);
+    return d->hData.endMargin;
+}
+
+void QSGFlickable::setRightMargin(qreal m)
+{
+    Q_D(QSGFlickable);
+    if (d->hData.endMargin == m)
+        return;
+    d->hData.endMargin = m;
+    d->hData.markExtentsDirty();
+    if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
+        d->fixupMode = QSGFlickablePrivate::Immediate;
+        d->fixupX();
+    }
+    emit rightMarginChanged();
+    d->updateBeginningEnd();
+}
+
+/*!
+    \qmlproperty real QtQuick2::Flickable::xOrigin
+    \qmlproperty real QtQuick2::Flickable::yOrigin
+
+    These properties hold the origin of the content.  This is usually (0,0), however
+    ListView and GridView may have an arbitrary origin due to delegate size variation,
+    or item insertion/removal outside the visible region.
+*/
+
+qreal QSGFlickable::yOrigin() const
+{
+    Q_D(const QSGFlickable);
+    return -minYExtent() + d->vData.startMargin;
+}
+
+qreal QSGFlickable::xOrigin() const
+{
+    Q_D(const QSGFlickable);
+    return -minXExtent() + d->hData.startMargin;
+}
+
+
+/*!
     \qmlmethod QtQuick2::Flickable::resizeContent(real width, real height, QPointF center)
-    \preliminary
 
     Resizes the content to \a width x \a height about \a center.
 
@@ -1453,7 +1597,6 @@ void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center)
 
 /*!
     \qmlmethod QtQuick2::Flickable::returnToBounds()
-    \preliminary
 
     Ensures the content is within legal bounds.
 
index d638b92..54581a5 100644 (file)
@@ -63,6 +63,14 @@ class Q_AUTOTEST_EXPORT QSGFlickable : public QSGItem
     Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
     Q_PROPERTY(QSGItem *contentItem READ contentItem CONSTANT)
 
+    Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged)
+    Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged)
+    Q_PROPERTY(qreal yOrigin READ yOrigin NOTIFY yOriginChanged)
+
+    Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged)
+    Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged)
+    Q_PROPERTY(qreal xOrigin READ xOrigin NOTIFY xOriginChanged)
+
     Q_PROPERTY(qreal horizontalVelocity READ horizontalVelocity NOTIFY horizontalVelocityChanged)
     Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged)
 
@@ -122,6 +130,21 @@ public:
     qreal contentY() const;
     virtual void setContentY(qreal pos);
 
+    qreal topMargin() const;
+    void setTopMargin(qreal m);
+
+    qreal bottomMargin() const;
+    void setBottomMargin(qreal m);
+
+    qreal leftMargin() const;
+    void setLeftMargin(qreal m);
+
+    qreal rightMargin() const;
+    void setRightMargin(qreal m);
+
+    virtual qreal yOrigin() const;
+    virtual qreal xOrigin() const;
+
     bool isMoving() const;
     bool isMovingHorizontally() const;
     bool isMovingVertically() const;
@@ -169,6 +192,12 @@ Q_SIGNALS:
     void contentHeightChanged();
     void contentXChanged();
     void contentYChanged();
+    void topMarginChanged();
+    void bottomMarginChanged();
+    void leftMarginChanged();
+    void rightMarginChanged();
+    void yOriginChanged();
+    void xOriginChanged();
     void movingChanged();
     void movingHorizontallyChanged();
     void movingVerticallyChanged();
@@ -219,6 +248,7 @@ protected:
     virtual qreal maxYExtent() const;
     qreal vWidth() const;
     qreal vHeight() const;
+    virtual void componentComplete();
     virtual void viewportMoved();
     virtual void geometryChanged(const QRectF &newGeometry,
                                  const QRectF &oldGeometry);
index 21c3106..b3690b4 100644 (file)
@@ -95,8 +95,10 @@ public:
 
     struct AxisData {
         AxisData(QSGFlickablePrivate *fp, void (QSGFlickablePrivate::*func)(qreal))
-            : move(fp, func), viewSize(-1), smoothVelocity(fp), atEnd(false), atBeginning(true)
-            , fixingUp(false), inOvershoot(false), dragging(false)
+            : move(fp, func), viewSize(-1), startMargin(0), endMargin(0)
+            , smoothVelocity(fp), atEnd(false), atBeginning(true)
+            , fixingUp(false), inOvershoot(false), dragging(false), extentsChanged(false)
+            , explicitValue(false), minExtentDirty(true), maxExtentDirty(true)
         {}
 
         void reset() {
@@ -106,6 +108,12 @@ public:
             inOvershoot = false;
         }
 
+        void markExtentsDirty() {
+            minExtentDirty = true;
+            maxExtentDirty = true;
+            extentsChanged = true;
+        }
+
         void addVelocitySample(qreal v, qreal maxVelocity);
         void updateVelocity();
 
@@ -117,6 +125,8 @@ public:
         qreal dragMaxBound;
         qreal velocity;
         qreal flickTarget;
+        qreal startMargin;
+        qreal endMargin;
         QSGFlickablePrivate::Velocity smoothVelocity;
         QPODVector<qreal,10> velocityBuffer;
         bool atEnd : 1;
@@ -124,6 +134,10 @@ public:
         bool fixingUp : 1;
         bool inOvershoot : 1;
         bool dragging : 1;
+        bool extentsChanged : 1;
+        bool explicitValue : 1;
+        mutable bool minExtentDirty : 1;
+        mutable bool maxExtentDirty : 1;
     };
 
     void flickX(qreal velocity);
index 291592b..63040c1 100644 (file)
@@ -401,8 +401,7 @@ void QSGItemView::setHeader(QDeclarativeComponent *headerComponent)
         d->header = 0;
         d->headerComponent = headerComponent;
 
-        d->minExtentDirty = true;
-        d->maxExtentDirty = true;
+        d->markExtentsDirty();
 
         if (isComponentComplete()) {
             d->updateHeader();
@@ -724,14 +723,12 @@ void QSGItemViewPrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeo
 
     if (header && header->item == item) {
         updateHeader();
-        minExtentDirty = true;
-        maxExtentDirty = true;
+        markExtentsDirty();
         if (!q->isMoving() && !q->isFlicking())
             fixupPosition();
     } else if (footer && footer->item == item) {
         updateFooter();
-        minExtentDirty = true;
-        maxExtentDirty = true;
+        markExtentsDirty();
         if (!q->isMoving() && !q->isFlicking())
             fixupPosition();
     }
@@ -892,8 +889,7 @@ void QSGItemView::trackedPositionChanged()
 void QSGItemView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
 {
     Q_D(QSGItemView);
-    d->maxExtentDirty = true;
-    d->minExtentDirty = true;
+    d->markExtentsDirty();
     QSGFlickable::geometryChanged(newGeometry, oldGeometry);
 }
 
@@ -904,8 +900,8 @@ qreal QSGItemView::minYExtent() const
     if (d->layoutOrientation() == Qt::Horizontal)
         return QSGFlickable::minYExtent();
 
-    if (d->minExtentDirty) {
-        d->minExtent = -d->startPosition();
+    if (d->vData.minExtentDirty) {
+        d->minExtent = d->vData.startMargin-d->startPosition();
         if (d->header)
             d->minExtent += d->headerSize();
         if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) {
@@ -914,7 +910,7 @@ qreal QSGItemView::minYExtent() const
                 d->minExtent -= d->visibleItem(0)->sectionSize();
             d->minExtent = qMax(d->minExtent, -(d->endPositionAt(0) - d->highlightRangeEnd));
         }
-        d->minExtentDirty = false;
+        d->vData.minExtentDirty = false;
     }
 
     return d->minExtent;
@@ -926,7 +922,7 @@ qreal QSGItemView::maxYExtent() const
     if (d->layoutOrientation() == Qt::Horizontal)
         return height();
 
-    if (d->maxExtentDirty) {
+    if (d->vData.maxExtentDirty) {
         if (!d->model || !d->model->count()) {
             d->maxExtent = d->header ? -d->headerSize() : 0;
             d->maxExtent += height();
@@ -940,10 +936,11 @@ qreal QSGItemView::maxYExtent() const
 
         if (d->footer)
             d->maxExtent -= d->footerSize();
+        d->maxExtent -= d->vData.endMargin;
         qreal minY = minYExtent();
         if (d->maxExtent > minY)
             d->maxExtent = minY;
-        d->maxExtentDirty = false;
+        d->vData.maxExtentDirty = false;
     }
     return d->maxExtent;
 }
@@ -954,12 +951,13 @@ qreal QSGItemView::minXExtent() const
     if (d->layoutOrientation() == Qt::Vertical)
         return QSGFlickable::minXExtent();
 
-    if (d->minExtentDirty) {
+    if (d->hData.minExtentDirty) {
         d->minExtent = -d->startPosition();
         qreal highlightStart;
         qreal highlightEnd;
         qreal endPositionFirstItem = 0;
         if (d->isContentFlowReversed()) {
+            d->minExtent += d->hData.endMargin;
             if (d->model && d->model->count())
                 endPositionFirstItem = d->positionAt(d->model->count()-1);
             else if (d->header)
@@ -974,6 +972,7 @@ qreal QSGItemView::minXExtent() const
             if (d->minExtent < maxX)
                 d->minExtent = maxX;
         } else {
+            d->minExtent += d->hData.startMargin;
             endPositionFirstItem = d->endPositionAt(0);
             highlightStart = d->highlightRangeStart;
             highlightEnd = d->highlightRangeEnd;
@@ -984,7 +983,7 @@ qreal QSGItemView::minXExtent() const
             d->minExtent += highlightStart;
             d->minExtent = qMax(d->minExtent, -(endPositionFirstItem - highlightEnd));
         }
-        d->minExtentDirty = false;
+        d->hData.minExtentDirty = false;
     }
 
     return d->minExtent;
@@ -996,7 +995,7 @@ qreal QSGItemView::maxXExtent() const
     if (d->layoutOrientation() == Qt::Vertical)
         return width();
 
-    if (d->maxExtentDirty) {
+    if (d->hData.maxExtentDirty) {
         qreal highlightStart;
         qreal highlightEnd;
         qreal lastItemPosition = 0;
@@ -1028,14 +1027,16 @@ qreal QSGItemView::maxXExtent() const
         if (d->isContentFlowReversed()) {
             if (d->header)
                 d->maxExtent -= d->headerSize();
+            d->maxExtent -= d->hData.startMargin;
         } else {
             if (d->footer)
                 d->maxExtent -= d->footerSize();
+            d->maxExtent -= d->hData.endMargin;
             qreal minX = minXExtent();
             if (d->maxExtent > minX)
                 d->maxExtent = minX;
         }
-        d->maxExtentDirty = false;
+        d->hData.maxExtentDirty = false;
     }
 
     return d->maxExtent;
@@ -1057,6 +1058,14 @@ void QSGItemView::setContentY(qreal pos)
     QSGFlickable::setContentY(pos);
 }
 
+qreal QSGItemView::xOrigin() const
+{
+    Q_D(const QSGItemView);
+    if (d->isContentFlowReversed())
+        return -maxXExtent() + d->size() - d->hData.startMargin;
+    else
+        return -minXExtent() + d->hData.startMargin;
+}
 
 void QSGItemView::updatePolish()
 {
@@ -1111,7 +1120,6 @@ QSGItemViewPrivate::QSGItemViewPrivate()
     , ownModel(false), wrap(false), lazyRelease(false), deferredRelease(false)
     , inApplyModelChanges(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
     , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
-    , minExtentDirty(true), maxExtentDirty(true)
 {
 }
 
@@ -1144,7 +1152,16 @@ qreal QSGItemViewPrivate::endPosition() const
 
 qreal QSGItemViewPrivate::contentStartPosition() const
 {
-    return -headerSize();
+    Q_Q(const QSGItemView);
+    qreal pos = -headerSize();
+    if (layoutOrientation() == Qt::Vertical)
+        pos -= vData.startMargin;
+    else if (isContentFlowReversed())
+        pos -= hData.endMargin;
+    else
+        pos -= hData.startMargin;
+
+    return pos;
 }
 
 int QSGItemViewPrivate::findLastVisibleIndex(int defaultValue) const
@@ -1263,8 +1280,7 @@ void QSGItemViewPrivate::clear()
     createHighlight();
     trackedItem = 0;
 
-    minExtentDirty = true;
-    maxExtentDirty = true;
+    markExtentsDirty();
     itemCount = 0;
 }
 
@@ -1318,8 +1334,7 @@ void QSGItemViewPrivate::refill(qreal from, qreal to, bool doBuffer)
     }
 
     if (changed) {
-        minExtentDirty = true;
-        maxExtentDirty = true;
+        markExtentsDirty();
         visibleItemsChanged();
     } else if (!doBuffer && buffer && bufferMode != NoBuffer) {
         refill(from, to, true);
@@ -1376,8 +1391,7 @@ void QSGItemViewPrivate::layout()
     layoutVisibleItems();
     refill();
 
-    minExtentDirty = true;
-    maxExtentDirty = true;
+    markExtentsDirty();
 
     updateHighlight();
 
@@ -1404,6 +1418,8 @@ bool QSGItemViewPrivate::applyModelChanges()
 
     int prevCount = itemCount;
     bool removedVisible = false;
+    bool viewportChanged = !currentChanges.pendingChanges.removes().isEmpty()
+            || !currentChanges.pendingChanges.inserts().isEmpty();
 
     FxViewItem *firstVisible = firstVisibleItem();
     FxViewItem *origVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0;
@@ -1516,8 +1532,12 @@ bool QSGItemViewPrivate::applyModelChanges()
     if (prevCount != itemCount)
         emit q->countChanged();
 
+    bool visibleAffected = removedVisible || addedVisible || !currentChanges.pendingChanges.changes().isEmpty();
+    if (!visibleAffected && viewportChanged)
+        updateViewport();
+
     inApplyModelChanges = false;
-    return removedVisible || addedVisible || !currentChanges.pendingChanges.changes().isEmpty();
+    return visibleAffected;
 }
 
 FxViewItem *QSGItemViewPrivate::createItem(int modelIndex)
index 9d25eab..7b8efbb 100644 (file)
@@ -154,6 +154,7 @@ public:
 
     virtual void setContentX(qreal pos);
     virtual void setContentY(qreal pos);
+    virtual qreal xOrigin() const;
 
 signals:
     void modelChanged();
index 73cb68c..2d39dce 100644 (file)
@@ -142,6 +142,13 @@ public:
 
     void checkVisible() const;
 
+    void markExtentsDirty() {
+        if (layoutOrientation() == Qt::Vertical)
+            vData.markExtentsDirty();
+        else
+            hData.markExtentsDirty();
+    }
+
     QDeclarativeGuard<QSGVisualModel> model;
     QVariant modelVariant;
     int itemCount;
@@ -188,8 +195,6 @@ public:
     bool autoHighlight : 1;
     bool highlightRangeStartValid : 1;
     bool highlightRangeEndValid : 1;
-    mutable bool minExtentDirty : 1;
-    mutable bool maxExtentDirty : 1;
 
 protected:
     virtual Qt::Orientation layoutOrientation() const = 0;
diff --git a/tests/auto/declarative/qsgflickable/data/margins.qml b/tests/auto/declarative/qsgflickable/data/margins.qml
new file mode 100644 (file)
index 0000000..4866bd8
--- /dev/null
@@ -0,0 +1,19 @@
+import QtQuick 2.0
+
+Flickable {
+    width: 200; height: 200
+    contentWidth: row.width; contentHeight: row.height
+
+    topMargin: 20
+    bottomMargin: 30
+    leftMargin: 40
+    rightMargin: 50
+
+    Row {
+        id: row
+        Repeater {
+            model: 4
+            Rectangle { width: 400; height: 600; color: "blue" }
+        }
+    }
+}
index e75914a..554be23 100644 (file)
@@ -80,6 +80,7 @@ private slots:
     void movingAndDragging();
     void disabled();
     void flickVelocity();
+    void margins();
 
 private:
     QDeclarativeEngine engine;
@@ -563,6 +564,65 @@ void tst_qsgflickable::flickVelocity()
     delete canvas;
 }
 
+void tst_qsgflickable::margins()
+{
+    QDeclarativeEngine engine;
+    QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/margins.qml"));
+    QSGItem *root = qobject_cast<QSGItem*>(c.create());
+    QSGFlickable *obj = qobject_cast<QSGFlickable*>(root);
+    QVERIFY(obj != 0);
+
+    // starting state
+    QCOMPARE(obj->contentX(), -40.);
+    QCOMPARE(obj->contentY(), -20.);
+    QCOMPARE(obj->contentWidth(), 1600.);
+    QCOMPARE(obj->contentHeight(), 600.);
+    QCOMPARE(obj->xOrigin(), 0.);
+    QCOMPARE(obj->yOrigin(), 0.);
+
+    // Reduce left margin
+    obj->setLeftMargin(30);
+    QTRY_COMPARE(obj->contentX(), -30.);
+
+    // Reduce top margin
+    obj->setTopMargin(20);
+    QTRY_COMPARE(obj->contentY(), -20.);
+
+    // position to the far right, including margin
+    obj->setContentX(1600 + 50 - obj->width());
+    obj->returnToBounds();
+    QTest::qWait(200);
+    QCOMPARE(obj->contentX(), 1600. + 50. - obj->width());
+
+    // position beyond the far right, including margin
+    obj->setContentX(1600 + 50 - obj->width() + 1.);
+    obj->returnToBounds();
+    QTRY_COMPARE(obj->contentX(), 1600. + 50. - obj->width());
+
+    // Reduce right margin
+    obj->setRightMargin(40);
+    QTRY_COMPARE(obj->contentX(), 1600. + 40. - obj->width());
+    QCOMPARE(obj->contentWidth(), 1600.);
+
+    // position to the far bottom, including margin
+    obj->setContentY(600 + 30 - obj->height());
+    obj->returnToBounds();
+    QTest::qWait(200);
+    QCOMPARE(obj->contentY(), 600. + 30. - obj->height());
+
+    // position beyond the far bottom, including margin
+    obj->setContentY(600 + 30 - obj->height() + 1.);
+    obj->returnToBounds();
+    QTRY_COMPARE(obj->contentY(), 600. + 30. - obj->height());
+
+    // Reduce bottom margin
+    obj->setBottomMargin(20);
+    QTRY_COMPARE(obj->contentY(), 600. + 20. - obj->height());
+    QCOMPARE(obj->contentHeight(), 600.);
+
+    delete root;
+}
+
 void tst_qsgflickable::flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration)
 {
     const int pointCount = 5;
diff --git a/tests/auto/declarative/qsggridview/data/margins.qml b/tests/auto/declarative/qsggridview/data/margins.qml
new file mode 100644 (file)
index 0000000..d369658
--- /dev/null
@@ -0,0 +1,55 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+
+    width: 240
+    height: 320
+    color: "#ffffff"
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            width: 100
+            height: 80
+            border.color: "blue"
+            property string name: model.name
+            Text {
+                text: index
+            }
+            Text {
+                x: 40
+                text: wrapper.x + ", " + wrapper.y
+            }
+            Text {
+                y: 20
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                y: 40
+                id: textNumber
+                objectName: "textNumber"
+                text: number
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+        }
+    }
+    GridView {
+        id: grid
+        objectName: "grid"
+        width: 240
+        height: 320
+        cellWidth: 100
+        cellHeight: 80
+        leftMargin: 30
+        rightMargin: 50
+        flow: GridView.TopToBottom
+        layoutDirection: (testRightToLeft == true) ? Qt.RightToLeft : Qt.LeftToRight
+        model: testModel
+        delegate: myDelegate
+    }
+    Text { anchors.bottom: parent.bottom; text: grid.contentX }
+}
index 5d35ffc..f5cda2b 100644 (file)
@@ -110,6 +110,7 @@ private slots:
     void testQtQuick11Attributes();
     void testQtQuick11Attributes_data();
     void columnCount();
+    void margins();
 
 private:
     QSGView *createView();
@@ -2803,6 +2804,136 @@ void tst_QSGGridView::columnCount()
     QCOMPARE(items.at(9)->y(), qreal(100));
 }
 
+void tst_QSGGridView::margins()
+{
+    {
+        QSGView *canvas = createView();
+        canvas->show();
+
+        TestModel model;
+        for (int i = 0; i < 40; i++)
+            model.addItem("Item" + QString::number(i), "");
+
+        QDeclarativeContext *ctxt = canvas->rootContext();
+        ctxt->setContextProperty("testModel", &model);
+        ctxt->setContextProperty("testRightToLeft", QVariant(false));
+
+        canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/margins.qml"));
+        qApp->processEvents();
+
+        QSGGridView *gridview = findItem<QSGGridView>(canvas->rootObject(), "grid");
+        QTRY_VERIFY(gridview != 0);
+
+        QSGItem *contentItem = gridview->contentItem();
+        QTRY_VERIFY(contentItem != 0);
+
+        QCOMPARE(gridview->contentX(), -30.);
+        QCOMPARE(gridview->xOrigin(), 0.);
+
+        // check end bound
+        gridview->positionViewAtEnd();
+        qreal pos = gridview->contentX();
+        gridview->setContentX(pos + 80);
+        gridview->returnToBounds();
+        QTRY_COMPARE(gridview->contentX(), pos + 50);
+
+        // remove item before visible and check that left margin is maintained
+        // and xOrigin is updated
+        gridview->setContentX(200);
+        model.removeItems(0, 4);
+        QTest::qWait(100);
+        gridview->setContentX(-50);
+        gridview->returnToBounds();
+        QCOMPARE(gridview->xOrigin(), 100.);
+        QTRY_COMPARE(gridview->contentX(), 70.);
+
+        // reduce left margin
+        gridview->setLeftMargin(20);
+        QCOMPARE(gridview->xOrigin(), 100.);
+        QTRY_COMPARE(gridview->contentX(), 80.);
+
+        // check end bound
+        gridview->positionViewAtEnd();
+        QCOMPARE(gridview->xOrigin(), 0.); // positionViewAtEnd() resets origin
+        pos = gridview->contentX();
+        gridview->setContentX(pos + 80);
+        gridview->returnToBounds();
+        QTRY_COMPARE(gridview->contentX(), pos + 50);
+
+        // reduce right margin
+        pos = gridview->contentX();
+        gridview->setRightMargin(40);
+        QCOMPARE(gridview->xOrigin(), 0.);
+        QTRY_COMPARE(gridview->contentX(), pos-10);
+
+        delete canvas;
+    }
+    {
+        //RTL
+        QSGView *canvas = createView();
+        canvas->show();
+
+        TestModel model;
+        for (int i = 0; i < 40; i++)
+            model.addItem("Item" + QString::number(i), "");
+
+        QDeclarativeContext *ctxt = canvas->rootContext();
+        ctxt->setContextProperty("testModel", &model);
+        ctxt->setContextProperty("testRightToLeft", QVariant(true));
+
+        canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/margins.qml"));
+        qApp->processEvents();
+
+        QSGGridView *gridview = findItem<QSGGridView>(canvas->rootObject(), "grid");
+        QTRY_VERIFY(gridview != 0);
+
+        QSGItem *contentItem = gridview->contentItem();
+        QTRY_VERIFY(contentItem != 0);
+
+        QCOMPARE(gridview->contentX(), -240+30.);
+        QCOMPARE(gridview->xOrigin(), 0.);
+
+        // check end bound
+        gridview->positionViewAtEnd();
+        qreal pos = gridview->contentX();
+        gridview->setContentX(pos - 80);
+        gridview->returnToBounds();
+        QTRY_COMPARE(gridview->contentX(), pos - 50);
+
+        // remove item before visible and check that left margin is maintained
+        // and xOrigin is updated
+        gridview->setContentX(-400);
+        model.removeItems(0, 4);
+        QTest::qWait(100);
+        gridview->setContentX(-240+50);
+        gridview->returnToBounds();
+        QCOMPARE(gridview->xOrigin(), -100.);
+        QTRY_COMPARE(gridview->contentX(), -240-70.);
+
+        // reduce left margin (i.e. right side due to RTL)
+        pos = gridview->contentX();
+        gridview->setLeftMargin(20);
+        QCOMPARE(gridview->xOrigin(), -100.);
+        QTRY_COMPARE(gridview->contentX(), -240-80.);
+
+        // check end bound
+        gridview->positionViewAtEnd();
+        QCOMPARE(gridview->xOrigin(), 0.); // positionViewAtEnd() resets origin
+        pos = gridview->contentX();
+        gridview->setContentX(pos - 80);
+        gridview->returnToBounds();
+        QTRY_COMPARE(gridview->contentX(), pos - 50);
+
+        // reduce right margin (i.e. left side due to RTL)
+        pos = gridview->contentX();
+        gridview->setRightMargin(40);
+        QCOMPARE(gridview->xOrigin(), 0.);
+        QTRY_COMPARE(gridview->contentX(), pos+10);
+
+        delete canvas;
+    }
+}
+
 QSGView *tst_QSGGridView::createView()
 {
     QSGView *canvas = new QSGView(0);
diff --git a/tests/auto/declarative/qsglistview/data/margins.qml b/tests/auto/declarative/qsglistview/data/margins.qml
new file mode 100644 (file)
index 0000000..19bbef5
--- /dev/null
@@ -0,0 +1,47 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 240
+    height: 320
+    color: "#ffffff"
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text {
+                text: index
+            }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 120
+                id: textNumber
+                objectName: "textNumber"
+                text: number
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+        }
+    }
+    ListView {
+        id: list
+        objectName: "list"
+        anchors.fill: parent
+        topMargin: 30
+        bottomMargin: 50
+        model: testModel
+        delegate: myDelegate
+    }
+}
index ce1587d..291877c 100644 (file)
@@ -136,6 +136,7 @@ private slots:
     void onRemove_data();
     void rightToLeft();
     void test_mirroring();
+    void margins();
 
 private:
     template <class T> void items();
@@ -3542,6 +3543,68 @@ void tst_QSGListView::test_mirroring()
     delete canvasB;
 }
 
+void tst_QSGListView::margins()
+{
+    QSGView *canvas = createView();
+
+    TestModel2 model;
+    for (int i = 0; i < 50; i++)
+        model.addItem("Item" + QString::number(i), "");
+
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+
+    canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/margins.qml"));
+    canvas->show();
+    qApp->processEvents();
+
+    QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+
+    QSGItem *contentItem = listview->contentItem();
+    QTRY_VERIFY(contentItem != 0);
+
+    QCOMPARE(listview->contentY(), -30.);
+    QCOMPARE(listview->yOrigin(), 0.);
+    
+    // check end bound
+    listview->positionViewAtEnd();
+    qreal pos = listview->contentY();
+    listview->setContentY(pos + 80);
+    listview->returnToBounds();
+    QTRY_COMPARE(listview->contentY(), pos + 50);
+
+    // remove item before visible and check that top margin is maintained
+    // and yOrigin is updated
+    listview->setContentY(100);
+    model.removeItem(1);
+    QTest::qWait(100);
+    listview->setContentY(-50);
+    listview->returnToBounds();
+    QCOMPARE(listview->yOrigin(), 20.);
+    QTRY_COMPARE(listview->contentY(), -10.);
+
+    // reduce top margin
+    listview->setTopMargin(20);
+    QCOMPARE(listview->yOrigin(), 20.);
+    QTRY_COMPARE(listview->contentY(), 0.);
+    
+    // check end bound
+    listview->positionViewAtEnd();
+    pos = listview->contentY();
+    listview->setContentY(pos + 80);
+    listview->returnToBounds();
+    QTRY_COMPARE(listview->contentY(), pos + 50);
+
+    // reduce bottom margin
+    pos = listview->contentY();
+    listview->setBottomMargin(40);
+    QCOMPARE(listview->yOrigin(), 20.);
+    QTRY_COMPARE(listview->contentY(), pos-10);
+
+    delete canvas;
+}
+
 void tst_QSGListView::qListModelInterface_items()
 {
     items<TestModel>();