From ed19d6fae91f7cd538585d6f1c9b6655a1d3960b Mon Sep 17 00:00:00 2001 From: Zachary Burkett Date: Fri, 13 Dec 2019 22:30:03 -0500 Subject: [PATCH] Add cursor capture functionality (#1016) Fixes #1016 --- cursormodes.go | 28 +++++++++++++++++++ internal/driver/cursormode.go | 23 +++++++++++++++ internal/driver/ui.go | 4 +-- internal/glfw/const.go | 7 +++-- internal/uidriver/glfw/ui.go | 51 +++++++++++++++++++++++----------- internal/uidriver/js/ui.go | 21 +++++++++++--- internal/uidriver/mobile/ui.go | 6 ++-- run.go | 44 +++++++++++++++++++++-------- 8 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 cursormodes.go create mode 100644 internal/driver/cursormode.go diff --git a/cursormodes.go b/cursormodes.go new file mode 100644 index 000000000..4ca60b677 --- /dev/null +++ b/cursormodes.go @@ -0,0 +1,28 @@ +// Copyright 2019 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. + +package ebiten + +import "github.com/hajimehoshi/ebiten/internal/driver" + +// A CursorModeType represents +// a render and coordinate mode of a mouse cursor. +type CursorModeType int + +// Cursor Modes +const ( + CursorModeVisible = CursorModeType(driver.CursorModeVisible) + CursorModeHidden = CursorModeType(driver.CursorModeHidden) + CursorModeCaptured = CursorModeType(driver.CursorModeCaptured) +) diff --git a/internal/driver/cursormode.go b/internal/driver/cursormode.go new file mode 100644 index 000000000..9348b45f1 --- /dev/null +++ b/internal/driver/cursormode.go @@ -0,0 +1,23 @@ +// Copyright 2019 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. + +package driver + +type CursorMode int + +const ( + CursorModeVisible CursorMode = 1 << iota + CursorModeHidden + CursorModeCaptured +) diff --git a/internal/driver/ui.go b/internal/driver/ui.go index 543904176..8d9055a29 100644 --- a/internal/driver/ui.go +++ b/internal/driver/ui.go @@ -34,7 +34,7 @@ type UI interface { RunWithoutMainLoop(width, height int, scale float64, title string, context UIContext, graphics Graphics) <-chan error DeviceScaleFactor() float64 - IsCursorVisible() bool + CursorMode() CursorMode IsFullscreen() bool IsRunnableInBackground() bool IsVsyncEnabled() bool @@ -46,7 +46,7 @@ type UI interface { WindowPosition() (int, int) IsScreenTransparent() bool - SetCursorVisible(visible bool) + SetCursorMode(mode CursorMode) SetFullscreen(fullscreen bool) SetRunnableInBackground(runnableInBackground bool) SetScreenScale(scale float64) diff --git a/internal/glfw/const.go b/internal/glfw/const.go index 8640b112b..c1643dbbc 100644 --- a/internal/glfw/const.go +++ b/internal/glfw/const.go @@ -90,9 +90,10 @@ const ( ) const ( - CursorHidden = 0x00034002 - CursorNormal = 0x00034001 - NoAPI = 0 + CursorDisabled = 0x00034003 + CursorHidden = 0x00034002 + CursorNormal = 0x00034001 + NoAPI = 0 ) const ( diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index d356b730e..722c98558 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -58,7 +58,7 @@ type UserInterface struct { initFullscreenWidthInDP int initFullscreenHeightInDP int initFullscreen bool - initCursorVisible bool + initCursorMode driver.CursorMode initWindowDecorated bool initWindowResizable bool initWindowPositionX int @@ -86,7 +86,7 @@ var ( theUI = &UserInterface{ origPosX: invalidPos, origPosY: invalidPos, - initCursorVisible: true, + initCursorMode: driver.CursorModeVisible, initWindowDecorated: true, initWindowPositionX: invalidPos, initWindowPositionY: invalidPos, @@ -209,16 +209,16 @@ func (u *UserInterface) setInitFullscreen(initFullscreen bool) { u.m.Unlock() } -func (u *UserInterface) isInitCursorVisible() bool { +func (u *UserInterface) getInitCursorMode() driver.CursorMode { u.m.RLock() - v := u.initCursorVisible + v := u.initCursorMode u.m.RUnlock() return v } -func (u *UserInterface) setInitCursorVisible(visible bool) { +func (u *UserInterface) setInitCursorMode(mode driver.CursorMode) { u.m.Lock() - u.initCursorVisible = visible + u.initCursorMode = mode u.m.Unlock() } @@ -487,27 +487,44 @@ func (u *UserInterface) adjustPosition(x, y int) (int, int) { return x - int(ox/s), y - int(oy/s) } -func (u *UserInterface) IsCursorVisible() bool { +func (u *UserInterface) CursorMode() driver.CursorMode { if !u.isRunning() { - return u.isInitCursorVisible() + return u.getInitCursorMode() } - v := false + var v driver.CursorMode _ = u.t.Call(func() error { - v = u.window.GetInputMode(glfw.CursorMode) == glfw.CursorNormal + mode := u.window.GetInputMode(glfw.CursorMode) + switch mode { + case glfw.CursorNormal: + v = driver.CursorModeVisible + case glfw.CursorHidden: + v = driver.CursorModeHidden + case glfw.CursorDisabled: + v = driver.CursorModeCaptured + default: + panic(fmt.Sprintf("invalid cursor mode: %d", mode)) + } return nil }) return v } -func (u *UserInterface) SetCursorVisible(visible bool) { +func (u *UserInterface) SetCursorMode(mode driver.CursorMode) { if !u.isRunning() { - u.setInitCursorVisible(visible) + u.setInitCursorMode(mode) return } _ = u.t.Call(func() error { - c := glfw.CursorNormal - if !visible { + var c int + switch mode { + case driver.CursorModeVisible: + c = glfw.CursorNormal + case driver.CursorModeHidden: c = glfw.CursorHidden + case driver.CursorModeCaptured: + c = glfw.CursorDisabled + default: + panic(fmt.Sprintf("invalid cursor mode: %d", mode)) } u.window.SetInputMode(glfw.CursorMode, c) return nil @@ -649,10 +666,12 @@ func (u *UserInterface) createWindow() error { u.window.SetInputMode(glfw.StickyMouseButtonsMode, glfw.True) u.window.SetInputMode(glfw.StickyKeysMode, glfw.True) - // Solve the initial properties of the window. mode := glfw.CursorNormal - if !u.isInitCursorVisible() { + switch u.getInitCursorMode() { + case driver.CursorModeHidden: mode = glfw.CursorHidden + case driver.CursorModeCaptured: + mode = glfw.CursorDisabled } u.window.SetInputMode(glfw.CursorMode, mode) u.window.SetTitle(u.title) diff --git a/internal/uidriver/js/ui.go b/internal/uidriver/js/ui.go index ad16422ed..316065085 100644 --- a/internal/uidriver/js/ui.go +++ b/internal/uidriver/js/ui.go @@ -123,12 +123,25 @@ func (u *UserInterface) adjustPosition(x, y int) (int, int) { return int(float64(x) / s), int(float64(y) / s) } -func (u *UserInterface) IsCursorVisible() bool { - // The initial value is an empty string, so don't compare with "auto" here. - return canvas.Get("style").Get("cursor").String() != "none" +func (u *UserInterface) CursorMode() driver.CursorMode { + if u.IsCursorVisible { + return driver.CursorModeVisible + } else { + return driver.CursorModeHidden + } } -func (u *UserInterface) SetCursorVisible(visible bool) { +func (u *UserInterface) SetCursorMode(mode driver.CursorMode) { + var visible bool + switch mode { + case driver.CursorModeVisible: + visible = true + case driver.CursorModeHidden: + visible = false + default: + return + } + if visible { canvas.Get("style").Set("cursor", "auto") } else { diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index 07dd71597..bf07467a1 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -383,11 +383,11 @@ func (u *UserInterface) adjustPosition(x, y int) (int, int) { return int(float64(x)/s - ox/as), int(float64(y)/s - oy/as) } -func (u *UserInterface) IsCursorVisible() bool { - return false +func (u *UserInterface) CursorMode() driver.CursorMode { + return driver.CursorModeHidden } -func (u *UserInterface) SetCursorVisible(visible bool) { +func (u *UserInterface) SetCursorMode(mode driver.CursorMode) { // Do nothing } diff --git a/run.go b/run.go index 29c5175dc..a432903d9 100644 --- a/run.go +++ b/run.go @@ -261,26 +261,46 @@ func ScreenScale() float64 { return uiDriver().ScreenScale() } -// IsCursorVisible returns a boolean value indicating whether -// the cursor is visible or not. +// CursorMode returns the current cursor mode. // -// IsCursorVisible always returns false on mobiles. +// On browsers, only CursorModeVisible and CursorModeHidden are supported. // -// IsCursorVisible is concurrent-safe. +// CursorMode returns CursorModeHidden on mobiles. +// +// CursorMode is concurrent-safe. +func CursorMode() CursorModeType { + return CursorModeType(uiDriver().CursorMode()) +} + +// SetCursorMode sets the render and capture mode of the mouse cursor. +// CursorModeVisible sets the cursor to always be visible. +// CursorModeHidden hides the system cursor when over the window. +// CursorModeCaptured hides the system cursor and locks it to the window. +// +// On browsers, only CursorModeVisible and CursorModeHidden are supported. +// +// SetCursorMode does nothing on mobiles. +// +// SetCursorMode is concurrent-safe. +func SetCursorMode(mode CursorModeType) { + uiDriver().SetCursorMode(driver.CursorMode(mode)) +} + +// IsCursorVisible is deprecated as of 1.11.0-alpha. Use CursorMode instead. func IsCursorVisible() bool { - return uiDriver().IsCursorVisible() + return CursorMode() == CursorModeVisible } -// SetCursorVisible changes the state of cursor visiblity. -// -// SetCursorVisible does nothing on mobiles. -// -// SetCursorVisible is concurrent-safe. +// SetCursorVisible is deprecated as of 1.11.0-alpha. Use SetCursorMode instead. func SetCursorVisible(visible bool) { - uiDriver().SetCursorVisible(visible) + if visible { + SetCursorMode(CursorModeVisible) + } else { + SetCursorMode(CursorModeHidden) + } } -// SetCursorVisibility is deprecated as of 1.6.0-alpha. Use SetCursorVisible instead. +// SetCursorVisibility is deprecated as of 1.6.0-alpha. Use SetCursorMode instead. func SetCursorVisibility(visible bool) { SetCursorVisible(visible) }