From 7a7d4fd91f340ba1b0704396ac1f0b31301dca56 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 7 Jul 2023 01:17:28 +0900 Subject: [PATCH] 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 --- internal/ui/ui_js.go | 31 +++++++++++++++++++++++++++++++ run.go | 1 + 2 files changed, 32 insertions(+) diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 3eadde5d7..8121afb76 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -80,8 +80,10 @@ type userInterfaceImpl struct { running bool cursorMode CursorMode cursorPrevMode CursorMode + captureCursorLater bool cursorShape CursorShape onceUpdateCalled bool + lastCaptureExitTime time.Time lastDeviceScaleFactor float64 @@ -189,6 +191,16 @@ func (u *userInterfaceImpl) CursorMode() 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() { return } @@ -199,6 +211,7 @@ func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) { u.cursorPrevMode = u.cursorMode if u.cursorMode == CursorModeCaptured { document.Call("exitPointerLock") + u.lastCaptureExitTime = time.Now() } u.cursorMode = mode switch mode { @@ -272,7 +285,25 @@ func (u *userInterfaceImpl) isFocused() bool { 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 { + if u.captureCursorLater && u.canCaptureCursor() { + u.setCursorMode(CursorModeCaptured) + } + if u.suspended() { return hooks.SuspendAudio() } diff --git a/run.go b/run.go index 7f42391c2..abc81050d 100644 --- a/run.go +++ b/run.go @@ -353,6 +353,7 @@ func CursorMode() CursorModeType { // CursorModeCaptured also works on browsers. // 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. +// On browsers, setting CursorModeCaptured might be delayed especially just after escaping from a capture. // // SetCursorMode does nothing on mobiles. //