From 94828aa307af32191124d4fb8c0033a365dc3568 Mon Sep 17 00:00:00 2001 From: Felix Ernst Date: Sun, 18 Feb 2024 13:15:18 +0100 Subject: [PATCH] Add drag-open animation This commit adds an animation for folders that makes clear that they will open or expand soon. This is the case when the option to open folders during drag operations is enabled and a user drags an item on top of a folder. The animation goes like this: - Replace the folder's icon with the "folder-open" icon - Go back to the folder's original icon - Replace the folder's icon with the "folder-open" icon once more --- src/kitemviews/kitemlistcontroller.cpp | 1 + src/kitemviews/kitemlistwidget.cpp | 5 +++ src/kitemviews/kitemlistwidget.h | 6 +++ src/kitemviews/kstandarditemlistwidget.cpp | 49 +++++++++++++++++++++- src/kitemviews/kstandarditemlistwidget.h | 6 +++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index b4d28047e9..aea59c7110 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -830,6 +830,7 @@ bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent *event, cons if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) { m_autoActivationTimer->setProperty("index", index); m_autoActivationTimer->start(); + newHoveredWidget->startActivateSoonAnimation(m_autoActivationTimer->remainingTime()); } } else { m_autoActivationTimer->stop(); diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index c4fa1f2ccf..4c9f259861 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -438,6 +438,11 @@ QPixmap KItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem *option return pixmap; } +void KItemListWidget::startActivateSoonAnimation(int timeUntilActivation) +{ + Q_UNUSED(timeUntilActivation) +} + void KItemListWidget::dataChanged(const QHash ¤t, const QSet &roles) { Q_UNUSED(current) diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h index 55d9247190..fdfe5e78a2 100644 --- a/src/kitemviews/kitemlistwidget.h +++ b/src/kitemviews/kitemlistwidget.h @@ -188,6 +188,12 @@ public: */ virtual QPixmap createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr); + /** + * Starts an animation that makes clear that the item will be activated soon. + * @param timeUntilActivation time in milliseconds until the item will activate + */ + virtual void startActivateSoonAnimation(int timeUntilActivation); + Q_SIGNALS: void roleEditingCanceled(int index, const QByteArray &role, const QVariant &value); void roleEditingFinished(int index, const QByteArray &role, const QVariant &value); diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index 2a28d198ab..b534338ce6 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -24,6 +24,7 @@ #include #include #include +#include // #define KSTANDARDITEMLISTWIDGET_DEBUG @@ -589,6 +590,50 @@ QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem return clippedPixmap; } +void KStandardItemListWidget::startActivateSoonAnimation(int timeUntilActivation) +{ + if (m_activateSoonAnimation) { + m_activateSoonAnimation->stop(); // automatically DeleteWhenStopped + } + + m_activateSoonAnimation = new QVariantAnimation{this}; + m_activateSoonAnimation->setStartValue(0.0); + m_activateSoonAnimation->setEndValue(1.0); + m_activateSoonAnimation->setDuration(timeUntilActivation); + + const QVariant originalIconName{data()["iconName"]}; + connect(m_activateSoonAnimation, &QVariantAnimation::valueChanged, this, [originalIconName, this](const QVariant &value) { + auto progress = value.toFloat(); + + QVariant wantedIconName; + if (progress < 0.333) { + wantedIconName = "folder-open"; + } else if (progress < 0.666) { + wantedIconName = originalIconName; + } else { + wantedIconName = "folder-open"; + } + + QHash itemData{data()}; + if (itemData["iconName"] != wantedIconName) { + itemData.insert("iconName", wantedIconName); + setData(itemData); + invalidateIconCache(); + } + }); + + connect(m_activateSoonAnimation, &QObject::destroyed, this, [originalIconName, this]() { + QHash itemData{data()}; + if (itemData["iconName"] == "folder-open") { + itemData.insert("iconName", originalIconName); + setData(itemData); + invalidateIconCache(); + } + }); + + m_activateSoonAnimation->start(QAbstractAnimation::DeleteWhenStopped); +} + KItemListWidgetInformant *KStandardItemListWidget::createInformant() { return new KStandardItemListWidgetInformant(); @@ -736,7 +781,9 @@ void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption &cur void KStandardItemListWidget::hoveredChanged(bool hovered) { - Q_UNUSED(hovered) + if (!hovered && m_activateSoonAnimation) { + m_activateSoonAnimation->stop(); // automatically DeleteWhenStopped + } m_dirtyLayout = true; } diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h index 06bf0529ad..5afadd8cd2 100644 --- a/src/kitemviews/kstandarditemlistwidget.h +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -17,6 +17,7 @@ class KItemListRoleEditor; class KItemListStyleOption; class KItemListView; +class QVariantAnimation; /** * @brief standard implementation of the ItemList widget informant for use with KStandardItemListView and KStandardItemModel. @@ -103,6 +104,8 @@ public: QRectF expansionToggleRect() const override; QRectF selectionToggleRect() const override; QPixmap createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + /** @see KItemListWidget::startActivateSoonAnimation() */ + void startActivateSoonAnimation(int timeUntilActivation) override; static KItemListWidgetInformant *createInformant(); @@ -282,6 +285,9 @@ private: KItemListRoleEditor *m_roleEditor; KItemListRoleEditor *m_oldRoleEditor; + /** @see startActivateSoonAnimation() */ + QPointer m_activateSoonAnimation; + friend class KStandardItemListWidgetInformant; // Accesses private static methods to be able to // share a common layout calculation };