Add ShapeCursorSource
ShapeCursorSource provides the contents of the given cursor shape in the Xcursor theme. If the given cursor shape is animated, ShapeCursorSource will keep updating its content based on the value of current sprite's delay value.
This commit is contained in:
parent
e552be6cb1
commit
b4569f5985
6 changed files with 173 additions and 59 deletions
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cursorsource.h"
|
#include "cursorsource.h"
|
||||||
|
#include "cursor.h"
|
||||||
|
|
||||||
namespace KWin
|
namespace KWin
|
||||||
{
|
{
|
||||||
|
@ -36,4 +37,83 @@ void ImageCursorSource::update(const QImage &image, const QPoint &hotspot)
|
||||||
Q_EMIT changed();
|
Q_EMIT changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShapeCursorSource::ShapeCursorSource(QObject *parent)
|
||||||
|
: CursorSource(parent)
|
||||||
|
{
|
||||||
|
m_delayTimer.setSingleShot(true);
|
||||||
|
connect(&m_delayTimer, &QTimer::timeout, this, &ShapeCursorSource::selectNextSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ShapeCursorSource::shape() const
|
||||||
|
{
|
||||||
|
return m_shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::setShape(const QByteArray &shape)
|
||||||
|
{
|
||||||
|
if (m_shape != shape) {
|
||||||
|
m_shape = shape;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::setShape(Qt::CursorShape shape)
|
||||||
|
{
|
||||||
|
setShape(CursorShape(shape).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
KXcursorTheme ShapeCursorSource::theme() const
|
||||||
|
{
|
||||||
|
return m_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::setTheme(const KXcursorTheme &theme)
|
||||||
|
{
|
||||||
|
if (m_theme != theme) {
|
||||||
|
m_theme = theme;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::refresh()
|
||||||
|
{
|
||||||
|
m_currentSprite = -1;
|
||||||
|
m_delayTimer.stop();
|
||||||
|
|
||||||
|
m_sprites = m_theme.shape(m_shape);
|
||||||
|
if (m_sprites.isEmpty()) {
|
||||||
|
const auto alternativeNames = Cursor::cursorAlternativeNames(m_shape);
|
||||||
|
for (const QByteArray &alternativeName : alternativeNames) {
|
||||||
|
m_sprites = m_theme.shape(alternativeName);
|
||||||
|
if (!m_sprites.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_sprites.isEmpty()) {
|
||||||
|
selectSprite(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::selectNextSprite()
|
||||||
|
{
|
||||||
|
selectSprite((m_currentSprite + 1) % m_sprites.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeCursorSource::selectSprite(int index)
|
||||||
|
{
|
||||||
|
if (m_currentSprite == index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const KXcursorSprite &sprite = m_sprites[index];
|
||||||
|
m_currentSprite = index;
|
||||||
|
m_image = sprite.data();
|
||||||
|
m_hotspot = sprite.hotspot();
|
||||||
|
if (sprite.delay().count() && m_sprites.size() > 1) {
|
||||||
|
m_delayTimer.start(sprite.delay());
|
||||||
|
}
|
||||||
|
Q_EMIT changed();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace KWin
|
} // namespace KWin
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <kwin_export.h>
|
#include "utils/xcursortheme.h"
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
namespace KWin
|
namespace KWin
|
||||||
{
|
{
|
||||||
|
@ -50,4 +51,33 @@ public Q_SLOTS:
|
||||||
void update(const QImage &image, const QPoint &hotspot);
|
void update(const QImage &image, const QPoint &hotspot);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ShapeCursorSource class represents the contents of a shape in the cursor theme.
|
||||||
|
*/
|
||||||
|
class KWIN_EXPORT ShapeCursorSource : public CursorSource
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ShapeCursorSource(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QByteArray shape() const;
|
||||||
|
void setShape(const QByteArray &shape);
|
||||||
|
void setShape(Qt::CursorShape shape);
|
||||||
|
|
||||||
|
KXcursorTheme theme() const;
|
||||||
|
void setTheme(const KXcursorTheme &theme);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refresh();
|
||||||
|
void selectNextSprite();
|
||||||
|
void selectSprite(int index);
|
||||||
|
|
||||||
|
KXcursorTheme m_theme;
|
||||||
|
QByteArray m_shape;
|
||||||
|
QVector<KXcursorSprite> m_sprites;
|
||||||
|
QTimer m_delayTimer;
|
||||||
|
int m_currentSprite = -1;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace KWin
|
} // namespace KWin
|
||||||
|
|
|
@ -886,11 +886,11 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_pointer(parent)
|
, m_pointer(parent)
|
||||||
{
|
{
|
||||||
m_effectsCursor = std::make_unique<ImageCursorSource>();
|
m_effectsCursor = std::make_unique<ShapeCursorSource>();
|
||||||
m_fallbackCursor = std::make_unique<ImageCursorSource>();
|
m_fallbackCursor = std::make_unique<ShapeCursorSource>();
|
||||||
m_moveResizeCursor = std::make_unique<ImageCursorSource>();
|
m_moveResizeCursor = std::make_unique<ShapeCursorSource>();
|
||||||
m_windowSelectionCursor = std::make_unique<ImageCursorSource>();
|
m_windowSelectionCursor = std::make_unique<ShapeCursorSource>();
|
||||||
m_decoration.cursor = std::make_unique<ImageCursorSource>();
|
m_decoration.cursor = std::make_unique<ShapeCursorSource>();
|
||||||
m_drag.cursor = std::make_unique<ImageCursorSource>();
|
m_drag.cursor = std::make_unique<ImageCursorSource>();
|
||||||
m_serverCursor.cursor = std::make_unique<ImageCursorSource>();
|
m_serverCursor.cursor = std::make_unique<ImageCursorSource>();
|
||||||
|
|
||||||
|
@ -915,13 +915,21 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
|
||||||
const auto clients = workspace()->allClientList();
|
const auto clients = workspace()->allClientList();
|
||||||
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
|
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
|
||||||
connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
|
connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
|
||||||
loadThemeCursor(Qt::ArrowCursor, m_fallbackCursor.get());
|
|
||||||
|
m_fallbackCursor->setShape(Qt::ArrowCursor);
|
||||||
|
|
||||||
|
m_effectsCursor->setTheme(m_waylandImage.theme());
|
||||||
|
m_fallbackCursor->setTheme(m_waylandImage.theme());
|
||||||
|
m_moveResizeCursor->setTheme(m_waylandImage.theme());
|
||||||
|
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
|
||||||
|
m_decoration.cursor->setTheme(m_waylandImage.theme());
|
||||||
|
|
||||||
connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
|
connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
|
||||||
loadThemeCursor(Qt::ArrowCursor, m_fallbackCursor.get());
|
m_effectsCursor->setTheme(m_waylandImage.theme());
|
||||||
updateDecorationCursor();
|
m_fallbackCursor->setTheme(m_waylandImage.theme());
|
||||||
updateMoveResize();
|
m_moveResizeCursor->setTheme(m_waylandImage.theme());
|
||||||
// TODO: update effects
|
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
|
||||||
|
m_decoration.cursor->setTheme(m_waylandImage.theme());
|
||||||
});
|
});
|
||||||
|
|
||||||
handlePointerChanged();
|
handlePointerChanged();
|
||||||
|
@ -998,7 +1006,7 @@ void CursorImage::updateDecorationCursor()
|
||||||
{
|
{
|
||||||
auto deco = m_pointer->decoration();
|
auto deco = m_pointer->decoration();
|
||||||
if (Window *window = deco ? deco->window() : nullptr) {
|
if (Window *window = deco ? deco->window() : nullptr) {
|
||||||
loadThemeCursor(window->cursor(), m_decoration.cursor.get());
|
m_decoration.cursor->setShape(window->cursor().name());
|
||||||
}
|
}
|
||||||
reevaluteSource();
|
reevaluteSource();
|
||||||
}
|
}
|
||||||
|
@ -1006,7 +1014,7 @@ void CursorImage::updateDecorationCursor()
|
||||||
void CursorImage::updateMoveResize()
|
void CursorImage::updateMoveResize()
|
||||||
{
|
{
|
||||||
if (Window *window = workspace()->moveResizeWindow()) {
|
if (Window *window = workspace()->moveResizeWindow()) {
|
||||||
loadThemeCursor(window->cursor(), m_moveResizeCursor.get());
|
m_moveResizeCursor->setShape(window->cursor().name());
|
||||||
}
|
}
|
||||||
reevaluteSource();
|
reevaluteSource();
|
||||||
}
|
}
|
||||||
|
@ -1040,7 +1048,7 @@ void CursorImage::updateServerCursor()
|
||||||
|
|
||||||
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
|
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
|
||||||
{
|
{
|
||||||
loadThemeCursor(shape, m_effectsCursor.get());
|
m_effectsCursor->setShape(shape);
|
||||||
reevaluteSource();
|
reevaluteSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1052,9 +1060,9 @@ void CursorImage::removeEffectsOverrideCursor()
|
||||||
void CursorImage::setWindowSelectionCursor(const QByteArray &shape)
|
void CursorImage::setWindowSelectionCursor(const QByteArray &shape)
|
||||||
{
|
{
|
||||||
if (shape.isEmpty()) {
|
if (shape.isEmpty()) {
|
||||||
loadThemeCursor(Qt::CrossCursor, m_windowSelectionCursor.get());
|
m_windowSelectionCursor->setShape(Qt::CrossCursor);
|
||||||
} else {
|
} else {
|
||||||
loadThemeCursor(shape, m_windowSelectionCursor.get());
|
m_windowSelectionCursor->setShape(shape);
|
||||||
}
|
}
|
||||||
reevaluteSource();
|
reevaluteSource();
|
||||||
}
|
}
|
||||||
|
@ -1144,31 +1152,23 @@ void CursorImage::updateDragCursor()
|
||||||
m_drag.cursor->update(image, hotspot);
|
m_drag.cursor->update(image, hotspot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CursorImage::loadThemeCursor(CursorShape shape, ImageCursorSource *source)
|
|
||||||
{
|
|
||||||
m_waylandImage.loadThemeCursor(shape, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CursorImage::loadThemeCursor(const QByteArray &shape, ImageCursorSource *source)
|
|
||||||
{
|
|
||||||
m_waylandImage.loadThemeCursor(shape, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
WaylandCursorImage::WaylandCursorImage(QObject *parent)
|
WaylandCursorImage::WaylandCursorImage(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
Cursor *pointerCursor = Cursors::self()->mouse();
|
Cursor *pointerCursor = Cursors::self()->mouse();
|
||||||
|
updateCursorTheme();
|
||||||
|
|
||||||
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme);
|
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::updateCursorTheme);
|
||||||
connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::invalidateCursorTheme);
|
connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::updateCursorTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandCursorImage::ensureCursorTheme()
|
KXcursorTheme WaylandCursorImage::theme() const
|
||||||
{
|
{
|
||||||
if (!m_cursorTheme.isEmpty()) {
|
return m_cursorTheme;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
void WaylandCursorImage::updateCursorTheme()
|
||||||
|
{
|
||||||
const Cursor *pointerCursor = Cursors::self()->mouse();
|
const Cursor *pointerCursor = Cursors::self()->mouse();
|
||||||
qreal targetDevicePixelRatio = 1;
|
qreal targetDevicePixelRatio = 1;
|
||||||
|
|
||||||
|
@ -1180,21 +1180,11 @@ bool WaylandCursorImage::ensureCursorTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio);
|
m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio);
|
||||||
if (!m_cursorTheme.isEmpty()) {
|
if (m_cursorTheme.isEmpty()) {
|
||||||
return true;
|
m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio);
|
Q_EMIT themeChanged();
|
||||||
if (!m_cursorTheme.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WaylandCursorImage::invalidateCursorTheme()
|
|
||||||
{
|
|
||||||
m_cursorTheme = KXcursorTheme();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSource *source)
|
void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSource *source)
|
||||||
|
@ -1204,10 +1194,6 @@ void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSo
|
||||||
|
|
||||||
void WaylandCursorImage::loadThemeCursor(const QByteArray &name, ImageCursorSource *source)
|
void WaylandCursorImage::loadThemeCursor(const QByteArray &name, ImageCursorSource *source)
|
||||||
{
|
{
|
||||||
if (!ensureCursorTheme()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadThemeCursor_helper(name, source)) {
|
if (loadThemeCursor_helper(name, source)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1281,6 +1267,11 @@ void CursorImage::setSource(CursorSource *source)
|
||||||
Q_EMIT changed();
|
Q_EMIT changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KXcursorTheme CursorImage::theme() const
|
||||||
|
{
|
||||||
|
return m_waylandImage.theme();
|
||||||
|
}
|
||||||
|
|
||||||
InputRedirectionCursor::InputRedirectionCursor(QObject *parent)
|
InputRedirectionCursor::InputRedirectionCursor(QObject *parent)
|
||||||
: Cursor(parent)
|
: Cursor(parent)
|
||||||
, m_currentButtons(Qt::NoButton)
|
, m_currentButtons(Qt::NoButton)
|
||||||
|
|
|
@ -35,6 +35,7 @@ class InputDevice;
|
||||||
class InputRedirection;
|
class InputRedirection;
|
||||||
class CursorShape;
|
class CursorShape;
|
||||||
class ImageCursorSource;
|
class ImageCursorSource;
|
||||||
|
class ShapeCursorSource;
|
||||||
|
|
||||||
namespace Decoration
|
namespace Decoration
|
||||||
{
|
{
|
||||||
|
@ -183,6 +184,8 @@ class WaylandCursorImage : public QObject
|
||||||
public:
|
public:
|
||||||
explicit WaylandCursorImage(QObject *parent = nullptr);
|
explicit WaylandCursorImage(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
KXcursorTheme theme() const;
|
||||||
|
|
||||||
void loadThemeCursor(const CursorShape &shape, ImageCursorSource *source);
|
void loadThemeCursor(const CursorShape &shape, ImageCursorSource *source);
|
||||||
void loadThemeCursor(const QByteArray &name, ImageCursorSource *source);
|
void loadThemeCursor(const QByteArray &name, ImageCursorSource *source);
|
||||||
|
|
||||||
|
@ -191,8 +194,7 @@ Q_SIGNALS:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool loadThemeCursor_helper(const QByteArray &name, ImageCursorSource *source);
|
bool loadThemeCursor_helper(const QByteArray &name, ImageCursorSource *source);
|
||||||
bool ensureCursorTheme();
|
void updateCursorTheme();
|
||||||
void invalidateCursorTheme();
|
|
||||||
|
|
||||||
KXcursorTheme m_cursorTheme;
|
KXcursorTheme m_cursorTheme;
|
||||||
};
|
};
|
||||||
|
@ -209,6 +211,7 @@ public:
|
||||||
void setWindowSelectionCursor(const QByteArray &shape);
|
void setWindowSelectionCursor(const QByteArray &shape);
|
||||||
void removeWindowSelectionCursor();
|
void removeWindowSelectionCursor();
|
||||||
|
|
||||||
|
KXcursorTheme theme() const;
|
||||||
CursorSource *source() const;
|
CursorSource *source() const;
|
||||||
void setSource(CursorSource *source);
|
void setSource(CursorSource *source);
|
||||||
void markAsRendered(std::chrono::milliseconds timestamp);
|
void markAsRendered(std::chrono::milliseconds timestamp);
|
||||||
|
@ -228,21 +231,18 @@ private:
|
||||||
void handlePointerChanged();
|
void handlePointerChanged();
|
||||||
void handleFocusedSurfaceChanged();
|
void handleFocusedSurfaceChanged();
|
||||||
|
|
||||||
void loadThemeCursor(CursorShape shape, ImageCursorSource *source);
|
|
||||||
void loadThemeCursor(const QByteArray &shape, ImageCursorSource *source);
|
|
||||||
|
|
||||||
PointerInputRedirection *m_pointer;
|
PointerInputRedirection *m_pointer;
|
||||||
CursorSource *m_currentSource = nullptr;
|
CursorSource *m_currentSource = nullptr;
|
||||||
WaylandCursorImage m_waylandImage;
|
WaylandCursorImage m_waylandImage;
|
||||||
|
|
||||||
std::unique_ptr<ImageCursorSource> m_effectsCursor;
|
std::unique_ptr<ShapeCursorSource> m_effectsCursor;
|
||||||
std::unique_ptr<ImageCursorSource> m_fallbackCursor;
|
std::unique_ptr<ShapeCursorSource> m_fallbackCursor;
|
||||||
std::unique_ptr<ImageCursorSource> m_moveResizeCursor;
|
std::unique_ptr<ShapeCursorSource> m_moveResizeCursor;
|
||||||
std::unique_ptr<ImageCursorSource> m_windowSelectionCursor;
|
std::unique_ptr<ShapeCursorSource> m_windowSelectionCursor;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
std::unique_ptr<ImageCursorSource> cursor;
|
std::unique_ptr<ShapeCursorSource> cursor;
|
||||||
QMetaObject::Connection connection;
|
QMetaObject::Connection connection;
|
||||||
} m_decoration;
|
} m_decoration;
|
||||||
struct
|
struct
|
||||||
|
|
|
@ -218,6 +218,16 @@ KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other)
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool KXcursorTheme::operator==(const KXcursorTheme &other)
|
||||||
|
{
|
||||||
|
return d == other.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KXcursorTheme::operator!=(const KXcursorTheme &other)
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
bool KXcursorTheme::isEmpty() const
|
bool KXcursorTheme::isEmpty() const
|
||||||
{
|
{
|
||||||
return d->registry.isEmpty();
|
return d->registry.isEmpty();
|
||||||
|
|
|
@ -106,6 +106,9 @@ public:
|
||||||
*/
|
*/
|
||||||
KXcursorTheme &operator=(const KXcursorTheme &other);
|
KXcursorTheme &operator=(const KXcursorTheme &other);
|
||||||
|
|
||||||
|
bool operator==(const KXcursorTheme &other);
|
||||||
|
bool operator!=(const KXcursorTheme &other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns @c true if the Xcursor theme is empty; otherwise returns @c false.
|
* Returns @c true if the Xcursor theme is empty; otherwise returns @c false.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue