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;
|
||||
}
|
||||
|
||||
struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
|
||||
::wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation)
|
||||
{
|
||||
struct wl_cursor *waylandCursor = nullptr;
|
||||
|
||||
@ -227,8 +227,9 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct wl_cursor_image *image = waylandCursor->images[0];
|
||||
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
||||
int frame = wl_cursor_frame(waylandCursor, millisecondsIntoAnimation);
|
||||
::wl_cursor_image *image = waylandCursor->images[frame];
|
||||
::wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
||||
if (!buffer) {
|
||||
qCWarning(lcQpaWayland) << "Could not find buffer for cursor";
|
||||
return nullptr;
|
||||
|
@ -75,7 +75,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
|
||||
public:
|
||||
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
|
||||
~QWaylandCursorTheme();
|
||||
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
|
||||
::wl_cursor_image *cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation = 0);
|
||||
|
||||
private:
|
||||
enum WaylandCursor {
|
||||
|
@ -191,6 +191,27 @@ QWaylandInputDevice::Pointer::~Pointer()
|
||||
|
||||
#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
|
||||
{
|
||||
public:
|
||||
@ -213,7 +234,7 @@ public:
|
||||
}
|
||||
|
||||
// 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
|
||||
Q_ASSERT(bufferScale == 1 || m_version >= 3);
|
||||
@ -230,6 +251,13 @@ public:
|
||||
|
||||
attach(buffer, 0, 0);
|
||||
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();
|
||||
}
|
||||
|
||||
@ -242,6 +270,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QScopedPointer<WlCallback> m_frameCallback;
|
||||
QWaylandInputDevice::Pointer *m_pointer = nullptr;
|
||||
uint m_version = 0;
|
||||
uint m_setSerial = 0;
|
||||
@ -320,12 +349,14 @@ void QWaylandInputDevice::Pointer::updateCursor()
|
||||
updateCursorTheme();
|
||||
|
||||
// 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);
|
||||
int bufferScale = mCursor.themeBufferScale;
|
||||
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / 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;
|
||||
}
|
||||
|
||||
@ -515,6 +546,7 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer<
|
||||
mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor;
|
||||
mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint();
|
||||
mCursor.fallbackOutputScale = fallbackOutputScale;
|
||||
mCursor.animationTimer.start();
|
||||
|
||||
if (mCursor.shape == Qt::BitmapCursor) {
|
||||
mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor);
|
||||
|
@ -69,7 +69,8 @@
|
||||
#endif
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QPointer>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
#if QT_CONFIG(cursor)
|
||||
struct wl_cursor_image;
|
||||
@ -153,6 +154,7 @@ private:
|
||||
Qt::CursorShape shape = Qt::ArrowCursor;
|
||||
int fallbackOutputScale = 1;
|
||||
QPoint hotspot;
|
||||
QElapsedTimer animationTimer;
|
||||
} mCursor;
|
||||
#endif
|
||||
|
||||
|
@ -80,6 +80,7 @@ private slots:
|
||||
void bitmapCursor();
|
||||
void hidpiBitmapCursor();
|
||||
void hidpiBitmapCursorNonInt();
|
||||
void animatedCursor();
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -554,6 +555,36 @@ void tst_seatv4::hidpiBitmapCursorNonInt()
|
||||
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)
|
||||
|
||||
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
|
||||
|
Loading…
x
Reference in New Issue
Block a user