Client: Add support for animated cursors
[ChangeLog][QPA plugin] Added support for animated cursors. Previously we would just show the first frame of the animation. Fixes: QTBUG-48181 Change-Id: Ie06bff8950678b5ff7b7e2e50915c85905a1200b Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
parent
c6476ae915
commit
5d1b75d9c1
@ -209,7 +209,7 @@ wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
|
::wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation)
|
||||||
{
|
{
|
||||||
struct wl_cursor *waylandCursor = nullptr;
|
struct wl_cursor *waylandCursor = nullptr;
|
||||||
|
|
||||||
@ -227,8 +227,9 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct wl_cursor_image *image = waylandCursor->images[0];
|
int frame = wl_cursor_frame(waylandCursor, millisecondsIntoAnimation);
|
||||||
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
::wl_cursor_image *image = waylandCursor->images[frame];
|
||||||
|
::wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
qCWarning(lcQpaWayland) << "Could not find buffer for cursor";
|
qCWarning(lcQpaWayland) << "Could not find buffer for cursor";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -75,7 +75,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
|
|||||||
public:
|
public:
|
||||||
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
|
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
|
||||||
~QWaylandCursorTheme();
|
~QWaylandCursorTheme();
|
||||||
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
|
::wl_cursor_image *cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum WaylandCursor {
|
enum WaylandCursor {
|
||||||
|
@ -191,6 +191,27 @@ QWaylandInputDevice::Pointer::~Pointer()
|
|||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
|
|
||||||
|
class WlCallback : public QtWayland::wl_callback {
|
||||||
|
public:
|
||||||
|
explicit WlCallback(::wl_callback *callback, std::function<void(uint32_t)> fn, bool autoDelete = false)
|
||||||
|
: QtWayland::wl_callback(callback)
|
||||||
|
, m_fn(fn)
|
||||||
|
, m_autoDelete(autoDelete)
|
||||||
|
{}
|
||||||
|
~WlCallback() override { wl_callback_destroy(object()); }
|
||||||
|
bool done() const { return m_done; }
|
||||||
|
void callback_done(uint32_t callback_data) override {
|
||||||
|
m_done = true;
|
||||||
|
m_fn(callback_data);
|
||||||
|
if (m_autoDelete)
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool m_done = false;
|
||||||
|
std::function<void(uint32_t)> m_fn;
|
||||||
|
bool m_autoDelete = false;
|
||||||
|
};
|
||||||
|
|
||||||
class CursorSurface : public QWaylandSurface
|
class CursorSurface : public QWaylandSurface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -213,7 +234,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Size and hotspot are in surface coordinates
|
// Size and hotspot are in surface coordinates
|
||||||
void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale)
|
void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, bool animated = false)
|
||||||
{
|
{
|
||||||
// Calling code needs to ensure buffer scale is supported if != 1
|
// Calling code needs to ensure buffer scale is supported if != 1
|
||||||
Q_ASSERT(bufferScale == 1 || m_version >= 3);
|
Q_ASSERT(bufferScale == 1 || m_version >= 3);
|
||||||
@ -230,6 +251,13 @@ public:
|
|||||||
|
|
||||||
attach(buffer, 0, 0);
|
attach(buffer, 0, 0);
|
||||||
damage(0, 0, size.width(), size.height());
|
damage(0, 0, size.width(), size.height());
|
||||||
|
m_frameCallback.reset();
|
||||||
|
if (animated) {
|
||||||
|
m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time){
|
||||||
|
Q_UNUSED(time);
|
||||||
|
m_pointer->updateCursor();
|
||||||
|
}));
|
||||||
|
}
|
||||||
commit();
|
commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +270,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QScopedPointer<WlCallback> m_frameCallback;
|
||||||
QWaylandInputDevice::Pointer *m_pointer = nullptr;
|
QWaylandInputDevice::Pointer *m_pointer = nullptr;
|
||||||
uint m_version = 0;
|
uint m_version = 0;
|
||||||
uint m_setSerial = 0;
|
uint m_setSerial = 0;
|
||||||
@ -320,12 +349,14 @@ void QWaylandInputDevice::Pointer::updateCursor()
|
|||||||
updateCursorTheme();
|
updateCursorTheme();
|
||||||
|
|
||||||
// Set from shape using theme
|
// Set from shape using theme
|
||||||
if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape)) {
|
uint time = seat()->mCursor.animationTimer.elapsed();
|
||||||
|
if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape, time)) {
|
||||||
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
||||||
int bufferScale = mCursor.themeBufferScale;
|
int bufferScale = mCursor.themeBufferScale;
|
||||||
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
|
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
|
||||||
QSize size = QSize(image->width, image->height) / bufferScale;
|
QSize size = QSize(image->width, image->height) / bufferScale;
|
||||||
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale);
|
bool animated = image->delay > 0;
|
||||||
|
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,6 +546,7 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer<
|
|||||||
mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor;
|
mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor;
|
||||||
mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint();
|
mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint();
|
||||||
mCursor.fallbackOutputScale = fallbackOutputScale;
|
mCursor.fallbackOutputScale = fallbackOutputScale;
|
||||||
|
mCursor.animationTimer.start();
|
||||||
|
|
||||||
if (mCursor.shape == Qt::BitmapCursor) {
|
if (mCursor.shape == Qt::BitmapCursor) {
|
||||||
mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor);
|
mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor);
|
||||||
|
@ -69,7 +69,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QPointer>
|
#include <QtCore/QElapsedTimer>
|
||||||
|
#include <QtCore/QPointer>
|
||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
struct wl_cursor_image;
|
struct wl_cursor_image;
|
||||||
@ -153,6 +154,7 @@ private:
|
|||||||
Qt::CursorShape shape = Qt::ArrowCursor;
|
Qt::CursorShape shape = Qt::ArrowCursor;
|
||||||
int fallbackOutputScale = 1;
|
int fallbackOutputScale = 1;
|
||||||
QPoint hotspot;
|
QPoint hotspot;
|
||||||
|
QElapsedTimer animationTimer;
|
||||||
} mCursor;
|
} mCursor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ private slots:
|
|||||||
void bitmapCursor();
|
void bitmapCursor();
|
||||||
void hidpiBitmapCursor();
|
void hidpiBitmapCursor();
|
||||||
void hidpiBitmapCursorNonInt();
|
void hidpiBitmapCursorNonInt();
|
||||||
|
void animatedCursor();
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -554,6 +555,36 @@ void tst_seatv4::hidpiBitmapCursorNonInt()
|
|||||||
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25));
|
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_seatv4::animatedCursor()
|
||||||
|
{
|
||||||
|
QRasterWindow window;
|
||||||
|
window.resize(64, 64);
|
||||||
|
window.setCursor(Qt::WaitCursor); // TODO: verify that the theme has an animated wait cursor or skip test
|
||||||
|
window.show();
|
||||||
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
|
|
||||||
|
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
||||||
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
|
||||||
|
|
||||||
|
// We should get the first buffer without waiting for a frame callback
|
||||||
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
|
||||||
|
QSignalSpy bufferSpy(exec([=] { return cursorSurface(); }), &Surface::bufferCommitted);
|
||||||
|
|
||||||
|
exec([&] {
|
||||||
|
// Make sure no extra buffers have arrived
|
||||||
|
QVERIFY(bufferSpy.empty());
|
||||||
|
|
||||||
|
// The client should send a frame request in order to time animations correctly
|
||||||
|
QVERIFY(!cursorSurface()->m_waitingFrameCallbacks.empty());
|
||||||
|
|
||||||
|
// Tell the client it's time to animate
|
||||||
|
cursorSurface()->sendFrameCallbacks();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that we get a new cursor buffer
|
||||||
|
QTRY_COMPARE(bufferSpy.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // QT_CONFIG(cursor)
|
#endif // QT_CONFIG(cursor)
|
||||||
|
|
||||||
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
|
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user