]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kitemlistview.cpp
[kitemlistview]: Animate rubberband fading out
[dolphin.git] / src / kitemviews / kitemlistview.cpp
index 5665d9b9cfea918f29787db5b342c483020b0906..96c337de39b02c873e9ed5335f396e5ec88d8367 100644 (file)
@@ -1,23 +1,10 @@
-/***************************************************************************
- *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
- *                                                                         *
- *   Based on the Itemviews NG project from Trolltech Labs                 *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ *
+ * Based on the Itemviews NG project from Trolltech Labs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "kitemlistview.h"
 
 #include <QElapsedTimer>
 #include <QGraphicsSceneMouseEvent>
 #include <QGraphicsView>
+#include <QPropertyAnimation>
 #include <QStyleOptionRubberBand>
 #include <QTimer>
+#include <QVariantAnimation>
 
 
 namespace {
@@ -48,6 +37,11 @@ namespace {
 
     // Delay in ms for triggering the next autoscroll
     const int RepeatingAutoScrollDelay = 1000 / 60;
+
+    // Copied from the Kirigami.Units.shortDuration
+    const int RubberFadeSpeed = 150;
+
+    const char* RubberPropertyName = "_kitemviews_rubberBandPosition";
 }
 
 #ifndef QT_NO_ACCESSIBILITY
@@ -93,14 +87,17 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
     m_oldMaximumItemOffset(0),
     m_skipAutoScrollForRubberBand(false),
     m_rubberBand(nullptr),
+    m_tapAndHoldIndicator(nullptr),
     m_mousePos(),
     m_autoScrollIncrement(0),
     m_autoScrollTimer(nullptr),
     m_header(nullptr),
     m_headerWidget(nullptr),
+    m_indicatorAnimation(nullptr),
     m_dropIndicator()
 {
     setAcceptHoverEvents(true);
+    setAcceptTouchEvents(true);
 
     m_sizeHintResolver = new KItemListSizeHintResolver(this);
 
@@ -118,6 +115,23 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
     m_rubberBand = new KItemListRubberBand(this);
     connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged);
 
+    m_tapAndHoldIndicator = new KItemListRubberBand(this);
+    m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition", this);
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) {
+        if (active) {
+            m_indicatorAnimation->setDuration(150);
+            m_indicatorAnimation->setStartValue(QPointF(1, 1));
+            m_indicatorAnimation->setEndValue(QPointF(40, 40));
+            m_indicatorAnimation->start();
+        }
+        update();
+    });
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() {
+        if (m_tapAndHoldIndicator->isActive()) {
+            update();
+        }
+    });
+
     m_headerWidget = new KItemListHeaderWidget(this);
     m_headerWidget->setVisible(false);
 
@@ -222,7 +236,7 @@ void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
         if (!m_headerWidget->automaticColumnResizing()) {
             // The column-width of new roles are still 0. Apply the preferred
             // column-width as default with.
-            foreach (const QByteArray& role, m_visibleRoles) {
+            for (const QByteArray& role : qAsConst(m_visibleRoles)) {
                 if (m_headerWidget->columnWidth(role) == 0) {
                     const qreal width = m_headerWidget->preferredColumnWidth(role);
                     m_headerWidget->setColumnWidth(role, width);
@@ -528,7 +542,7 @@ void KItemListView::scrollToItem(int index)
         }
 
         if (newOffset != scrollOffset()) {
-            emit scrollTo(newOffset);
+            Q_EMIT scrollTo(newOffset);
         }
     }
 }
@@ -652,6 +666,30 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
 {
     QGraphicsWidget::paint(painter, option, widget);
 
+    for (auto animation : qAsConst(m_rubberBandAnimations)) {
+        QRectF rubberBandRect = animation->property(RubberPropertyName).toRectF();
+
+        const QPointF topLeft = rubberBandRect.topLeft();
+        if (scrollOrientation() == Qt::Vertical) {
+            rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset());
+        } else {
+            rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y());
+        }
+
+        QStyleOptionRubberBand opt;
+        initStyleOption(&opt);
+        opt.shape = QRubberBand::Rectangle;
+        opt.opaque = false;
+        opt.rect = rubberBandRect.toRect();
+
+        painter->save();
+
+        painter->setOpacity(animation->currentValue().toReal());
+        style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
+
+        painter->restore();
+    }
+
     if (m_rubberBand->isActive()) {
         QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
                                        m_rubberBand->endPosition()).normalized();
@@ -671,10 +709,22 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
         style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
     }
 
+    if (m_tapAndHoldIndicator->isActive()) {
+        const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition();
+        const QRectF rubberBandRect = QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize,
+                                    (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized();
+        QStyleOptionRubberBand opt;
+        initStyleOption(&opt);
+        opt.shape = QRubberBand::Rectangle;
+        opt.opaque = false;
+        opt.rect = rubberBandRect.toRect();
+        style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
+    }
+
     if (!m_dropIndicator.isEmpty()) {
         const QRectF r = m_dropIndicator.toRect();
 
-        QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color();
+        QColor color = palette().brush(QPalette::Normal, QPalette::Text).color();
         painter->setPen(color);
 
         // TODO: The following implementation works only for a vertical scroll-orientation
@@ -817,7 +867,7 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation)
     doLayout(NoAnimation);
 
     onScrollOrientationChanged(orientation, previousOrientation);
-    emit scrollOrientationChanged(orientation, previousOrientation);
+    Q_EMIT scrollOrientationChanged(orientation, previousOrientation);
 }
 
 Qt::Orientation KItemListView::scrollOrientation() const
@@ -1009,7 +1059,7 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
     m_sizeHintResolver->itemsInserted(itemRanges);
 
     int previouslyInsertedCount = 0;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange& range : itemRanges) {
         // range.index is related to the model before anything has been inserted.
         // As in each loop the current item-range gets inserted the index must
         // be increased by the already previously inserted items.
@@ -1135,7 +1185,10 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
         QVector<int> itemsToMove;
 
         // Remove all KItemListWidget instances that got deleted
-        foreach (KItemListWidget* widget, m_visibleItems) {
+        // Iterate over a const copy because the container is mutated within the loop
+        // directly and in `recycleWidget()` (https://bugs.kde.org/show_bug.cgi?id=428374)
+        const auto visibleItems = m_visibleItems;
+        for (KItemListWidget* widget : visibleItems) {
             const int i = widget->index();
             if (i < firstRemovedIndex) {
                 continue;
@@ -1171,7 +1224,7 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
         // after the deleted items. It is important to update them in ascending
         // order to prevent overlaps when setting the new index.
         std::sort(itemsToMove.begin(), itemsToMove.end());
-        foreach (int i, itemsToMove) {
+        for (int i : qAsConst(itemsToMove)) {
             KItemListWidget* widget = m_visibleItems.value(i);
             Q_ASSERT(widget);
             const int newIndex = i - count;
@@ -1249,7 +1302,7 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
         updatePreferredColumnWidths(itemRanges);
     }
 
-    foreach (const KItemRange& itemRange, itemRanges) {
+    for (const KItemRange& itemRange : itemRanges) {
         const int index = itemRange.index;
         const int count = itemRange.count;
 
@@ -1432,6 +1485,30 @@ void KItemListView::slotRubberBandActivationChanged(bool active)
         connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
         m_skipAutoScrollForRubberBand = true;
     } else {
+        QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
+                                       m_rubberBand->endPosition()).normalized();
+
+        auto animation = new QVariantAnimation(this);
+        animation->setStartValue(1.0);
+        animation->setEndValue(0.0);
+        animation->setDuration(RubberFadeSpeed);
+        animation->setProperty(RubberPropertyName, rubberBandRect);
+
+        QEasingCurve curve;
+        curve.setType(QEasingCurve::BezierSpline);
+        curve.addCubicBezierSegment(QPointF(0.4, 0.0), QPointF(1.0, 1.0), QPointF(1.0, 1.0));
+        animation->setEasingCurve(curve);
+
+        connect(animation, &QVariantAnimation::valueChanged, this, [=](const QVariant&) {
+            update();
+        });
+        connect(animation, &QVariantAnimation::finished, this, [=]() {
+            m_rubberBandAnimations.removeAll(animation);
+            delete animation;
+        });
+        animation->start();
+        m_rubberBandAnimations << animation;
+
         disconnect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
         disconnect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
         m_skipAutoScrollForRubberBand = false;
@@ -1467,7 +1544,7 @@ void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
 
     setVisibleRoles(current);
 
-    emit visibleRolesChanged(current, previous);
+    Q_EMIT visibleRolesChanged(current, previous);
 }
 
 void KItemListView::triggerAutoScrolling()
@@ -1542,7 +1619,7 @@ void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, c
 {
     disconnectRoleEditingSignals(index);
 
-    emit roleEditingCanceled(index, role, value);
+    Q_EMIT roleEditingCanceled(index, role, value);
     m_editingRole = false;
 }
 
@@ -1550,7 +1627,7 @@ void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, c
 {
     disconnectRoleEditingSignals(index);
 
-    emit roleEditingFinished(index, role, value);
+    Q_EMIT roleEditingFinished(index, role, value);
     m_editingRole = false;
 }
 
@@ -1803,7 +1880,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
     }
 
     // Delete invisible KItemListWidget instances that have not been reused
-    foreach (int index, reusableItems) {
+    for (int index : qAsConst(reusableItems)) {
         recycleWidget(m_visibleItems.value(index));
     }
 
@@ -1906,25 +1983,25 @@ void KItemListView::emitOffsetChanges()
 {
     const qreal newScrollOffset = m_layouter->scrollOffset();
     if (m_oldScrollOffset != newScrollOffset) {
-        emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
+        Q_EMIT scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
         m_oldScrollOffset = newScrollOffset;
     }
 
     const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
     if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
-        emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
+        Q_EMIT maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
         m_oldMaximumScrollOffset = newMaximumScrollOffset;
     }
 
     const qreal newItemOffset = m_layouter->itemOffset();
     if (m_oldItemOffset != newItemOffset) {
-        emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
+        Q_EMIT itemOffsetChanged(newItemOffset, m_oldItemOffset);
         m_oldItemOffset = newItemOffset;
     }
 
     const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
     if (m_oldMaximumItemOffset != newMaximumItemOffset) {
-        emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
+        Q_EMIT maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
         m_oldMaximumItemOffset = newMaximumItemOffset;
     }
 }
@@ -2177,7 +2254,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
     const QFontMetricsF fontMetrics(m_headerWidget->font());
     const int gripMargin   = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin);
     const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
-    foreach (const QByteArray& visibleRole, visibleRoles()) {
+    for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
         const QString headerText = m_model->roleDescription(visibleRole);
         const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
         widths.insert(visibleRole, headerWidth);
@@ -2188,12 +2265,12 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
     const KItemListWidgetCreatorBase* creator = widgetCreator();
     int calculatedItemCount = 0;
     bool maxTimeExceeded = false;
-    foreach (const KItemRange& itemRange, itemRanges) {
+    for (const KItemRange& itemRange : itemRanges) {
         const int startIndex = itemRange.index;
         const int endIndex = startIndex + itemRange.count - 1;
 
         for (int i = startIndex; i <= endIndex; ++i) {
-            foreach (const QByteArray& visibleRole, visibleRoles()) {
+            for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
                 qreal maxWidth = widths.value(visibleRole, 0);
                 const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this);
                 maxWidth = qMax(width, maxWidth);
@@ -2235,7 +2312,7 @@ void KItemListView::applyColumnWidthsFromHeader()
 
 void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
 {
-    foreach (const QByteArray& role, m_visibleRoles) {
+    for (const QByteArray& role : qAsConst(m_visibleRoles)) {
         widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
     }
 }
@@ -2245,13 +2322,13 @@ void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges
     Q_ASSERT(m_itemSize.isEmpty());
     const int itemCount = m_model->count();
     int rangesItemCount = 0;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange& range : itemRanges) {
         rangesItemCount += range.count;
     }
 
     if (itemCount == rangesItemCount) {
         const QHash<QByteArray, qreal> preferredWidths = preferredColumnWidths(itemRanges);
-        foreach (const QByteArray& role, m_visibleRoles) {
+        for (const QByteArray& role : qAsConst(m_visibleRoles)) {
             m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role));
         }
     } else {
@@ -2306,7 +2383,7 @@ void KItemListView::applyAutomaticColumnWidths()
     // size does not use the available view-size the size of the
     // first role will get stretched.
 
-    foreach (const QByteArray& role, m_visibleRoles) {
+    for (const QByteArray& role : qAsConst(m_visibleRoles)) {
         const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role);
         m_headerWidget->setColumnWidth(role, preferredWidth);
     }
@@ -2353,7 +2430,7 @@ void KItemListView::applyAutomaticColumnWidths()
 qreal KItemListView::columnWidthsSum() const
 {
     qreal widthsSum = 0;
-    foreach (const QByteArray& role, m_visibleRoles) {
+    for (const QByteArray& role : qAsConst(m_visibleRoles)) {
         widthsSum += m_headerWidget->columnWidth(role);
     }
     return widthsSum;
@@ -2494,7 +2571,7 @@ void KItemListView::updateGroupHeaderHeight()
         groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
         groupHeaderMargin = m_styleOption.horizontalMargin;
     } else if (m_itemSize.isEmpty()){
-        groupHeaderHeight += 4 * m_styleOption.padding;
+        groupHeaderHeight += 2 * m_styleOption.padding;
         groupHeaderMargin = m_styleOption.iconSize / 2;
     } else {
         groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;