Skip to content

Commit

Permalink
Merge pull request fyne-io#1601 from MagicalTux/custom-cursors-proposal
Browse files Browse the repository at this point in the history
Proposal: Custom cursors in Fyne
  • Loading branch information
andydotxyz authored Dec 2, 2020
2 parents 5065c28 + 17ea20c commit 157776c
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 26 deletions.
24 changes: 21 additions & 3 deletions driver/desktop/cursor.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package desktop

// Cursor represents a standard fyne cursor
type Cursor int
import "image"

// Cursor interface is used for objects usable as cursor
//
// Since: 2.0.0
type Cursor interface {
Image() (image.Image, int, int) // Image returns the image for the given cursor, or nil to hide the cursor
}

// StandardCursor represents a standard fyne cursor
//
// Since: 2.0.0
type StandardCursor int

// Image will always return nil for StandardCursor
//
// Since: 2.0.0
func (d StandardCursor) Image() (image.Image, int, int) {
return nil, 0, 0
}

const (
// DefaultCursor is the default cursor typically an arrow
DefaultCursor Cursor = iota
DefaultCursor StandardCursor = iota
// TextCursor is the cursor often used to indicate text selection
TextCursor
// CrosshairCursor is the cursor often used to indicate bitmaps
Expand Down
53 changes: 36 additions & 17 deletions internal/driver/glfw/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ const (
)

var (
cursorMap map[desktop.Cursor]*glfw.Cursor
cursorMap map[desktop.StandardCursor]*glfw.Cursor
defaultTitle = "Fyne Application"
)

func initCursors() {
cursorMap = map[desktop.Cursor]*glfw.Cursor{
cursorMap = map[desktop.StandardCursor]*glfw.Cursor{
desktop.DefaultCursor: glfw.CreateStandardCursor(glfw.ArrowCursor),
desktop.TextCursor: glfw.CreateStandardCursor(glfw.IBeamCursor),
desktop.CrosshairCursor: glfw.CreateStandardCursor(glfw.CrosshairCursor),
Expand All @@ -52,11 +52,12 @@ type window struct {
decorate bool
fixedSize bool

cursor *glfw.Cursor
canvas *glCanvas
title string
icon fyne.Resource
mainmenu *fyne.MainMenu
cursor desktop.Cursor
customCursor *glfw.Cursor
canvas *glCanvas
title string
icon fyne.Resource
mainmenu *fyne.MainMenu

clipboard fyne.Clipboard

Expand Down Expand Up @@ -558,22 +559,31 @@ func (w *window) findObjectAtPositionMatching(canvas *glCanvas, mouse fyne.Posit
return driver.FindObjectAtPositionMatching(mouse, matches, canvas.Overlays().Top(), canvas.menu, canvas.Content())
}

func fyneToNativeCursor(cursor desktop.Cursor) *glfw.Cursor {
ret, ok := cursorMap[cursor]
if !ok {
return cursorMap[desktop.DefaultCursor]
func fyneToNativeCursor(cursor desktop.Cursor) (*glfw.Cursor, bool) {
switch v := cursor.(type) {
case desktop.StandardCursor:
ret, ok := cursorMap[v]
if !ok {
return cursorMap[desktop.DefaultCursor], false
}
return ret, false
default:
img, x, y := cursor.Image()
if img == nil {
return nil, true
}
return glfw.CreateCursor(img, x, y), true
}
return ret
}

func (w *window) mouseMoved(viewport *glfw.Window, xpos float64, ypos float64) {
w.mousePos = fyne.NewPos(internal.UnscaleInt(w.canvas, int(xpos)), internal.UnscaleInt(w.canvas, int(ypos)))

cursor := cursorMap[desktop.DefaultCursor]
cursor := desktop.Cursor(desktop.DefaultCursor)

obj, pos, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool {
if cursorable, ok := object.(desktop.Cursorable); ok {
fyneCursor := cursorable.Cursor()
cursor = fyneToNativeCursor(fyneCursor)
cursor = cursorable.Cursor()
}

_, hover := object.(desktop.Hoverable)
Expand All @@ -582,12 +592,21 @@ func (w *window) mouseMoved(viewport *glfw.Window, xpos float64, ypos float64) {

if w.cursor != cursor {
// cursor has changed, store new cursor and apply change via glfw
rawCursor, isCustomCursor := fyneToNativeCursor(cursor)
w.cursor = cursor
if cursor == nil {

if rawCursor == nil {
viewport.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
} else {
viewport.SetInputMode(glfw.CursorMode, glfw.CursorNormal)
viewport.SetCursor(cursor)
viewport.SetCursor(rawCursor)
}
if w.customCursor != nil {
w.customCursor.Destroy()
w.customCursor = nil
}
if isCustomCursor {
w.customCursor = rawCursor
}
}
if obj != nil && !w.objIsDragged(obj) {
Expand Down
12 changes: 6 additions & 6 deletions internal/driver/glfw/window_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,16 @@ func TestWindow_Cursor(t *testing.T) {
w.SetContent(widget.NewVBox(e, h, b))

w.mouseMoved(w.viewport, 10, float64(e.Position().Y+10))
textCursor := cursorMap[desktop.TextCursor]
assert.Same(t, textCursor, w.cursor)
textCursor := desktop.TextCursor
assert.Equal(t, textCursor, w.cursor)

w.mouseMoved(w.viewport, 10, float64(h.Position().Y+10))
pointerCursor := cursorMap[desktop.PointerCursor]
assert.Same(t, pointerCursor, w.cursor)
pointerCursor := desktop.PointerCursor
assert.Equal(t, pointerCursor, w.cursor)

w.mouseMoved(w.viewport, 10, float64(b.Position().Y+10))
defaultCursor := cursorMap[desktop.DefaultCursor]
assert.Same(t, defaultCursor, w.cursor)
defaultCursor := desktop.DefaultCursor
assert.Equal(t, defaultCursor, w.cursor)
}

func TestWindow_HandleHoverable(t *testing.T) {
Expand Down

0 comments on commit 157776c

Please sign in to comment.