Skip to content

Commit

Permalink
ebiten: add more cursor shapes
Browse files Browse the repository at this point in the history
This change adds these new cursor shapes:

* CursorShapeNESWResize
* CursorShapeNWSEResize
* CursorShapeMove
* CursorShapeNotAllowed

Closes hajimehoshi#2476
  • Loading branch information
hajimehoshi committed Jul 22, 2023
1 parent 269b557 commit 91e1c0e
Show file tree
Hide file tree
Showing 21 changed files with 347 additions and 119 deletions.
16 changes: 10 additions & 6 deletions cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ type CursorShapeType = ui.CursorShape

// CursorShapeTypes
const (
CursorShapeDefault CursorShapeType = ui.CursorShapeDefault
CursorShapeText CursorShapeType = ui.CursorShapeText
CursorShapeCrosshair CursorShapeType = ui.CursorShapeCrosshair
CursorShapePointer CursorShapeType = ui.CursorShapePointer
CursorShapeEWResize CursorShapeType = ui.CursorShapeEWResize
CursorShapeNSResize CursorShapeType = ui.CursorShapeNSResize
CursorShapeDefault CursorShapeType = CursorShapeType(ui.CursorShapeDefault)
CursorShapeText CursorShapeType = CursorShapeType(ui.CursorShapeText)
CursorShapeCrosshair CursorShapeType = CursorShapeType(ui.CursorShapeCrosshair)
CursorShapePointer CursorShapeType = CursorShapeType(ui.CursorShapePointer)
CursorShapeEWResize CursorShapeType = CursorShapeType(ui.CursorShapeEWResize)
CursorShapeNSResize CursorShapeType = CursorShapeType(ui.CursorShapeNSResize)
CursorShapeNESWResize CursorShapeType = CursorShapeType(ui.CursorShapeNESWResize)
CursorShapeNWSEResize CursorShapeType = CursorShapeType(ui.CursorShapeNWSEResize)
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed)
)
33 changes: 27 additions & 6 deletions examples/cursor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
type Game struct {
grids map[image.Rectangle]ebiten.CursorShapeType
gridColors map[image.Rectangle]color.Color
cursor ebiten.CursorShapeType
}

func (g *Game) Update() error {
Expand All @@ -44,7 +45,12 @@ func (g *Game) Update() error {
break
}
}
ebiten.SetCursorShape(cursor)

// Call SetCursorShape only when this is changed to test Ebitengine remembers the current cursor correctly even when it is hidden.
if g.cursor != cursor {
ebiten.SetCursorShape(cursor)
g.cursor = cursor
}

if inpututil.IsKeyJustPressed(ebiten.KeyC) {
switch ebiten.CursorMode() {
Expand Down Expand Up @@ -76,6 +82,14 @@ func (g *Game) Draw(screen *ebiten.Image) {
ebitenutil.DebugPrint(screen, "CursorShape: EW Resize")
case ebiten.CursorShapeNSResize:
ebitenutil.DebugPrint(screen, "CursorShape: NS Resize")
case ebiten.CursorShapeNESWResize:
ebitenutil.DebugPrint(screen, "CursorShape: NESW Resize")
case ebiten.CursorShapeNWSEResize:
ebitenutil.DebugPrint(screen, "CursorShape: NWSE Resize")
case ebiten.CursorShapeMove:
ebitenutil.DebugPrint(screen, "CursorShape: Move")
case ebiten.CursorShapeNotAllowed:
ebitenutil.DebugPrint(screen, "CursorShape: Not Allowed")
}
}

Expand All @@ -89,18 +103,25 @@ func main() {
image.Rect(100, 100, 200, 200): ebiten.CursorShapeDefault,
image.Rect(200, 100, 300, 200): ebiten.CursorShapeText,
image.Rect(300, 100, 400, 200): ebiten.CursorShapeCrosshair,
image.Rect(100, 200, 200, 300): ebiten.CursorShapePointer,
image.Rect(200, 200, 300, 300): ebiten.CursorShapeEWResize,
image.Rect(300, 200, 400, 300): ebiten.CursorShapeNSResize,
image.Rect(400, 100, 500, 200): ebiten.CursorShapePointer,
image.Rect(100, 200, 200, 300): ebiten.CursorShapeEWResize,
image.Rect(200, 200, 300, 300): ebiten.CursorShapeNSResize,
image.Rect(300, 200, 400, 300): ebiten.CursorShapeNESWResize,
image.Rect(400, 200, 500, 300): ebiten.CursorShapeNWSEResize,
image.Rect(100, 300, 200, 400): ebiten.CursorShapeMove,
image.Rect(200, 300, 300, 400): ebiten.CursorShapeNotAllowed,
},
gridColors: map[image.Rectangle]color.Color{},
}
for rect, c := range g.grids {
clr := color.RGBA{0x40, 0x40, 0x40, 0xff}
if c%2 == 0 {
switch c % 3 {
case 0:
clr.R = 0x80
} else {
case 1:
clr.G = 0x80
case 2:
clr.B = 0x80
}
g.gridColors[rect] = clr
}
Expand Down
70 changes: 58 additions & 12 deletions internal/cglfw/cocoa_window_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -1728,18 +1728,64 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
@autoreleasepool {

if (shape == GLFW_ARROW_CURSOR)
cursor->ns.object = [NSCursor arrowCursor];
else if (shape == GLFW_IBEAM_CURSOR)
cursor->ns.object = [NSCursor IBeamCursor];
else if (shape == GLFW_CROSSHAIR_CURSOR)
cursor->ns.object = [NSCursor crosshairCursor];
else if (shape == GLFW_HAND_CURSOR)
cursor->ns.object = [NSCursor pointingHandCursor];
else if (shape == GLFW_HRESIZE_CURSOR)
cursor->ns.object = [NSCursor resizeLeftRightCursor];
else if (shape == GLFW_VRESIZE_CURSOR)
cursor->ns.object = [NSCursor resizeUpDownCursor];
SEL cursorSelector = NULL;

switch (shape)
{
case GLFW_HRESIZE_CURSOR:
cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor");
break;
case GLFW_VRESIZE_CURSOR:
cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor");
break;
case GLFW_RESIZE_NWSE_CURSOR:
cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor");
break;
case GLFW_RESIZE_NESW_CURSOR:
cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor");
break;
}

if (cursorSelector && [NSCursor respondsToSelector:cursorSelector])
{
id object = [NSCursor performSelector:cursorSelector];
if ([object isKindOfClass:[NSCursor class]])
cursor->ns.object = object;
}

if (!cursor->ns.object)
{
switch (shape)
{
case GLFW_ARROW_CURSOR:
cursor->ns.object = [NSCursor arrowCursor];
break;
case GLFW_IBEAM_CURSOR:
cursor->ns.object = [NSCursor IBeamCursor];
break;
case GLFW_CROSSHAIR_CURSOR:
cursor->ns.object = [NSCursor crosshairCursor];
break;
case GLFW_HAND_CURSOR:
cursor->ns.object = [NSCursor pointingHandCursor];
break;
case GLFW_RESIZE_ALL_CURSOR:
{
// Use the OS's resource: https://stackoverflow.com/a/21786835/5435443
NSString *cursorName = @"move";
NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:cursorName];
NSImage *image = [[NSImage alloc] initByReferencingFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]];
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]];
cursor->ns.object = [[NSCursor alloc] initWithImage:image
hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue],
[[info valueForKey:@"hoty"] doubleValue])];
break;
}
case GLFW_NOT_ALLOWED_CURSOR:
cursor->ns.object = [NSCursor operationNotAllowedCursor];
break;
}
}

if (!cursor->ns.object)
{
Expand Down
6 changes: 6 additions & 0 deletions internal/cglfw/glfw3.h
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,12 @@ extern "C" {
#define GLFW_VRESIZE_CURSOR 0x00036006
/*! @} */

// Added in GLFW v3.4.
#define GLFW_RESIZE_NWSE_CURSOR 0x00036007
#define GLFW_RESIZE_NESW_CURSOR 0x00036008
#define GLFW_RESIZE_ALL_CURSOR 0x00036009
#define GLFW_NOT_ALLOWED_CURSOR 0x0003600A

#define GLFW_CONNECTED 0x00040001
#define GLFW_DISCONNECTED 0x00040002

Expand Down
6 changes: 5 additions & 1 deletion internal/cglfw/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,11 @@ GLFWAPI GLFWcursor* glfwCreateStandardCursor(int shape)
shape != GLFW_CROSSHAIR_CURSOR &&
shape != GLFW_HAND_CURSOR &&
shape != GLFW_HRESIZE_CURSOR &&
shape != GLFW_VRESIZE_CURSOR)
shape != GLFW_VRESIZE_CURSOR &&
shape != GLFW_RESIZE_NWSE_CURSOR &&
shape != GLFW_RESIZE_NESW_CURSOR &&
shape != GLFW_RESIZE_ALL_CURSOR &&
shape != GLFW_NOT_ALLOWED_CURSOR)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid standard cursor 0x%08X", shape);
return NULL;
Expand Down
123 changes: 88 additions & 35 deletions internal/cglfw/wl_window_linbsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1823,28 +1823,6 @@ const struct wl_data_device_listener dataDeviceListener =
dataDeviceHandleSelection,
};

// Translates a GLFW standard cursor to a theme cursor name
//
static char *translateCursorShape(int shape)
{
switch (shape)
{
case GLFW_ARROW_CURSOR:
return "left_ptr";
case GLFW_IBEAM_CURSOR:
return "xterm";
case GLFW_CROSSHAIR_CURSOR:
return "crosshair";
case GLFW_HAND_CURSOR:
return "hand2";
case GLFW_HRESIZE_CURSOR:
return "sb_h_double_arrow";
case GLFW_VRESIZE_CURSOR:
return "sb_v_double_arrow";
}
return NULL;
}

void _glfwAddSeatListenerWayland(struct wl_seat* seat)
{
wl_seat_add_listener(seat, &seatListener, NULL);
Expand Down Expand Up @@ -2392,26 +2370,101 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,

int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
struct wl_cursor* standardCursor;
// See GLFW 3.4 implementation.
const char* name = NULL;

standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
translateCursorShape(shape));
if (!standardCursor)
// Try the XDG names first
switch (shape)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Standard cursor \"%s\" not found",
translateCursorShape(shape));
return GLFW_FALSE;
case GLFW_ARROW_CURSOR:
name = "default";
break;
case GLFW_IBEAM_CURSOR:
name = "text";
break;
case GLFW_CROSSHAIR_CURSOR:
name = "crosshair";
break;
case GLFW_HAND_CURSOR:
name = "pointer";
break;
case GLFW_HRESIZE_CURSOR:
name = "ew-resize";
break;
case GLFW_VRESIZE_CURSOR:
name = "ns-resize";
break;
case GLFW_RESIZE_NWSE_CURSOR:
name = "nwse-resize";
break;
case GLFW_RESIZE_NESW_CURSOR:
name = "nesw-resize";
break;
case GLFW_RESIZE_ALL_CURSOR:
name = "all-scroll";
break;
case GLFW_NOT_ALLOWED_CURSOR:
name = "not-allowed";
break;
}

cursor->wl.cursor = standardCursor;
cursor->wl.currentImage = 0;
cursor->wl.cursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name);

if (_glfw.wl.cursorThemeHiDPI)
{
standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorThemeHiDPI,
translateCursorShape(shape));
cursor->wl.cursorHiDPI = standardCursor;
cursor->wl.cursorHiDPI =
wl_cursor_theme_get_cursor(_glfw.wl.cursorThemeHiDPI, name);
}

if (!cursor->wl.cursor)
{
// Fall back to the core X11 names
switch (shape)
{
case GLFW_ARROW_CURSOR:
name = "left_ptr";
break;
case GLFW_IBEAM_CURSOR:
name = "xterm";
break;
case GLFW_CROSSHAIR_CURSOR:
name = "crosshair";
break;
case GLFW_HAND_CURSOR:
name = "hand2";
break;
case GLFW_HRESIZE_CURSOR:
name = "sb_h_double_arrow";
break;
case GLFW_VRESIZE_CURSOR:
name = "sb_v_double_arrow";
break;
case GLFW_RESIZE_ALL_CURSOR:
name = "fleur";
break;
default:
//_glfwInputError(GLFW_CURSOR_UNAVAILABLE,
// "Wayland: Standard cursor shape unavailable");
return GLFW_FALSE;
}

cursor->wl.cursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name);
if (!cursor->wl.cursor)
{
//_glfwInputError(GLFW_CURSOR_UNAVAILABLE,
// "Wayland: Failed to create standard cursor \"%s\"",
// name);
return GLFW_FALSE;
}

if (_glfw.wl.cursorThemeHiDPI)
{
if (!cursor->wl.cursorHiDPI)
{
cursor->wl.cursorHiDPI =
wl_cursor_theme_get_cursor(_glfw.wl.cursorThemeHiDPI, name);
}
}
}

return GLFW_TRUE;
Expand Down
6 changes: 6 additions & 0 deletions internal/cglfw/x11_init_linbsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,12 @@ static GLFWbool initExtensions(void)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageDestroy");
_glfw.x11.xcursor.ImageLoadCursor = (PFN_XcursorImageLoadCursor)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
_glfw.x11.xcursor.GetTheme = (PFN_XcursorGetTheme)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorGetTheme");
_glfw.x11.xcursor.GetDefaultSize = (PFN_XcursorGetDefaultSize)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorGetDefaultSize");
_glfw.x11.xcursor.LibraryLoadImage = (PFN_XcursorLibraryLoadImage)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorLibraryLoadImage");
}

#if defined(__CYGWIN__)
Expand Down
9 changes: 9 additions & 0 deletions internal/cglfw/x11_platform_linbsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,15 @@ typedef int (* PFN_XRRUpdateConfiguration)(XEvent*);
typedef XcursorImage* (* PFN_XcursorImageCreate)(int,int);
typedef void (* PFN_XcursorImageDestroy)(XcursorImage*);
typedef Cursor (* PFN_XcursorImageLoadCursor)(Display*,const XcursorImage*);
typedef char* (* PFN_XcursorGetTheme)(Display*);
typedef int (* PFN_XcursorGetDefaultSize)(Display*);
typedef XcursorImage* (* PFN_XcursorLibraryLoadImage)(const char*,const char*,int);
#define XcursorImageCreate _glfw.x11.xcursor.ImageCreate
#define XcursorImageDestroy _glfw.x11.xcursor.ImageDestroy
#define XcursorImageLoadCursor _glfw.x11.xcursor.ImageLoadCursor
#define XcursorGetTheme _glfw.x11.xcursor.GetTheme
#define XcursorGetDefaultSize _glfw.x11.xcursor.GetDefaultSize
#define XcursorLibraryLoadImage _glfw.x11.xcursor.LibraryLoadImage

typedef Bool (* PFN_XineramaIsActive)(Display*);
typedef Bool (* PFN_XineramaQueryExtension)(Display*,int*,int*);
Expand Down Expand Up @@ -327,6 +333,9 @@ typedef struct _GLFWlibraryX11
PFN_XcursorImageCreate ImageCreate;
PFN_XcursorImageDestroy ImageDestroy;
PFN_XcursorImageLoadCursor ImageLoadCursor;
PFN_XcursorGetTheme GetTheme;
PFN_XcursorGetDefaultSize GetDefaultSize;
PFN_XcursorLibraryLoadImage LibraryLoadImage;
} xcursor;

struct {
Expand Down
Loading

0 comments on commit 91e1c0e

Please sign in to comment.