diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb246b..446fc18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - 传送门面板:DBlur 双栏、layer-shell 定位、拖拽自动弹出、位置/高度可调并持久化 - 暂存区:文件/文字/图片、去重、上限、文件消失清理、固定/消耗语义、全选/反选/删除选中 +- 暂存区动画:hover 高亮、删除右移淡出、拖动排序 - 内容工厂:按类型给出动作,动作按钮可直接接收 drop - http(s) 链接按扩展名分类,支持下载/另存为/复制/Markdown/待办/问小埃 - thunder:// 解析真实地址,magnet 解析名称,非标准 scheme 尝试打开 @@ -16,6 +17,6 @@ 编辑/重试/分支、上下文条数与自动压缩、关于 - 合成器 portal(flake-wm / gxde-wlcom):org.gxde.Portal、缩略图坐标映射、 原生 drop 到目标窗口 -- gxde-k9 开机自启与保活开关 +- gxde-k9 开机自启与保活开关;不支持环境只提示一次并保持待命 -- shenmo diff --git a/src/staging/stagingmodel.cpp b/src/staging/stagingmodel.cpp index de36126..41884f6 100644 --- a/src/staging/stagingmodel.cpp +++ b/src/staging/stagingmodel.cpp @@ -306,6 +306,23 @@ void StagingModel::setChecked(int row, bool checked) save(); } +void StagingModel::moveItem(int from, int to) +{ + if (from < 0 || from >= m_items.size() || to < 0 || to >= m_items.size() || from == to) { + return; + } + if (to > from) { + beginMoveRows(QModelIndex(), from, from, QModelIndex(), to + 1); + m_items.move(from, to); + endMoveRows(); + } else { + beginMoveRows(QModelIndex(), from, from, QModelIndex(), to); + m_items.move(from, to); + endMoveRows(); + } + save(); +} + void StagingModel::setPinned(int row, bool pinned) { if (row < 0 || row >= m_items.size()) { diff --git a/src/staging/stagingmodel.h b/src/staging/stagingmodel.h index 529bd89..0e55ca3 100644 --- a/src/staging/stagingmodel.h +++ b/src/staging/stagingmodel.h @@ -51,6 +51,7 @@ public: void setChecked(int row, bool checked); void setPinned(int row, bool pinned); + void moveItem(int from, int to); void clearAll(); const StagingItem *itemAt(int row) const; diff --git a/src/ui/portalpanel.cpp b/src/ui/portalpanel.cpp index d11cfab..43c6c36 100644 --- a/src/ui/portalpanel.cpp +++ b/src/ui/portalpanel.cpp @@ -48,6 +48,8 @@ #include #include #include +#include +#include #include #include @@ -132,17 +134,28 @@ QString typeLabel(const QModelIndex &index) class StagingDelegate : public QStyledItemDelegate { + Q_OBJECT + public: explicit StagingDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) { } - void setHoverRow(int row) + void setHoverRow(int row) { m_hoverRow = row; } + + void setRemoveState(int row, qreal progress) { - m_hoverRow = row; + m_removingRow = row; + m_removeProgress = progress; } +signals: + void removeRequested(int row); + +public: + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(option) @@ -156,6 +169,11 @@ public: painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); + if (index.row() == m_removingRow && m_removeProgress > 0.0) { + painter->setOpacity(1.0 - m_removeProgress); + painter->translate(m_removeProgress * 48.0, 0.0); + } + const QRect row = option.rect.adjusted(4, 2, -4, -2); const bool hovered = index.row() == m_hoverRow; @@ -247,7 +265,7 @@ public: } if (closeRect(option.rect).contains(pos)) { - model->removeRow(index.row()); + Q_EMIT removeRequested(index.row()); return true; } @@ -256,6 +274,8 @@ public: private: int m_hoverRow = -1; + int m_removingRow = -1; + qreal m_removeProgress = 0.0; }; /* ------------------------------------------------------------------------- */ @@ -285,6 +305,30 @@ public: viewport()->setAutoFillBackground(false); setStyleSheet(QStringLiteral("QListView { background: transparent; }")); setAcceptDrops(true); + + m_removeAnim = new QVariantAnimation(this); + m_removeAnim->setDuration(200); + m_removeAnim->setEasingCurve(QEasingCurve::OutCubic); + connect(m_removeAnim, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) { + if (m_removingRow >= 0) { + m_delegate->setRemoveState(m_removingRow, value.toReal()); + viewport()->update(); + } + }); + connect(m_removeAnim, &QVariantAnimation::finished, this, [this]() { + if (m_removingRow < 0) { + return; + } + auto *staging = qobject_cast(this->model()); + const int row = m_removingRow; + m_delegate->setRemoveState(-1, 0.0); + m_removingRow = -1; + if (staging) { + staging->removeRow(row); + } + viewport()->update(); + }); + connect(m_delegate, &StagingDelegate::removeRequested, this, &StagingView::startRemove); } signals: @@ -346,7 +390,7 @@ protected: void dragEnterEvent(QDragEnterEvent *event) override { if (isOwnDrag(event->mimeData())) { - event->ignore(); + event->acceptProposedAction(); return; } if (mimeSupported(event->mimeData())) { @@ -360,7 +404,7 @@ protected: void dragMoveEvent(QDragMoveEvent *event) override { if (isOwnDrag(event->mimeData())) { - event->ignore(); + event->acceptProposedAction(); return; } if (mimeSupported(event->mimeData())) { @@ -374,12 +418,35 @@ protected: void dropEvent(QDropEvent *event) override { - if (isOwnDrag(event->mimeData()) || !mimeSupported(event->mimeData())) { + if (isOwnDrag(event->mimeData())) { + auto *staging = qobject_cast(this->model()); + const QString id = QString::fromUtf8( + event->mimeData()->data(QStringLiteral("application/x-gxde-ai-see-staging"))); + int from = -1; + if (staging) { + for (int row = 0; row < staging->count(); ++row) { + const StagingItem *item = staging->itemAt(row); + if (item && item->id == id) { + from = row; + break; + } + } + } + const QModelIndex index = indexAt(event->position().toPoint()); + int to = index.isValid() ? index.row() : (staging ? staging->count() - 1 : -1); + if (staging && from >= 0 && to >= 0 && from != to) { + staging->moveItem(from, to); + } + m_internalMove = true; + event->acceptProposedAction(); + return; + } + if (!mimeSupported(event->mimeData())) { event->ignore(); return; } - auto *staging = qobject_cast(model()); + auto *staging = qobject_cast(this->model()); if (staging) { staging->addMimeData(event->mimeData()); } @@ -410,6 +477,21 @@ private: return mime && mime->hasFormat(QStringLiteral("application/x-gxde-ai-see-staging")); } + void startRemove(int row) + { + if (m_removeAnim->state() == QAbstractAnimation::Running) { + return; + } + if (row < 0 || !model() || row >= model()->rowCount()) { + return; + } + m_removingRow = row; + m_delegate->setRemoveState(row, 0.0); + m_removeAnim->setStartValue(0.0); + m_removeAnim->setEndValue(1.0); + m_removeAnim->start(); + } + void startDragForIndex(const QModelIndex &index) { if (!index.isValid()) { @@ -449,10 +531,15 @@ private: const Qt::DropAction action = drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); + if (m_internalMove) { + m_internalMove = false; + return; + } + /* Dragging an item out of the staging area consumes it unless it is * pinned. A cancelled drag (Esc / dropped on nothing) keeps it. */ if (action != Qt::IgnoreAction && !pinned) { - auto *staging = qobject_cast(model()); + auto *staging = qobject_cast(this->model()); if (staging) { for (int row = 0; row < staging->count(); ++row) { const StagingItem *item = staging->itemAt(row); @@ -466,9 +553,12 @@ private: } StagingDelegate *m_delegate = nullptr; + QVariantAnimation *m_removeAnim = nullptr; QPoint m_pressPos; QModelIndex m_pressIndex; int m_hoverRow = -1; + int m_removingRow = -1; + bool m_internalMove = false; }; /* ------------------------------------------------------------------------- */