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 "cursor.h"
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
@ -36,4 +37,83 @@ void ImageCursorSource::update(const QImage &image, const QPoint &hotspot)
|
|||
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
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <kwin_export.h>
|
||||
#include "utils/xcursortheme.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
#include <QTimer>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
@ -50,4 +51,33 @@ public Q_SLOTS:
|
|||
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
|
||||
|
|
|
@ -886,11 +886,11 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
|
|||
: QObject(parent)
|
||||
, m_pointer(parent)
|
||||
{
|
||||
m_effectsCursor = std::make_unique<ImageCursorSource>();
|
||||
m_fallbackCursor = std::make_unique<ImageCursorSource>();
|
||||
m_moveResizeCursor = std::make_unique<ImageCursorSource>();
|
||||
m_windowSelectionCursor = std::make_unique<ImageCursorSource>();
|
||||
m_decoration.cursor = std::make_unique<ImageCursorSource>();
|
||||
m_effectsCursor = std::make_unique<ShapeCursorSource>();
|
||||
m_fallbackCursor = std::make_unique<ShapeCursorSource>();
|
||||
m_moveResizeCursor = std::make_unique<ShapeCursorSource>();
|
||||
m_windowSelectionCursor = std::make_unique<ShapeCursorSource>();
|
||||
m_decoration.cursor = std::make_unique<ShapeCursorSource>();
|
||||
m_drag.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();
|
||||
std::for_each(clients.begin(), clients.end(), 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] {
|
||||
loadThemeCursor(Qt::ArrowCursor, m_fallbackCursor.get());
|
||||
updateDecorationCursor();
|
||||
updateMoveResize();
|
||||
// TODO: update effects
|
||||
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());
|
||||
});
|
||||
|
||||
handlePointerChanged();
|
||||
|
@ -998,7 +1006,7 @@ void CursorImage::updateDecorationCursor()
|
|||
{
|
||||
auto deco = m_pointer->decoration();
|
||||
if (Window *window = deco ? deco->window() : nullptr) {
|
||||
loadThemeCursor(window->cursor(), m_decoration.cursor.get());
|
||||
m_decoration.cursor->setShape(window->cursor().name());
|
||||
}
|
||||
reevaluteSource();
|
||||
}
|
||||
|
@ -1006,7 +1014,7 @@ void CursorImage::updateDecorationCursor()
|
|||
void CursorImage::updateMoveResize()
|
||||
{
|
||||
if (Window *window = workspace()->moveResizeWindow()) {
|
||||
loadThemeCursor(window->cursor(), m_moveResizeCursor.get());
|
||||
m_moveResizeCursor->setShape(window->cursor().name());
|
||||
}
|
||||
reevaluteSource();
|
||||
}
|
||||
|
@ -1040,7 +1048,7 @@ void CursorImage::updateServerCursor()
|
|||
|
||||
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
|
||||
{
|
||||
loadThemeCursor(shape, m_effectsCursor.get());
|
||||
m_effectsCursor->setShape(shape);
|
||||
reevaluteSource();
|
||||
}
|
||||
|
||||
|
@ -1052,9 +1060,9 @@ void CursorImage::removeEffectsOverrideCursor()
|
|||
void CursorImage::setWindowSelectionCursor(const QByteArray &shape)
|
||||
{
|
||||
if (shape.isEmpty()) {
|
||||
loadThemeCursor(Qt::CrossCursor, m_windowSelectionCursor.get());
|
||||
m_windowSelectionCursor->setShape(Qt::CrossCursor);
|
||||
} else {
|
||||
loadThemeCursor(shape, m_windowSelectionCursor.get());
|
||||
m_windowSelectionCursor->setShape(shape);
|
||||
}
|
||||
reevaluteSource();
|
||||
}
|
||||
|
@ -1144,31 +1152,23 @@ void CursorImage::updateDragCursor()
|
|||
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)
|
||||
: QObject(parent)
|
||||
{
|
||||
Cursor *pointerCursor = Cursors::self()->mouse();
|
||||
updateCursorTheme();
|
||||
|
||||
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme);
|
||||
connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::invalidateCursorTheme);
|
||||
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::updateCursorTheme);
|
||||
connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::updateCursorTheme);
|
||||
}
|
||||
|
||||
bool WaylandCursorImage::ensureCursorTheme()
|
||||
KXcursorTheme WaylandCursorImage::theme() const
|
||||
{
|
||||
if (!m_cursorTheme.isEmpty()) {
|
||||
return true;
|
||||
return m_cursorTheme;
|
||||
}
|
||||
|
||||
void WaylandCursorImage::updateCursorTheme()
|
||||
{
|
||||
const Cursor *pointerCursor = Cursors::self()->mouse();
|
||||
qreal targetDevicePixelRatio = 1;
|
||||
|
||||
|
@ -1180,21 +1180,11 @@ bool WaylandCursorImage::ensureCursorTheme()
|
|||
}
|
||||
|
||||
m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio);
|
||||
if (!m_cursorTheme.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_cursorTheme.isEmpty()) {
|
||||
m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio);
|
||||
if (!m_cursorTheme.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WaylandCursorImage::invalidateCursorTheme()
|
||||
{
|
||||
m_cursorTheme = KXcursorTheme();
|
||||
Q_EMIT themeChanged();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!ensureCursorTheme()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadThemeCursor_helper(name, source)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1281,6 +1267,11 @@ void CursorImage::setSource(CursorSource *source)
|
|||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
KXcursorTheme CursorImage::theme() const
|
||||
{
|
||||
return m_waylandImage.theme();
|
||||
}
|
||||
|
||||
InputRedirectionCursor::InputRedirectionCursor(QObject *parent)
|
||||
: Cursor(parent)
|
||||
, m_currentButtons(Qt::NoButton)
|
||||
|
|
|
@ -35,6 +35,7 @@ class InputDevice;
|
|||
class InputRedirection;
|
||||
class CursorShape;
|
||||
class ImageCursorSource;
|
||||
class ShapeCursorSource;
|
||||
|
||||
namespace Decoration
|
||||
{
|
||||
|
@ -183,6 +184,8 @@ class WaylandCursorImage : public QObject
|
|||
public:
|
||||
explicit WaylandCursorImage(QObject *parent = nullptr);
|
||||
|
||||
KXcursorTheme theme() const;
|
||||
|
||||
void loadThemeCursor(const CursorShape &shape, ImageCursorSource *source);
|
||||
void loadThemeCursor(const QByteArray &name, ImageCursorSource *source);
|
||||
|
||||
|
@ -191,8 +194,7 @@ Q_SIGNALS:
|
|||
|
||||
private:
|
||||
bool loadThemeCursor_helper(const QByteArray &name, ImageCursorSource *source);
|
||||
bool ensureCursorTheme();
|
||||
void invalidateCursorTheme();
|
||||
void updateCursorTheme();
|
||||
|
||||
KXcursorTheme m_cursorTheme;
|
||||
};
|
||||
|
@ -209,6 +211,7 @@ public:
|
|||
void setWindowSelectionCursor(const QByteArray &shape);
|
||||
void removeWindowSelectionCursor();
|
||||
|
||||
KXcursorTheme theme() const;
|
||||
CursorSource *source() const;
|
||||
void setSource(CursorSource *source);
|
||||
void markAsRendered(std::chrono::milliseconds timestamp);
|
||||
|
@ -228,21 +231,18 @@ private:
|
|||
void handlePointerChanged();
|
||||
void handleFocusedSurfaceChanged();
|
||||
|
||||
void loadThemeCursor(CursorShape shape, ImageCursorSource *source);
|
||||
void loadThemeCursor(const QByteArray &shape, ImageCursorSource *source);
|
||||
|
||||
PointerInputRedirection *m_pointer;
|
||||
CursorSource *m_currentSource = nullptr;
|
||||
WaylandCursorImage m_waylandImage;
|
||||
|
||||
std::unique_ptr<ImageCursorSource> m_effectsCursor;
|
||||
std::unique_ptr<ImageCursorSource> m_fallbackCursor;
|
||||
std::unique_ptr<ImageCursorSource> m_moveResizeCursor;
|
||||
std::unique_ptr<ImageCursorSource> m_windowSelectionCursor;
|
||||
std::unique_ptr<ShapeCursorSource> m_effectsCursor;
|
||||
std::unique_ptr<ShapeCursorSource> m_fallbackCursor;
|
||||
std::unique_ptr<ShapeCursorSource> m_moveResizeCursor;
|
||||
std::unique_ptr<ShapeCursorSource> m_windowSelectionCursor;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<ImageCursorSource> cursor;
|
||||
std::unique_ptr<ShapeCursorSource> cursor;
|
||||
QMetaObject::Connection connection;
|
||||
} m_decoration;
|
||||
struct
|
||||
|
|
|
@ -218,6 +218,16 @@ KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other)
|
|||
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
|
||||
{
|
||||
return d->registry.isEmpty();
|
||||
|
|
|
@ -106,6 +106,9 @@ public:
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue