From 8b054656dd600b779542dfc7ef55915501dd09ac Mon Sep 17 00:00:00 2001 From: David Redondo Date: Fri, 8 Apr 2022 11:43:50 +0200 Subject: [PATCH] kcms/cursortheme: Make the cursor preview on hover animated again I removed that feature when killing the X specific code just using the pixmap setter in 9f4ad82c5b5c47f6e3a3ed78ce559de4db361c78 (plasma-desktop). To make it work we can drive the animation ourselves. --- kcms/cursortheme/xcursor/cursortheme.h | 11 +++++ kcms/cursortheme/xcursor/previewwidget.cpp | 51 +++++++++++++++------- kcms/cursortheme/xcursor/previewwidget.h | 3 ++ kcms/cursortheme/xcursor/xcursortheme.cpp | 29 ++++++++++++ kcms/cursortheme/xcursor/xcursortheme.h | 2 + 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/kcms/cursortheme/xcursor/cursortheme.h b/kcms/cursortheme/xcursor/cursortheme.h index 146b94f4f..a00bedbe0 100644 --- a/kcms/cursortheme/xcursor/cursortheme.h +++ b/kcms/cursortheme/xcursor/cursortheme.h @@ -9,6 +9,8 @@ #include #include +#include + /** * This is the abstract base class for all cursor themes stored in a * CursorThemeModel and previewed in a PreviewWidget. @@ -41,6 +43,11 @@ public: PendingDeletionRole, }; + struct CursorImage { + QImage image; + std::chrono::milliseconds delay; + }; + CursorTheme() { } @@ -97,6 +104,10 @@ public: /// If the theme doesn't have the cursor @p name, it should return a null image. virtual QImage loadImage(const QString &name, int size = 0) const = 0; + /// Loads the cursor images @p name, with the nominal size @p size. + /// The images should be autocropped to the smallest possible size. + /// If the theme doesn't have the cursor @p name, it should return an empty vector + virtual std::vector loadImages(const QString &name, int size = 0) const = 0; /// Loads the cursor @p name, with the nominal size @p size. /// If the theme doesn't have the cursor @p name, it should return diff --git a/kcms/cursortheme/xcursor/previewwidget.cpp b/kcms/cursortheme/xcursor/previewwidget.cpp index df1149f5d..137e17c42 100644 --- a/kcms/cursortheme/xcursor/previewwidget.cpp +++ b/kcms/cursortheme/xcursor/previewwidget.cpp @@ -79,23 +79,25 @@ public: { return pixmap(); } - + const std::vector &images() const + { + return m_images; + } private: int m_boundingSize; QPixmap m_pixmap; + std::vector m_images; QPoint m_pos; }; PreviewCursor::PreviewCursor(const CursorTheme *theme, const QString &name, int size) : m_boundingSize(size > 0 ? size : theme->defaultCursorSize()) + , m_images(theme->loadImages(name, size)) { - // Create the preview pixmap - QImage image = theme->loadImage(name, size); - - if (image.isNull()) + if (m_images.empty()) return; - m_pixmap = QPixmap::fromImage(image); + m_pixmap = QPixmap::fromImage(m_images.front().image); } QRect PreviewCursor::rect() const @@ -112,6 +114,11 @@ PreviewWidget::PreviewWidget(QQuickItem *parent) { setAcceptHoverEvents(true); current = nullptr; + connect(&m_animationTimer, &QTimer::timeout, this, [this] { + setCursor(QPixmap::fromImage(current->images().at(nextAnimationFrame).image)); + m_animationTimer.setInterval(current->images().at(nextAnimationFrame).delay); + nextAnimationFrame = (nextAnimationFrame + 1) % current->images().size(); + }); } PreviewWidget::~PreviewWidget() @@ -259,23 +266,35 @@ void PreviewWidget::hoverMoveEvent(QHoverEvent *e) if (needLayout) layoutItems(); - for (const PreviewCursor *c : qAsConst(list)) { - if (c->rect().contains(e->pos())) { - if (c != current) { - setCursor(QCursor(c->pixmap())); - current = c; - } - return; - } + auto it = std::find_if(list.cbegin(), list.cend(), [e](const PreviewCursor *c) { + return c->rect().contains(e->pos()); + }); + const PreviewCursor *cursor = it != list.cend() ? *it : nullptr; + + if (cursor == std::exchange(current, cursor)) { + return; } + m_animationTimer.stop(); - setCursor(Qt::ArrowCursor); - current = nullptr; + if (current == nullptr) { + setCursor(Qt::ArrowCursor); + return; + } + + if (current->images().size() <= 1) { + setCursor(current->pixmap()); + return; + } + + nextAnimationFrame = 0; + m_animationTimer.setInterval(0); + m_animationTimer.start(); } void PreviewWidget::hoverLeaveEvent(QHoverEvent *e) { Q_UNUSED(e); + m_animationTimer.stop(); unsetCursor(); } diff --git a/kcms/cursortheme/xcursor/previewwidget.h b/kcms/cursortheme/xcursor/previewwidget.h index cc921d3f1..2ce15900e 100644 --- a/kcms/cursortheme/xcursor/previewwidget.h +++ b/kcms/cursortheme/xcursor/previewwidget.h @@ -9,6 +9,7 @@ #include "sortproxymodel.h" #include #include +#include class CursorTheme; class PreviewCursor; @@ -63,4 +64,6 @@ private: QPointer m_themeModel; int m_currentIndex; int m_currentSize; + QTimer m_animationTimer; + size_t nextAnimationFrame; }; diff --git a/kcms/cursortheme/xcursor/xcursortheme.cpp b/kcms/cursortheme/xcursor/xcursortheme.cpp index a3cb0b59d..3ad9c91d1 100644 --- a/kcms/cursortheme/xcursor/xcursortheme.cpp +++ b/kcms/cursortheme/xcursor/xcursortheme.cpp @@ -209,3 +209,32 @@ QImage XCursorTheme::loadImage(const QString &name, int size) const return image; } + +std::vector XCursorTheme::loadImages(const QString &name, int size) const +{ + if (size <= 0) + size = defaultCursorSize(); + + // Load the images + XcursorImages *xcimages = xcLoadImages(name, size); + + if (!xcimages) + xcimages = xcLoadImages(findAlternative(name), size); + + if (!xcimages) { + return {}; + } + + std::vector images; + images.reserve(xcimages->nimage); + for (int i = 0; i < xcimages->nimage; ++i) { + // Convert the XcursorImage to a QImage, and auto-crop it + const XcursorImage *xcimage = xcimages->images[i]; + QImage image(reinterpret_cast(xcimage->pixels), xcimage->width, xcimage->height, QImage::Format_ARGB32_Premultiplied); + images.push_back(CursorImage{autoCropImage(image), std::chrono::milliseconds{xcimage->delay}}); + } + + XcursorImagesDestroy(xcimages); + + return images; +} diff --git a/kcms/cursortheme/xcursor/xcursortheme.h b/kcms/cursortheme/xcursor/xcursortheme.h index 15608d9b0..26745a9de 100644 --- a/kcms/cursortheme/xcursor/xcursortheme.h +++ b/kcms/cursortheme/xcursor/xcursortheme.h @@ -37,7 +37,9 @@ public: { return m_inherits; } + QImage loadImage(const QString &name, int size = 0) const override; + std::vector loadImages(const QString &name, int size = 0) const override; qulonglong loadCursor(const QString &name, int size = 0) const override; /** Returns the size that the XCursor library would use if no