internal/ui: bug fix: need to delay to capture a cursor

From the spec https://w3c.github.io/pointerlock/#extensions-to-the-element-interface,
capturing a cursor might require a cooltime. This change adds a delay
to capture a cursor just after escaping from a capture.

Closes #2693
This commit is contained in:
Hajime Hoshi 2023-07-07 01:17:28 +09:00
parent d0b6d2c41a
commit 7a7d4fd91f
2 changed files with 32 additions and 0 deletions

View File

@ -80,8 +80,10 @@ type userInterfaceImpl struct {
running bool running bool
cursorMode CursorMode cursorMode CursorMode
cursorPrevMode CursorMode cursorPrevMode CursorMode
captureCursorLater bool
cursorShape CursorShape cursorShape CursorShape
onceUpdateCalled bool onceUpdateCalled bool
lastCaptureExitTime time.Time
lastDeviceScaleFactor float64 lastDeviceScaleFactor float64
@ -189,6 +191,16 @@ func (u *userInterfaceImpl) CursorMode() CursorMode {
} }
func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) { func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) {
if mode == CursorModeCaptured && !u.canCaptureCursor() {
u.captureCursorLater = true
return
}
u.setCursorMode(mode)
}
func (u *userInterfaceImpl) setCursorMode(mode CursorMode) {
u.captureCursorLater = false
if !canvas.Truthy() { if !canvas.Truthy() {
return return
} }
@ -199,6 +211,7 @@ func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) {
u.cursorPrevMode = u.cursorMode u.cursorPrevMode = u.cursorMode
if u.cursorMode == CursorModeCaptured { if u.cursorMode == CursorModeCaptured {
document.Call("exitPointerLock") document.Call("exitPointerLock")
u.lastCaptureExitTime = time.Now()
} }
u.cursorMode = mode u.cursorMode = mode
switch mode { switch mode {
@ -272,7 +285,25 @@ func (u *userInterfaceImpl) isFocused() bool {
return true return true
} }
// canCaptureCursor reports whether a cursor can be captured or not now.
// Just after escaping from a capture, a browser might not be able to capture a cursor (#2693).
// If it is too early to capture a cursor, Ebitengine tries to delay it.
//
// See also https://w3c.github.io/pointerlock/#extensions-to-the-element-interface
//
// > Pointer lock is a transient activation-gated API, therefore a requestPointerLock() call
// > MUST fail if the relevant global object of this does not have transient activation.
// > This prevents locking upon initial navigation or re-acquiring lock without user's attention.
func (u *userInterfaceImpl) canCaptureCursor() bool {
// 1.5 [sec] seems enough in the real world.
return time.Now().Sub(u.lastCaptureExitTime) >= 1500*time.Millisecond
}
func (u *userInterfaceImpl) update() error { func (u *userInterfaceImpl) update() error {
if u.captureCursorLater && u.canCaptureCursor() {
u.setCursorMode(CursorModeCaptured)
}
if u.suspended() { if u.suspended() {
return hooks.SuspendAudio() return hooks.SuspendAudio()
} }

1
run.go
View File

@ -353,6 +353,7 @@ func CursorMode() CursorModeType {
// CursorModeCaptured also works on browsers. // CursorModeCaptured also works on browsers.
// When the user exits the captured mode not by SetCursorMode but by the UI (e.g., pressing ESC), // When the user exits the captured mode not by SetCursorMode but by the UI (e.g., pressing ESC),
// the previous cursor mode is set automatically. // the previous cursor mode is set automatically.
// On browsers, setting CursorModeCaptured might be delayed especially just after escaping from a capture.
// //
// SetCursorMode does nothing on mobiles. // SetCursorMode does nothing on mobiles.
// //