Set specific edge cursor shape when resizing

Summary:
Instead of seeing the cursor <--> on the left edge you now see an icon
that looks like |<-  .

This brings kwin decorations in line with GTK CSD icons.

In theory this is also useful to tell which window will resize in the
case of side-by-side windows (regardless of whether borders are on or
not). In practice with the adwaita icon theme I tested with it's not
very intuitive to realise which is which till you learn the icon.

Change is more involved than it should be as Qt::CursorShape doesn't
have these entries, and I don't want to shadow that enum internally or
have
to change kwin effect code.

Specifics depend on cursor icon theme if they are not present it will
fallback to the <--> icon. (Breeze does not have them currently)

Test Plan:
Resized some windows (on X and on Wayland)
Correct icon appeared on Adwaita
Existing icon appeared on Breeze

Reviewers: #plasma

Subscribers: kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D13396
This commit is contained in:
David Edmundson 2018-06-07 01:33:54 +01:00
parent fcfe8763f6
commit 5b4eb80c8f
12 changed files with 167 additions and 67 deletions

View file

@ -1291,23 +1291,31 @@ void AbstractClient::updateCursor()
Position m = moveResizePointerMode();
if (!isResizable() || isShade())
m = PositionCenter;
Qt::CursorShape c = Qt::ArrowCursor;
CursorShape c = Qt::ArrowCursor;
switch(m) {
case PositionTopLeft:
c = KWin::ExtendedCursor::SizeNorthWest;
break;
case PositionBottomRight:
c = Qt::SizeFDiagCursor;
c = KWin::ExtendedCursor::SizeSouthEast;
break;
case PositionBottomLeft:
c = KWin::ExtendedCursor::SizeSouthWest;
break;
case PositionTopRight:
c = Qt::SizeBDiagCursor;
c = KWin::ExtendedCursor::SizeNorthEast;
break;
case PositionTop:
c = KWin::ExtendedCursor::SizeNorth;
break;
case PositionBottom:
c = Qt::SizeVerCursor;
c = KWin::ExtendedCursor::SizeSouth;
break;
case PositionLeft:
c = KWin::ExtendedCursor::SizeWest;
break;
case PositionRight:
c = Qt::SizeHorCursor;
c = KWin::ExtendedCursor::SizeEast;
break;
default:
if (isMoveResize())

View file

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "options.h"
#include "rules.h"
#include "tabgroup.h"
#include "cursor.h"
#include <memory>
@ -602,7 +603,7 @@ public:
/**
* Cursor shape for move/resize mode.
**/
Qt::CursorShape cursor() const {
CursorShape cursor() const {
return m_moveResize.cursor;
}
@ -747,7 +748,7 @@ Q_SIGNALS:
void modalChanged();
void quickTileModeChanged();
void moveResizedChanged();
void moveResizeCursorChanged(Qt::CursorShape);
void moveResizeCursorChanged(CursorShape);
void clientStartUserMovedResized(KWin::AbstractClient*);
void clientStepUserMovedResized(KWin::AbstractClient *, const QRect&);
void clientFinishUserMovedResized(KWin::AbstractClient*);
@ -1122,7 +1123,7 @@ private:
QRect geometry;
Position pointer = PositionCenter;
bool buttonDown = false;
Qt::CursorShape cursor = Qt::ArrowCursor;
CursorShape cursor = Qt::ArrowCursor;
int startScreen = 0;
QTimer *delayedTimer = nullptr;
} m_moveResize;

View file

@ -365,28 +365,28 @@ void DecorationInputTest::testHover()
quint32 timestamp = 1;
MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2));
QCOMPARE(c->cursor(), Qt::ArrowCursor);
QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
MOTION(QPoint(20, 0));
QCOMPARE(c->cursor(), Qt::SizeFDiagCursor);
MOTION(QPoint(c->geometry().x(), 0));
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest));
MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, 0));
QCOMPARE(c->cursor(), Qt::SizeVerCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth));
MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, 0));
QCOMPARE(c->cursor(), Qt::SizeBDiagCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast));
MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() / 2));
QCOMPARE(c->cursor(), Qt::SizeHorCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast));
MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() - 1));
QCOMPARE(c->cursor(), Qt::SizeFDiagCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast));
MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, c->height() - 1));
QCOMPARE(c->cursor(), Qt::SizeVerCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth));
MOTION(QPoint(c->geometry().x(), c->height() - 1));
QCOMPARE(c->cursor(), Qt::SizeBDiagCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest));
MOTION(QPoint(c->geometry().x(), c->height() / 2));
QCOMPARE(c->cursor(), Qt::SizeHorCursor);
QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest));
MOTION(c->geometry().center());
QEXPECT_FAIL("", "Cursor not set back on leave", Continue);
QCOMPARE(c->cursor(), Qt::ArrowCursor);
QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
}
void DecorationInputTest::testPressToMove_data()
@ -425,7 +425,7 @@ void DecorationInputTest::testPressToMove()
quint32 timestamp = 1;
MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2));
QCOMPARE(c->cursor(), Qt::ArrowCursor);
QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
PRESS;
QVERIFY(!c->isMove());

View file

@ -154,7 +154,7 @@ Client::Client()
connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption);
connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption);
connect(this, &Client::moveResizeCursorChanged, this, [this] (Qt::CursorShape cursor) {
connect(this, &Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) {
xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor);
m_frame.defineCursor(nativeCursor);
if (m_decoInputExtent.isValid())

View file

@ -125,7 +125,7 @@ void Cursor::setPos(int x, int y)
Cursor::setPos(QPoint(x, y));
}
xcb_cursor_t Cursor::getX11Cursor(Qt::CursorShape shape)
xcb_cursor_t Cursor::getX11Cursor(CursorShape shape)
{
Q_UNUSED(shape)
return XCB_CURSOR_NONE;
@ -137,7 +137,7 @@ xcb_cursor_t Cursor::getX11Cursor(const QByteArray &name)
return XCB_CURSOR_NONE;
}
xcb_cursor_t Cursor::x11Cursor(Qt::CursorShape shape)
xcb_cursor_t Cursor::x11Cursor(CursorShape shape)
{
return s_self->getX11Cursor(shape);
}
@ -299,7 +299,46 @@ QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name) const
QByteArrayLiteral("1081e37283d90000800003c07f3ef6bf"),
QByteArrayLiteral("6407b0e94181790501fd1e167b474872"),
QByteArrayLiteral("b66166c04f8c3109214a4fbd64a50fc8")}},
{QByteArrayLiteral("dnd-move"), {QByteArrayLiteral("move")}}
{QByteArrayLiteral("dnd-move"), {QByteArrayLiteral("move")}},
{QByteArrayLiteral("sw-resize"), {QByteArrayLiteral("size_bdiag"),
QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"),
QByteArrayLiteral("fd_double_arrow"),
QByteArrayLiteral("bottom_left_corner")}},
{QByteArrayLiteral("se-resize"), {QByteArrayLiteral("size_fdiag"),
QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"),
QByteArrayLiteral("bd_double_arrow"),
QByteArrayLiteral("bottom_right_corner")}},
{QByteArrayLiteral("ne-resize"), {QByteArrayLiteral("size_bdiag"),
QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"),
QByteArrayLiteral("fd_double_arrow"),
QByteArrayLiteral("top_right_corner")}},
{QByteArrayLiteral("nw-resize"), {QByteArrayLiteral("size_fdiag"),
QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"),
QByteArrayLiteral("bd_double_arrow"),
QByteArrayLiteral("top_left_corner")}},
{QByteArrayLiteral("n-resize"), {QByteArrayLiteral("size_ver"),
QByteArrayLiteral("00008160000006810000408080010102"),
QByteArrayLiteral("sb_v_double_arrow"),
QByteArrayLiteral("v_double_arrow"),
QByteArrayLiteral("col-resize"),
QByteArrayLiteral("top_side")}},
{QByteArrayLiteral("e-resize"), {QByteArrayLiteral("size_hor"),
QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"),
QByteArrayLiteral("sb_h_double_arrow"),
QByteArrayLiteral("h_double_arrow"),
QByteArrayLiteral("row-resize"),
QByteArrayLiteral("left_side")}},
{QByteArrayLiteral("s-resize"), {QByteArrayLiteral("size_ver"),
QByteArrayLiteral("00008160000006810000408080010102"),
QByteArrayLiteral("sb_v_double_arrow"),
QByteArrayLiteral("v_double_arrow"),
QByteArrayLiteral("col-resize"),
QByteArrayLiteral("bottom_side")}},
{QByteArrayLiteral("w-resize"), {QByteArrayLiteral("size_hor"),
QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"),
QByteArrayLiteral("sb_h_double_arrow"),
QByteArrayLiteral("h_double_arrow"),
QByteArrayLiteral("right_side")}}
};
auto it = alternatives.find(name);
if (it != alternatives.end()) {
@ -308,51 +347,67 @@ QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name) const
return QVector<QByteArray>();
}
QByteArray Cursor::cursorName(Qt::CursorShape shape) const
QByteArray CursorShape::name() const
{
switch (shape) {
switch (m_shape) {
case Qt::ArrowCursor:
return QByteArray("left_ptr");
return QByteArrayLiteral("left_ptr");
case Qt::UpArrowCursor:
return QByteArray("up_arrow");
return QByteArrayLiteral("up_arrow");
case Qt::CrossCursor:
return QByteArray("cross");
return QByteArrayLiteral("cross");
case Qt::WaitCursor:
return QByteArray("wait");
return QByteArrayLiteral("wait");
case Qt::IBeamCursor:
return QByteArray("ibeam");
return QByteArrayLiteral("ibeam");
case Qt::SizeVerCursor:
return QByteArray("size_ver");
return QByteArrayLiteral("size_ver");
case Qt::SizeHorCursor:
return QByteArray("size_hor");
return QByteArrayLiteral("size_hor");
case Qt::SizeBDiagCursor:
return QByteArray("size_bdiag");
return QByteArrayLiteral("size_bdiag");
case Qt::SizeFDiagCursor:
return QByteArray("size_fdiag");
return QByteArrayLiteral("size_fdiag");
case Qt::SizeAllCursor:
return QByteArray("size_all");
return QByteArrayLiteral("size_all");
case Qt::SplitVCursor:
return QByteArray("split_v");
return QByteArrayLiteral("split_v");
case Qt::SplitHCursor:
return QByteArray("split_h");
return QByteArrayLiteral("split_h");
case Qt::PointingHandCursor:
return QByteArray("pointing_hand");
return QByteArrayLiteral("pointing_hand");
case Qt::ForbiddenCursor:
return QByteArray("forbidden");
return QByteArrayLiteral("forbidden");
case Qt::OpenHandCursor:
return QByteArray("openhand");
return QByteArrayLiteral("openhand");
case Qt::ClosedHandCursor:
return QByteArray("closedhand");
return QByteArrayLiteral("closedhand");
case Qt::WhatsThisCursor:
return QByteArray("whats_this");
return QByteArrayLiteral("whats_this");
case Qt::BusyCursor:
return QByteArray("left_ptr_watch");
return QByteArrayLiteral("left_ptr_watch");
case Qt::DragMoveCursor:
return QByteArray("dnd-move");
return QByteArrayLiteral("dnd-move");
case Qt::DragCopyCursor:
return QByteArray("dnd-copy");
return QByteArrayLiteral("dnd-copy");
case Qt::DragLinkCursor:
return QByteArray("dnd-link");
return QByteArrayLiteral("dnd-link");
case KWin::ExtendedCursor::SizeNorthEast:
return QByteArrayLiteral("ne-resize");
case KWin::ExtendedCursor::SizeNorth:
return QByteArrayLiteral("n-resize");
case KWin::ExtendedCursor::SizeNorthWest:
return QByteArrayLiteral("nw-resize");
case KWin::ExtendedCursor::SizeEast:
return QByteArrayLiteral("e-resize");
case KWin::ExtendedCursor::SizeWest:
return QByteArrayLiteral("w-resize");
case KWin::ExtendedCursor::SizeSouthEast:
return QByteArrayLiteral("se-resize");
case KWin::ExtendedCursor::SizeSouth:
return QByteArrayLiteral("s-resize");
case KWin::ExtendedCursor::SizeSouthWest:
return QByteArrayLiteral("sw-resize");
default:
return QByteArray();
}

View file

@ -33,6 +33,48 @@ class QTimer;
namespace KWin
{
namespace ExtendedCursor {
enum Shape {
SizeNorthWest = 0x100 + 0,
SizeNorth = 0x100 + 1,
SizeNorthEast = 0x100 + 2,
SizeEast = 0x100 + 3,
SizeWest = 0x100 + 4,
SizeSouthEast = 0x100 + 5,
SizeSouth = 0x100 + 6,
SizeSouthWest = 0x100 + 7
};
}
/**
* Extension of Qt::CursorShape with values not currently present there
*/
/**
* @brief Wrapper round Qt::CursorShape with extensions enums into a single entity
*/
class KWIN_EXPORT CursorShape {
public:
CursorShape(Qt::CursorShape qtShape) {
m_shape = qtShape;
}
CursorShape(KWin::ExtendedCursor::Shape kwinShape) {
m_shape = kwinShape;
}
bool operator==(const CursorShape &o) const {
return m_shape == o.m_shape;
}
operator int() const {
return m_shape;
}
/**
* @brief The name of a cursor shape in the theme.
*/
QByteArray name() const;
private:
int m_shape = Qt::ArrowCursor;
};
/**
* @short Replacement for QCursor.
*
@ -93,13 +135,6 @@ public:
* @return int
*/
int themeSize() const;
/**
* @brief The name of a cursor shape in the theme.
*
* @param shape The cursor for which the name needs to be known.
* @return QByteArray
*/
QByteArray cursorName(Qt::CursorShape shape) const;
/**
* @return list of alternative names for the cursor with @p name
**/
@ -118,9 +153,9 @@ public:
**/
static void setPos(const QPoint &pos);
static void setPos(int x, int y);
static xcb_cursor_t x11Cursor(Qt::CursorShape shape);
static xcb_cursor_t x11Cursor(CursorShape shape);
/**
* Notice: if available always use the Qt::CursorShape variant to avoid cache duplicates for
* Notice: if available always use the CursorShape variant to avoid cache duplicates for
* ambiguous cursor names in the non existing cursor name spcification
**/
static xcb_cursor_t x11Cursor(const QByteArray &name);
@ -147,7 +182,7 @@ protected:
* a null cursor, an implementing subclass should implement this method if it can provide X11
* mouse cursors.
**/
virtual xcb_cursor_t getX11Cursor(Qt::CursorShape shape);
virtual xcb_cursor_t getX11Cursor(CursorShape shape);
/**
* Called from @link x11Cursor to actually retrieve the X11 cursor. Base implementation returns
* a null cursor, an implementing subclass should implement this method if it can provide X11

View file

@ -144,9 +144,9 @@ void X11Cursor::mousePolled()
}
}
xcb_cursor_t X11Cursor::getX11Cursor(Qt::CursorShape shape)
xcb_cursor_t X11Cursor::getX11Cursor(CursorShape shape)
{
return getX11Cursor(cursorName(shape));
return getX11Cursor(shape.name());
}
xcb_cursor_t X11Cursor::getX11Cursor(const QByteArray &name)

View file

@ -46,7 +46,7 @@ public:
void notifyCursorChanged();
protected:
virtual xcb_cursor_t getX11Cursor(Qt::CursorShape shape);
virtual xcb_cursor_t getX11Cursor(CursorShape shape);
xcb_cursor_t getX11Cursor(const QByteArray &name) override;
virtual void doSetPos();
virtual void doGetPos();

View file

@ -1178,7 +1178,7 @@ void CursorImage::updateDragCursor()
// TODO: add the cursor image
}
void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image)
void CursorImage::loadThemeCursor(CursorShape shape, Image *image)
{
loadThemeCursor(shape, m_cursors, image);
}

View file

@ -39,11 +39,11 @@ class SurfaceInterface;
namespace KWin
{
class CursorImage;
class InputRedirection;
class Toplevel;
class WaylandCursorTheme;
class CursorShape;
namespace Decoration
{
@ -203,7 +203,7 @@ private:
QImage image;
QPoint hotSpot;
};
void loadThemeCursor(Qt::CursorShape shape, Image *image);
void loadThemeCursor(CursorShape shape, Image *image);
void loadThemeCursor(const QByteArray &shape, Image *image);
template <typename T>
void loadThemeCursor(const T &shape, QHash<T, Image> &cursors, Image *image);
@ -235,7 +235,7 @@ private:
Image m_fallbackCursor;
Image m_moveResizeCursor;
Image m_windowSelectionCursor;
QHash<Qt::CursorShape, Image> m_cursors;
QHash<CursorShape, Image> m_cursors;
QHash<QByteArray, Image> m_cursorsByName;
QElapsedTimer m_surfaceRenderedTimer;
struct {

View file

@ -90,9 +90,9 @@ void WaylandCursorTheme::destroyTheme()
m_theme = nullptr;
}
wl_cursor_image *WaylandCursorTheme::get(Qt::CursorShape shape)
wl_cursor_image *WaylandCursorTheme::get(CursorShape shape)
{
return get(Cursor::self()->cursorName(shape));
return get(shape.name());
}
wl_cursor_image *WaylandCursorTheme::get(const QByteArray &name)

View file

@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <kwin_export.h>
#include <QObject>
#include "cursor.h"
struct wl_cursor_image;
struct wl_cursor_theme;
@ -45,7 +46,7 @@ public:
explicit WaylandCursorTheme(KWayland::Client::ShmPool *shm, QObject *parent = nullptr);
virtual ~WaylandCursorTheme();
wl_cursor_image *get(Qt::CursorShape shape);
wl_cursor_image *get(CursorShape shape);
wl_cursor_image *get(const QByteArray &name);
Q_SIGNALS: