mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
ebiten: Add CursorShape/SetCursorShape/CursorShapeType
This change adds APIs to enable to use system cursor shapes other than the default shape (an arrow). This change doesn't add these cursors since they seem a little different on macOS from the other platforms. * GLFW_HRESIZE_CURSOR * GLFW_VRESIZE_CURSOR Closes #995
This commit is contained in:
parent
71e899acf3
commit
d00d0c8556
@ -16,12 +16,23 @@ package ebiten
|
||||
|
||||
import "github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||
|
||||
// CursorModeType represents
|
||||
// a render and coordinate mode of a mouse cursor.
|
||||
// CursorModeType represents a render and coordinate mode of a mouse cursor.
|
||||
type CursorModeType int
|
||||
|
||||
// CursorModeTypes
|
||||
const (
|
||||
CursorModeVisible CursorModeType = CursorModeType(driver.CursorModeVisible)
|
||||
CursorModeHidden CursorModeType = CursorModeType(driver.CursorModeHidden)
|
||||
CursorModeCaptured CursorModeType = CursorModeType(driver.CursorModeCaptured)
|
||||
)
|
||||
|
||||
// CursorShapeType represents a shape of a mouse cursor.
|
||||
type CursorShapeType int
|
||||
|
||||
// CursorShapeTypes
|
||||
const (
|
||||
CursorShapeDefault CursorShapeType = CursorShapeType(driver.CursorShapeDefault)
|
||||
CursorShapeText CursorShapeType = CursorShapeType(driver.CursorShapeText)
|
||||
CursorShapeCrosshair CursorShapeType = CursorShapeType(driver.CursorShapeCrosshair)
|
||||
CursorShapePointer CursorShapeType = CursorShapeType(driver.CursorShapePointer)
|
||||
)
|
94
examples/cursor/main.go
Normal file
94
examples/cursor/main.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2021 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
grids map[image.Rectangle]ebiten.CursorShapeType
|
||||
gridColors map[image.Rectangle]color.Color
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
pt := image.Pt(ebiten.CursorPosition())
|
||||
for r, c := range g.grids {
|
||||
if pt.In(r) {
|
||||
ebiten.SetCursorShape(c)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
ebiten.SetCursorShape(ebiten.CursorShapeDefault)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
for r, c := range g.gridColors {
|
||||
ebitenutil.DrawRect(screen, float64(r.Min.X), float64(r.Min.Y), float64(r.Dx()), float64(r.Dy()), c)
|
||||
}
|
||||
|
||||
switch ebiten.CursorShape() {
|
||||
case ebiten.CursorShapeDefault:
|
||||
ebitenutil.DebugPrint(screen, "CursorShape: Default")
|
||||
case ebiten.CursorShapeText:
|
||||
ebitenutil.DebugPrint(screen, "CursorShape: Text")
|
||||
case ebiten.CursorShapeCrosshair:
|
||||
ebitenutil.DebugPrint(screen, "CursorShape: Crosshair")
|
||||
case ebiten.CursorShapePointer:
|
||||
ebitenutil.DebugPrint(screen, "CursorShape: Pointer")
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
return screenWidth, screenHeight
|
||||
}
|
||||
|
||||
func main() {
|
||||
g := &Game{
|
||||
grids: map[image.Rectangle]ebiten.CursorShapeType{
|
||||
image.Rect(100, 100, 200, 300): ebiten.CursorShapeDefault,
|
||||
image.Rect(200, 100, 300, 300): ebiten.CursorShapeText,
|
||||
image.Rect(300, 100, 400, 300): ebiten.CursorShapeCrosshair,
|
||||
image.Rect(400, 100, 500, 300): ebiten.CursorShapePointer,
|
||||
},
|
||||
gridColors: map[image.Rectangle]color.Color{},
|
||||
}
|
||||
for rect, c := range g.grids {
|
||||
a := byte(0x40)
|
||||
if c%2 == 0 {
|
||||
a += 0x40
|
||||
}
|
||||
g.gridColors[rect] = color.Alpha{a}
|
||||
}
|
||||
|
||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||
ebiten.SetWindowTitle("Cursor (Ebiten Demo)")
|
||||
if err := ebiten.RunGame(g); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -21,3 +21,12 @@ const (
|
||||
CursorModeHidden
|
||||
CursorModeCaptured
|
||||
)
|
||||
|
||||
type CursorShape int
|
||||
|
||||
const (
|
||||
CursorShapeDefault CursorShape = iota
|
||||
CursorShapeText
|
||||
CursorShapeCrosshair
|
||||
CursorShapePointer
|
||||
)
|
@ -45,6 +45,9 @@ type UI interface {
|
||||
CursorMode() CursorMode
|
||||
SetCursorMode(mode CursorMode)
|
||||
|
||||
CursorShape() CursorShape
|
||||
SetCursorShape(shape CursorShape)
|
||||
|
||||
IsFullscreen() bool
|
||||
SetFullscreen(fullscreen bool)
|
||||
|
||||
|
@ -30,6 +30,7 @@ type (
|
||||
ModifierKey int
|
||||
MouseButton int
|
||||
PeripheralEvent int
|
||||
StandardCursor int
|
||||
)
|
||||
|
||||
const (
|
||||
@ -142,3 +143,12 @@ func (e ErrorCode) String() string {
|
||||
return fmt.Sprintf("GLFW error code (%d)", e)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ArrowCursor = StandardCursor(0x00036001)
|
||||
IBeamCursor = StandardCursor(0x00036002)
|
||||
CrosshairCursor = StandardCursor(0x00036003)
|
||||
HandCursor = StandardCursor(0x00036004)
|
||||
HResizeCursor = StandardCursor(0x00036005)
|
||||
VResizeCursor = StandardCursor(0x00036006)
|
||||
)
|
||||
|
@ -58,6 +58,15 @@ func (w windows) get(win *glfw.Window) *Window {
|
||||
return ww
|
||||
}
|
||||
|
||||
type Cursor struct {
|
||||
c *glfw.Cursor
|
||||
}
|
||||
|
||||
func CreateStandardCursor(shape StandardCursor) *Cursor {
|
||||
c := glfw.CreateStandardCursor(glfw.StandardCursor(shape))
|
||||
return &Cursor{c: c}
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
m *glfw.Monitor
|
||||
}
|
||||
@ -163,6 +172,14 @@ func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsC
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
func (w *Window) SetCursor(cursor *Cursor) {
|
||||
var c *glfw.Cursor
|
||||
if cursor != nil {
|
||||
c = cursor.c
|
||||
}
|
||||
w.w.SetCursor(c)
|
||||
}
|
||||
|
||||
func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) {
|
||||
var gcb glfw.FramebufferSizeCallback
|
||||
if cbfun != nil {
|
||||
|
@ -65,6 +65,16 @@ func (w glfwWindows) get(win uintptr) *Window {
|
||||
return ww
|
||||
}
|
||||
|
||||
type Cursor struct {
|
||||
c uintptr
|
||||
}
|
||||
|
||||
func CreateStandardCursor(shape StandardCursor) *Cursor {
|
||||
c := glfwDLL.call("glfwCreateStandardCursor", uintptr(shape))
|
||||
panicError()
|
||||
return &Cursor{c: c}
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
m uintptr
|
||||
}
|
||||
@ -192,6 +202,14 @@ func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsC
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
func (w *Window) SetCursor(cursor *Cursor) {
|
||||
var c uintptr
|
||||
if cursor != nil {
|
||||
c = cursor.c
|
||||
}
|
||||
glfwDLL.call("glfwSetCursor", w.w, c)
|
||||
}
|
||||
|
||||
func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) {
|
||||
var gcb uintptr
|
||||
if cbfun != nil {
|
||||
|
@ -64,6 +64,7 @@ type UserInterface struct {
|
||||
runnableOnUnfocused bool
|
||||
vsync bool
|
||||
iconImages []image.Image
|
||||
cursorShape driver.CursorShape
|
||||
|
||||
// err must be accessed from the main thread.
|
||||
err error
|
||||
@ -143,10 +144,13 @@ func init() {
|
||||
cacheMonitors()
|
||||
}
|
||||
|
||||
var glfwSystemCursors = map[driver.CursorShape]*glfw.Cursor{}
|
||||
|
||||
func initialize() error {
|
||||
if err := glfw.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glfw.WindowHint(glfw.Visible, glfw.False)
|
||||
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
|
||||
|
||||
@ -168,6 +172,12 @@ func initialize() error {
|
||||
theUI.initFullscreenWidthInDP = int(fromGLFWMonitorPixel(float64(v.Width), scale))
|
||||
theUI.initFullscreenHeightInDP = int(fromGLFWMonitorPixel(float64(v.Height), scale))
|
||||
|
||||
// Create system cursors. These cursors are destroyed at glfw.Terminate().
|
||||
glfwSystemCursors[driver.CursorShapeDefault] = nil
|
||||
glfwSystemCursors[driver.CursorShapeText] = glfw.CreateStandardCursor(glfw.IBeamCursor)
|
||||
glfwSystemCursors[driver.CursorShapeCrosshair] = glfw.CreateStandardCursor(glfw.CrosshairCursor)
|
||||
glfwSystemCursors[driver.CursorShapePointer] = glfw.CreateStandardCursor(glfw.HandCursor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -271,6 +281,21 @@ func (u *UserInterface) setInitCursorMode(mode driver.CursorMode) {
|
||||
u.m.Unlock()
|
||||
}
|
||||
|
||||
func (u *UserInterface) getCursorShape() driver.CursorShape {
|
||||
u.m.RLock()
|
||||
v := u.cursorShape
|
||||
u.m.RUnlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (u *UserInterface) setCursorShape(shape driver.CursorShape) driver.CursorShape {
|
||||
u.m.Lock()
|
||||
old := u.cursorShape
|
||||
u.cursorShape = shape
|
||||
u.m.Unlock()
|
||||
return old
|
||||
}
|
||||
|
||||
func (u *UserInterface) isInitWindowDecorated() bool {
|
||||
u.m.RLock()
|
||||
v := u.initWindowDecorated
|
||||
@ -557,6 +582,24 @@ func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorShape() driver.CursorShape {
|
||||
return u.getCursorShape()
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
|
||||
old := u.setCursorShape(shape)
|
||||
if old == shape {
|
||||
return
|
||||
}
|
||||
if !u.isRunning() {
|
||||
return
|
||||
}
|
||||
_ = u.t.Call(func() error {
|
||||
u.window.SetCursor(glfwSystemCursors[shape])
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UserInterface) DeviceScaleFactor() float64 {
|
||||
if !u.isRunning() {
|
||||
return devicescale.GetAt(u.initMonitor.GetPos())
|
||||
@ -615,6 +658,7 @@ func (u *UserInterface) createWindow() error {
|
||||
u.window.SetInputMode(glfw.StickyMouseButtonsMode, glfw.True)
|
||||
u.window.SetInputMode(glfw.StickyKeysMode, glfw.True)
|
||||
u.window.SetInputMode(glfw.CursorMode, driverCursorModeToGLFWCursorMode(u.getInitCursorMode()))
|
||||
u.window.SetCursor(glfwSystemCursors[u.getCursorShape()])
|
||||
u.window.SetTitle(u.title)
|
||||
// TODO: Set icons
|
||||
|
||||
|
@ -31,11 +31,27 @@ var (
|
||||
stringTransparent = js.ValueOf("transparent")
|
||||
)
|
||||
|
||||
func driverCursorShapeToCSSCursor(cursor driver.CursorShape) string {
|
||||
switch cursor {
|
||||
case driver.CursorShapeDefault:
|
||||
return "default"
|
||||
case driver.CursorShapeText:
|
||||
return "text"
|
||||
case driver.CursorShapeCrosshair:
|
||||
return "crosshair"
|
||||
case driver.CursorShapePointer:
|
||||
return "pointer"
|
||||
}
|
||||
return "auto"
|
||||
}
|
||||
|
||||
type UserInterface struct {
|
||||
runnableOnUnfocused bool
|
||||
vsync bool
|
||||
running bool
|
||||
initFocused bool
|
||||
cursorHidden bool
|
||||
cursorShape driver.CursorShape
|
||||
|
||||
sizeChanged bool
|
||||
contextLost bool
|
||||
@ -107,31 +123,56 @@ func (u *UserInterface) CursorMode() driver.CursorMode {
|
||||
return driver.CursorModeHidden
|
||||
}
|
||||
|
||||
if jsutil.Equal(canvas.Get("style").Get("cursor"), stringNone) {
|
||||
return driver.CursorModeVisible
|
||||
}
|
||||
if u.cursorHidden {
|
||||
return driver.CursorModeHidden
|
||||
}
|
||||
return driver.CursorModeVisible
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
|
||||
if !canvas.Truthy() {
|
||||
return
|
||||
}
|
||||
|
||||
var visible bool
|
||||
switch mode {
|
||||
case driver.CursorModeVisible:
|
||||
visible = true
|
||||
if u.cursorHidden {
|
||||
return
|
||||
}
|
||||
u.cursorHidden = false
|
||||
case driver.CursorModeHidden:
|
||||
visible = false
|
||||
if !u.cursorHidden {
|
||||
return
|
||||
}
|
||||
u.cursorHidden = true
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if visible {
|
||||
canvas.Get("style").Set("cursor", "auto")
|
||||
if u.cursorHidden {
|
||||
canvas.Get("style").Set("cursor", stringNone)
|
||||
} else {
|
||||
canvas.Get("style").Set("cursor", "none")
|
||||
canvas.Get("style").Set("cursor", driverCursorShapeToCSSCursor(u.cursorShape))
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorShape() driver.CursorShape {
|
||||
if !canvas.Truthy() {
|
||||
return driver.CursorShapeDefault
|
||||
}
|
||||
return u.cursorShape
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
|
||||
if !canvas.Truthy() {
|
||||
return
|
||||
}
|
||||
if u.cursorShape == shape {
|
||||
return
|
||||
}
|
||||
u.cursorShape = shape
|
||||
if !u.cursorHidden {
|
||||
canvas.Get("style").Set("cursor", driverCursorShapeToCSSCursor(u.cursorShape))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,6 +370,14 @@ func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorShape() driver.CursorShape {
|
||||
return driver.CursorShapeDefault
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFullscreen() bool {
|
||||
return false
|
||||
}
|
||||
|
8
run.go
8
run.go
@ -241,6 +241,14 @@ func SetCursorMode(mode CursorModeType) {
|
||||
uiDriver().SetCursorMode(driver.CursorMode(mode))
|
||||
}
|
||||
|
||||
func CursorShape() CursorShapeType {
|
||||
return CursorShapeType(uiDriver().CursorShape())
|
||||
}
|
||||
|
||||
func SetCursorShape(shape CursorShapeType) {
|
||||
uiDriver().SetCursorShape(driver.CursorShape(shape))
|
||||
}
|
||||
|
||||
// IsFullscreen reports whether the current mode is fullscreen or not.
|
||||
//
|
||||
// IsFullscreen always returns false on browsers or mobiles.
|
||||
|
Loading…
Reference in New Issue
Block a user