2015-01-02 07:20:05 +01:00
// Copyright 2015 Hajime Hoshi
//
// 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.
2022-02-06 08:08:22 +01:00
package ui
2015-01-02 07:20:05 +01:00
import (
2022-09-20 06:19:40 +02:00
"sync"
2019-04-30 19:15:28 +02:00
"syscall/js"
2019-10-20 17:02:35 +02:00
"time"
2018-01-02 21:22:56 +01:00
2020-10-03 19:35:13 +02:00
"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
2022-02-05 14:21:41 +01:00
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
2022-03-21 16:02:37 +01:00
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
2022-03-22 15:33:21 +01:00
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
2020-10-03 19:35:13 +02:00
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
2015-01-02 07:20:05 +01:00
)
2022-06-16 19:10:29 +02:00
type graphicsDriverCreatorImpl struct { }
2022-03-22 15:33:21 +01:00
2022-07-30 19:56:16 +02:00
func ( g * graphicsDriverCreatorImpl ) newAuto ( ) ( graphicsdriver . Graphics , GraphicsLibrary , error ) {
graphics , err := g . newOpenGL ( )
return graphics , GraphicsLibraryOpenGL , err
2022-03-22 15:33:21 +01:00
}
2022-06-16 19:10:29 +02:00
func ( * graphicsDriverCreatorImpl ) newOpenGL ( ) ( graphicsdriver . Graphics , error ) {
2022-06-16 19:02:29 +02:00
return opengl . NewGraphics ( )
2022-03-22 15:33:21 +01:00
}
2022-06-17 04:39:43 +02:00
func ( * graphicsDriverCreatorImpl ) newDirectX ( ) ( graphicsdriver . Graphics , error ) {
return nil , nil
2022-03-25 11:43:32 +01:00
}
2022-06-16 19:10:29 +02:00
func ( * graphicsDriverCreatorImpl ) newMetal ( ) ( graphicsdriver . Graphics , error ) {
return nil , nil
2022-03-22 15:33:21 +01:00
}
2020-12-16 04:41:49 +01:00
var (
stringNone = js . ValueOf ( "none" )
stringTransparent = js . ValueOf ( "transparent" )
)
2022-02-06 09:43:52 +01:00
func driverCursorShapeToCSSCursor ( cursor CursorShape ) string {
2021-04-11 07:21:38 +02:00
switch cursor {
2022-02-06 09:43:52 +01:00
case CursorShapeDefault :
2021-04-11 07:21:38 +02:00
return "default"
2022-02-06 09:43:52 +01:00
case CursorShapeText :
2021-04-11 07:21:38 +02:00
return "text"
2022-02-06 09:43:52 +01:00
case CursorShapeCrosshair :
2021-04-11 07:21:38 +02:00
return "crosshair"
2022-02-06 09:43:52 +01:00
case CursorShapePointer :
2021-04-11 07:21:38 +02:00
return "pointer"
2022-02-06 09:43:52 +01:00
case CursorShapeEWResize :
2021-05-02 07:50:50 +02:00
return "ew-resize"
2022-02-06 09:43:52 +01:00
case CursorShapeNSResize :
2021-05-02 07:50:50 +02:00
return "ns-resize"
2021-04-11 07:21:38 +02:00
}
return "auto"
}
2022-03-21 15:00:50 +01:00
type userInterfaceImpl struct {
2022-03-21 16:02:37 +01:00
graphicsDriver graphicsdriver . Graphics
2020-03-20 16:34:12 +01:00
runnableOnUnfocused bool
2022-02-13 09:23:52 +01:00
fpsMode FPSModeType
2021-07-22 18:07:28 +02:00
renderingScheduled bool
2020-03-20 16:34:12 +01:00
running bool
2020-02-09 15:03:03 +01:00
initFocused bool
2022-02-06 09:43:52 +01:00
cursorMode CursorMode
cursorPrevMode CursorMode
cursorShape CursorShape
2021-07-22 18:07:28 +02:00
onceUpdateCalled bool
2017-07-21 18:52:08 +02:00
2019-12-09 18:37:10 +01:00
lastDeviceScaleFactor float64
2019-04-06 16:03:21 +02:00
2022-09-09 15:47:54 +02:00
err error
2022-04-01 10:59:44 +02:00
context * context
2019-04-09 05:03:56 +02:00
input Input
2022-09-20 06:19:40 +02:00
m sync . Mutex
2016-03-24 17:15:47 +01:00
}
2016-03-24 16:38:30 +01:00
2019-04-07 12:27:30 +02:00
func init ( ) {
2022-03-21 15:00:50 +01:00
theUI . userInterfaceImpl = userInterfaceImpl {
runnableOnUnfocused : true ,
initFocused : true ,
}
theUI . input . ui = & theUI . userInterfaceImpl
2019-04-07 03:42:55 +02:00
}
2018-06-18 20:40:37 +02:00
var (
2018-06-29 17:02:15 +02:00
window = js . Global ( ) . Get ( "window" )
document = js . Global ( ) . Get ( "document" )
2019-10-20 17:02:35 +02:00
canvas js . Value
2020-11-22 10:22:52 +01:00
requestAnimationFrame = js . Global ( ) . Get ( "requestAnimationFrame" )
setTimeout = js . Global ( ) . Get ( "setTimeout" )
2018-06-18 20:40:37 +02:00
)
2021-09-04 12:20:24 +02:00
var (
documentHasFocus js . Value
documentHidden js . Value
)
func init ( ) {
documentHasFocus = document . Get ( "hasFocus" ) . Call ( "bind" , document )
documentHidden = js . Global ( ) . Get ( "Object" ) . Call ( "getOwnPropertyDescriptor" , js . Global ( ) . Get ( "Document" ) . Get ( "prototype" ) , "hidden" ) . Get ( "get" ) . Call ( "bind" , document )
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) ScreenSizeInFullscreen ( ) ( int , int ) {
2018-06-18 20:40:37 +02:00
return window . Get ( "innerWidth" ) . Int ( ) , window . Get ( "innerHeight" ) . Int ( )
2018-05-04 09:09:55 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetFullscreen ( fullscreen bool ) {
2021-04-27 16:58:32 +02:00
if ! canvas . Truthy ( ) {
return
}
if ! document . Truthy ( ) {
return
}
2022-04-09 11:23:56 +02:00
if fullscreen == u . IsFullscreen ( ) {
2021-07-22 19:15:41 +02:00
return
}
2021-04-27 16:58:32 +02:00
if fullscreen {
f := canvas . Get ( "requestFullscreen" )
if ! f . Truthy ( ) {
f = canvas . Get ( "webkitRequestFullscreen" )
}
f . Call ( "bind" , canvas ) . Invoke ( )
return
}
f := document . Get ( "exitFullscreen" )
if ! f . Truthy ( ) {
f = document . Get ( "webkitExitFullscreen" )
}
f . Call ( "bind" , document ) . Invoke ( )
2017-06-29 17:35:34 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) IsFullscreen ( ) bool {
2021-04-27 16:58:32 +02:00
if ! document . Truthy ( ) {
return false
}
if ! document . Get ( "fullscreenElement" ) . Truthy ( ) && ! document . Get ( "webkitFullscreenElement" ) . Truthy ( ) {
return false
}
return true
2017-06-29 17:35:34 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) IsFocused ( ) bool {
2020-03-20 16:08:40 +01:00
return u . isFocused ( )
2020-01-16 02:47:23 +01:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetRunnableOnUnfocused ( runnableOnUnfocused bool ) {
2020-03-20 16:34:12 +01:00
u . runnableOnUnfocused = runnableOnUnfocused
2017-08-02 16:37:50 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) IsRunnableOnUnfocused ( ) bool {
2020-03-20 16:34:12 +01:00
return u . runnableOnUnfocused
2017-08-02 16:37:50 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetFPSMode ( mode FPSModeType ) {
2021-07-22 16:55:26 +02:00
u . fpsMode = mode
2018-07-14 13:50:09 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) ScheduleFrame ( ) {
2021-07-22 18:07:28 +02:00
u . renderingScheduled = true
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) CursorMode ( ) CursorMode {
2021-03-21 08:40:35 +01:00
if ! canvas . Truthy ( ) {
2022-02-06 09:43:52 +01:00
return CursorModeHidden
2021-03-21 08:40:35 +01:00
}
2021-04-15 19:26:10 +02:00
return u . cursorMode
2017-08-12 08:39:41 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetCursorMode ( mode CursorMode ) {
2021-03-21 08:40:35 +01:00
if ! canvas . Truthy ( ) {
return
}
2021-04-15 19:26:10 +02:00
if u . cursorMode == mode {
return
}
2021-04-15 20:14:46 +02:00
// Remember the previous cursor mode in the case when the pointer lock exits by pressing ESC.
2021-04-15 19:26:10 +02:00
u . cursorPrevMode = u . cursorMode
2022-02-06 09:43:52 +01:00
if u . cursorMode == CursorModeCaptured {
2021-04-15 19:26:10 +02:00
document . Call ( "exitPointerLock" )
}
u . cursorMode = mode
2019-12-14 04:30:03 +01:00
switch mode {
2022-02-06 09:43:52 +01:00
case CursorModeVisible :
2021-04-15 19:26:10 +02:00
canvas . Get ( "style" ) . Set ( "cursor" , driverCursorShapeToCSSCursor ( u . cursorShape ) )
2022-02-06 09:43:52 +01:00
case CursorModeHidden :
2021-04-11 07:21:38 +02:00
canvas . Get ( "style" ) . Set ( "cursor" , stringNone )
2022-02-06 09:43:52 +01:00
case CursorModeCaptured :
2021-04-15 19:26:10 +02:00
canvas . Call ( "requestPointerLock" )
2021-04-11 07:21:38 +02:00
}
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) recoverCursorMode ( ) {
2022-02-06 09:43:52 +01:00
if theUI . cursorPrevMode == CursorModeCaptured {
panic ( "ui: cursorPrevMode must not be CursorModeCaptured at recoverCursorMode" )
2021-04-15 20:13:12 +02:00
}
2021-04-15 19:26:10 +02:00
u . SetCursorMode ( u . cursorPrevMode )
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) CursorShape ( ) CursorShape {
2021-04-11 07:21:38 +02:00
if ! canvas . Truthy ( ) {
2022-02-06 09:43:52 +01:00
return CursorShapeDefault
2021-04-11 07:21:38 +02:00
}
return u . cursorShape
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetCursorShape ( shape CursorShape ) {
2021-04-11 07:21:38 +02:00
if ! canvas . Truthy ( ) {
return
}
if u . cursorShape == shape {
return
}
2021-04-15 19:26:10 +02:00
2021-04-11 07:21:38 +02:00
u . cursorShape = shape
2022-02-06 09:43:52 +01:00
if u . cursorMode == CursorModeVisible {
2021-04-11 07:21:38 +02:00
canvas . Get ( "style" ) . Set ( "cursor" , driverCursorShapeToCSSCursor ( u . cursorShape ) )
2016-09-03 10:17:54 +02:00
}
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) DeviceScaleFactor ( ) float64 {
2019-12-09 18:37:10 +01:00
return devicescale . GetAt ( 0 , 0 )
2016-05-19 17:07:04 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) outsideSize ( ) ( float64 , float64 ) {
2022-08-07 15:17:47 +02:00
if document . Truthy ( ) {
2022-02-13 18:50:32 +01:00
body := document . Get ( "body" )
bw := body . Get ( "clientWidth" ) . Float ( )
bh := body . Get ( "clientHeight" ) . Float ( )
2022-02-13 18:48:28 +01:00
return bw , bh
2018-02-01 18:08:03 +01:00
}
2022-08-07 15:17:47 +02:00
// Node.js
return 640 , 480
2018-02-01 18:08:03 +01:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) suspended ( ) bool {
2020-03-20 16:34:12 +01:00
if u . runnableOnUnfocused {
2019-10-20 17:02:35 +02:00
return false
}
2020-03-20 16:08:40 +01:00
return ! u . isFocused ( )
2020-01-21 15:56:47 +01:00
}
2019-10-20 17:02:35 +02:00
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) isFocused ( ) bool {
2021-09-04 12:20:24 +02:00
if ! documentHasFocus . Invoke ( ) . Bool ( ) {
2020-01-21 15:56:47 +01:00
return false
2019-10-20 17:02:35 +02:00
}
2021-09-04 12:20:24 +02:00
if documentHidden . Invoke ( ) . Bool ( ) {
2020-01-21 15:56:47 +01:00
return false
2019-10-20 17:02:35 +02:00
}
2020-01-21 15:56:47 +01:00
return true
2018-05-26 11:04:20 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) update ( ) error {
2018-05-26 11:04:20 +02:00
if u . suspended ( ) {
2021-05-04 15:24:31 +02:00
return hooks . SuspendAudio ( )
}
if err := hooks . ResumeAudio ( ) ; err != nil {
return err
2016-06-29 18:07:54 +02:00
}
2021-01-17 17:17:55 +01:00
return u . updateImpl ( false )
}
2018-05-26 11:04:20 +02:00
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) updateImpl ( force bool ) error {
2022-09-20 06:19:40 +02:00
// Guard updateImpl as this function cannot be invoked until this finishes (#2339).
u . m . Lock ( )
defer u . m . Unlock ( )
2022-01-03 07:17:26 +01:00
// context can be nil when an event is fired but the loop doesn't start yet (#1928).
if u . context == nil {
return nil
}
2022-06-24 13:20:49 +02:00
if err := gamepad . Update ( ) ; err != nil {
return err
}
2022-02-13 18:48:28 +01:00
a := u . DeviceScaleFactor ( )
if u . lastDeviceScaleFactor != a {
u . updateScreenSize ( )
}
u . lastDeviceScaleFactor = a
w , h := u . outsideSize ( )
2021-01-17 17:17:55 +01:00
if force {
2022-03-21 16:02:37 +01:00
if err := u . context . forceUpdateFrame ( u . graphicsDriver , w , h , u . DeviceScaleFactor ( ) ) ; err != nil {
2021-01-17 17:17:55 +01:00
return err
}
} else {
2022-03-21 16:02:37 +01:00
if err := u . context . updateFrame ( u . graphicsDriver , w , h , u . DeviceScaleFactor ( ) ) ; err != nil {
2021-01-17 17:17:55 +01:00
return err
}
2020-03-31 19:55:48 +02:00
}
2016-09-01 17:53:05 +02:00
return nil
2015-01-07 17:32:37 +01:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) needsUpdate ( ) bool {
2022-02-06 09:43:52 +01:00
if u . fpsMode != FPSModeVsyncOffMinimum {
2021-07-22 18:07:28 +02:00
return true
}
if ! u . onceUpdateCalled {
return true
}
if u . renderingScheduled {
return true
}
// TODO: Watch the gamepad state?
return false
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) loop ( game Game ) <- chan error {
2022-04-01 10:59:44 +02:00
u . context = newContext ( game )
2019-04-09 05:03:56 +02:00
2021-05-04 15:24:31 +02:00
errCh := make ( chan error , 1 )
2020-02-08 19:39:04 +01:00
reqStopAudioCh := make ( chan struct { } )
resStopAudioCh := make ( chan struct { } )
2019-04-30 19:15:28 +02:00
var cf js . Func
2020-05-18 20:28:14 +02:00
f := func ( ) {
2022-09-09 15:47:54 +02:00
if u . err != nil {
errCh <- u . err
return
}
2021-07-22 18:07:28 +02:00
if u . needsUpdate ( ) {
u . onceUpdateCalled = true
u . renderingScheduled = false
if err := u . update ( ) ; err != nil {
close ( reqStopAudioCh )
<- resStopAudioCh
errCh <- err
return
}
2018-05-26 18:59:35 +02:00
}
2021-07-22 18:07:28 +02:00
switch u . fpsMode {
2022-02-06 09:43:52 +01:00
case FPSModeVsyncOn :
2018-07-14 13:50:09 +02:00
requestAnimationFrame . Invoke ( cf )
2022-02-06 09:43:52 +01:00
case FPSModeVsyncOffMaximum :
2018-07-14 16:47:31 +02:00
setTimeout . Invoke ( cf , 0 )
2022-02-06 09:43:52 +01:00
case FPSModeVsyncOffMinimum :
2021-07-22 18:07:28 +02:00
requestAnimationFrame . Invoke ( cf )
2018-07-14 13:50:09 +02:00
}
2016-02-27 19:49:57 +01:00
}
2020-05-18 21:08:30 +02:00
2019-04-30 19:15:28 +02:00
// TODO: Should cf be released after the game ends?
2020-05-18 20:28:14 +02:00
cf = js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2020-05-18 21:08:30 +02:00
// f can be blocked but callbacks must not be blocked. Create a goroutine (#1161).
2020-05-18 20:28:14 +02:00
go f ( )
return nil
} )
2021-09-13 14:53:02 +02:00
// Call f asyncly since ch is used in f.
2020-05-18 20:28:14 +02:00
go f ( )
2015-01-02 07:20:05 +01:00
2019-10-20 17:02:35 +02:00
// Run another loop to watch suspended() as the above update function is never called when the tab is hidden.
// To check the document's visiblity, visibilitychange event should usually be used. However, this event is
// not reliable and sometimes it is not fired (#961). Then, watch the state regularly instead.
go func ( ) {
2020-02-08 19:39:04 +01:00
defer close ( resStopAudioCh )
const interval = 100 * time . Millisecond
t := time . NewTicker ( interval )
defer func ( ) {
t . Stop ( )
// This is a dirty hack. (*time.Ticker).Stop() just marks the timer 'deleted' [1] and
// something might run even after Stop. On Wasm, this causes an issue to execute Go program
// even after finishing (#1027). Sleep for the interval time duration to ensure that
// everything related to the timer is finished.
//
// [1] runtime.deltimer
time . Sleep ( interval )
} ( )
for {
select {
case <- t . C :
if u . suspended ( ) {
2021-05-04 15:24:31 +02:00
if err := hooks . SuspendAudio ( ) ; err != nil {
errCh <- err
return
}
2020-02-08 19:39:04 +01:00
} else {
2021-05-04 15:24:31 +02:00
if err := hooks . ResumeAudio ( ) ; err != nil {
errCh <- err
return
}
2020-02-08 19:39:04 +01:00
}
case <- reqStopAudioCh :
return
2019-10-20 17:02:35 +02:00
}
2018-05-26 11:04:20 +02:00
}
2019-10-20 17:02:35 +02:00
} ( )
2020-02-08 19:39:04 +01:00
return errCh
2019-10-13 19:41:04 +02:00
}
func init ( ) {
2020-11-22 10:22:52 +01:00
// docuemnt is undefined on node.js
if ! document . Truthy ( ) {
return
}
if ! document . Get ( "body" ) . Truthy ( ) {
2019-10-13 19:41:04 +02:00
ch := make ( chan struct { } )
window . Call ( "addEventListener" , "load" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
close ( ch )
return nil
} ) )
<- ch
}
2020-12-06 14:56:09 +01:00
setWindowEventHandlers ( window )
2015-01-07 03:22:48 +01:00
2017-07-01 21:08:25 +02:00
// Adjust the initial scale to 1.
// https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
2018-06-18 20:40:37 +02:00
meta := document . Call ( "createElement" , "meta" )
2017-07-01 21:08:25 +02:00
meta . Set ( "name" , "viewport" )
meta . Set ( "content" , "width=device-width, initial-scale=1" )
2018-06-18 20:40:37 +02:00
document . Get ( "head" ) . Call ( "appendChild" , meta )
2017-07-01 21:08:25 +02:00
2018-06-18 20:40:37 +02:00
canvas = document . Call ( "createElement" , "canvas" )
2015-01-02 08:48:07 +01:00
canvas . Set ( "width" , 16 )
canvas . Set ( "height" , 16 )
2019-12-09 18:37:10 +01:00
2018-06-18 20:40:37 +02:00
document . Get ( "body" ) . Call ( "appendChild" , canvas )
2015-01-05 14:22:47 +01:00
2018-06-18 20:40:37 +02:00
htmlStyle := document . Get ( "documentElement" ) . Get ( "style" )
2015-01-05 14:22:47 +01:00
htmlStyle . Set ( "height" , "100%" )
2015-01-05 16:44:39 +01:00
htmlStyle . Set ( "margin" , "0" )
htmlStyle . Set ( "padding" , "0" )
2015-01-05 14:22:47 +01:00
2018-06-18 20:40:37 +02:00
bodyStyle := document . Get ( "body" ) . Get ( "style" )
2015-01-05 14:22:47 +01:00
bodyStyle . Set ( "backgroundColor" , "#000" )
bodyStyle . Set ( "height" , "100%" )
2015-01-05 16:44:39 +01:00
bodyStyle . Set ( "margin" , "0" )
bodyStyle . Set ( "padding" , "0" )
2019-03-19 17:36:52 +01:00
2015-01-05 14:22:47 +01:00
canvasStyle := canvas . Get ( "style" )
2019-12-09 18:37:10 +01:00
canvasStyle . Set ( "width" , "100%" )
canvasStyle . Set ( "height" , "100%" )
canvasStyle . Set ( "margin" , "0" )
canvasStyle . Set ( "padding" , "0" )
2015-01-05 14:22:47 +01:00
2015-01-02 16:52:49 +01:00
// Make the canvas focusable.
canvas . Call ( "setAttribute" , "tabindex" , 1 )
canvas . Get ( "style" ) . Set ( "outline" , "none" )
2020-12-06 14:56:09 +01:00
setCanvasEventHandlers ( canvas )
2021-04-15 19:26:10 +02:00
// Pointer Lock
document . Call ( "addEventListener" , "pointerlockchange" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
if document . Get ( "pointerLockElement" ) . Truthy ( ) {
return nil
}
2021-04-15 20:14:46 +02:00
// Recover the state correctly when the pointer lock exits.
2021-04-15 19:26:10 +02:00
// A user can exit the pointer lock by pressing ESC. In this case, sync the cursor mode state.
2022-02-06 09:43:52 +01:00
if theUI . cursorMode == CursorModeCaptured {
2021-04-15 19:26:10 +02:00
theUI . recoverCursorMode ( )
}
theUI . input . recoverCursorPosition ( )
return nil
} ) )
2021-04-20 05:22:31 +02:00
document . Call ( "addEventListener" , "pointerlockerror" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2021-04-28 16:28:55 +02:00
js . Global ( ) . Get ( "console" ) . Call ( "error" , "pointerlockerror event is fired. 'sandbox=\"allow-pointer-lock\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change." )
2021-04-20 05:22:31 +02:00
return nil
} ) )
2021-04-27 16:58:32 +02:00
document . Call ( "addEventListener" , "fullscreenerror" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2022-04-09 09:55:09 +02:00
js . Global ( ) . Get ( "console" ) . Call ( "error" , "fullscreenerror event is fired. 'allow=\"fullscreen\"' or 'allowfullscreen' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change." )
2021-04-28 16:28:55 +02:00
return nil
} ) )
document . Call ( "addEventListener" , "webkitfullscreenerror" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2022-04-09 09:55:09 +02:00
js . Global ( ) . Get ( "console" ) . Call ( "error" , "webkitfullscreenerror event is fired. 'allow=\"fullscreen\"' or 'allowfullscreen' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change." )
2021-04-27 16:58:32 +02:00
return nil
} ) )
2020-12-06 14:56:09 +01:00
}
func setWindowEventHandlers ( v js . Value ) {
v . Call ( "addEventListener" , "resize" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
theUI . updateScreenSize ( )
2022-09-20 06:19:40 +02:00
// updateImpl can block. Use goroutine.
// See https://pkg.go.dev/syscall/js#FuncOf.
go func ( ) {
if err := theUI . updateImpl ( true ) ; err != nil && theUI . err != nil {
theUI . err = err
return
}
} ( )
2020-12-06 14:56:09 +01:00
return nil
} ) )
}
func setCanvasEventHandlers ( v js . Value ) {
2015-01-12 06:36:13 +01:00
// Keyboard
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "keydown" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-10-15 16:57:14 +02:00
// Focus the canvas explicitly to activate tha game (#961).
2020-12-06 14:56:09 +01:00
v . Call ( "focus" )
2019-10-15 16:57:14 +02:00
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "keyup" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2015-01-12 06:36:13 +01:00
// Mouse
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "mousedown" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-10-15 16:57:14 +02:00
// Focus the canvas explicitly to activate tha game (#961).
2020-12-06 14:56:09 +01:00
v . Call ( "focus" )
2019-10-15 16:57:14 +02:00
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "mouseup" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "mousemove" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "wheel" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2015-01-12 06:36:13 +01:00
2016-05-26 18:31:30 +02:00
// Touch
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "touchstart" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-10-15 16:57:14 +02:00
// Focus the canvas explicitly to activate tha game (#961).
2020-12-06 14:56:09 +01:00
v . Call ( "focus" )
2019-10-15 16:57:14 +02:00
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "touchend" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "touchmove" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2022-09-09 15:47:54 +02:00
if err := theUI . input . updateFromEvent ( e ) ; err != nil && theUI . err != nil {
theUI . err = err
return nil
}
2019-04-30 19:15:28 +02:00
return nil
2019-04-06 16:32:19 +02:00
} ) )
2015-01-20 18:58:29 +01:00
2021-04-15 19:26:10 +02:00
// Context menu
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "contextmenu" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
return nil
2018-05-26 18:59:35 +02:00
} ) )
2017-01-20 18:47:31 +01:00
2019-01-29 17:44:57 +01:00
// Context
2020-12-06 14:56:09 +01:00
v . Call ( "addEventListener" , "webglcontextlost" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2021-06-26 09:50:12 +02:00
window . Get ( "location" ) . Call ( "reload" )
2019-04-30 19:15:28 +02:00
return nil
2018-05-26 18:59:35 +02:00
} ) )
2015-01-02 07:20:05 +01:00
}
2022-09-20 06:19:40 +02:00
func ( u * userInterfaceImpl ) forceUpdateOnMinimumFPSMode ( ) {
2022-02-06 09:43:52 +01:00
if u . fpsMode != FPSModeVsyncOffMinimum {
2022-09-20 06:19:40 +02:00
return
2021-07-22 18:07:28 +02:00
}
2022-09-20 06:19:40 +02:00
// updateImpl can block. Use goroutine.
// See https://pkg.go.dev/syscall/js#FuncOf.
go func ( ) {
if err := u . updateImpl ( true ) ; err != nil && u . err != nil {
u . err = err
}
} ( )
2021-07-22 18:07:28 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) Run ( game Game ) error {
2020-11-22 10:22:52 +01:00
if u . initFocused && window . Truthy ( ) {
2020-10-10 08:52:29 +02:00
// Do not focus the canvas when the current document is in an iframe.
// Otherwise, the parent page tries to focus the iframe on every loading, which is annoying (#1373).
2021-06-07 14:43:25 +02:00
isInIframe := ! window . Get ( "location" ) . Equal ( window . Get ( "parent" ) . Get ( "location" ) )
2020-10-10 08:52:29 +02:00
if ! isInIframe {
canvas . Call ( "focus" )
}
2020-02-09 15:03:03 +01:00
}
2019-11-30 16:07:41 +01:00
u . running = true
2022-06-16 19:32:03 +02:00
g , err := newGraphicsDriver ( & graphicsDriverCreatorImpl { } )
2022-03-22 15:33:21 +01:00
if err != nil {
return err
}
u . graphicsDriver = g
2022-06-11 08:34:17 +02:00
if g , ok := u . graphicsDriver . ( interface { SetCanvas ( js . Value ) } ) ; ok {
g . SetCanvas ( canvas )
2022-06-11 08:02:10 +02:00
}
2022-02-13 12:18:41 +01:00
return <- u . loop ( game )
2015-02-09 02:25:00 +01:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) updateScreenSize ( ) {
2022-08-07 15:17:47 +02:00
if document . Truthy ( ) {
2020-11-22 10:22:52 +01:00
body := document . Get ( "body" )
bw := int ( body . Get ( "clientWidth" ) . Float ( ) * u . DeviceScaleFactor ( ) )
bh := int ( body . Get ( "clientHeight" ) . Float ( ) * u . DeviceScaleFactor ( ) )
canvas . Set ( "width" , bw )
canvas . Set ( "height" , bh )
}
2015-01-02 07:20:05 +01:00
}
2019-04-07 11:28:50 +02:00
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetScreenTransparent ( transparent bool ) {
2019-11-30 16:07:41 +01:00
if u . running {
2022-02-06 08:08:22 +01:00
panic ( "ui: SetScreenTransparent can't be called after the main loop starts" )
2019-11-30 16:07:41 +01:00
}
bodyStyle := document . Get ( "body" ) . Get ( "style" )
if transparent {
bodyStyle . Set ( "backgroundColor" , "transparent" )
} else {
bodyStyle . Set ( "backgroundColor" , "#000" )
}
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) IsScreenTransparent ( ) bool {
2019-11-30 16:07:41 +01:00
bodyStyle := document . Get ( "body" ) . Get ( "style" )
2021-06-07 14:43:25 +02:00
return bodyStyle . Get ( "backgroundColor" ) . Equal ( stringTransparent )
2019-11-30 16:07:41 +01:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) resetForTick ( ) {
2022-02-13 11:54:38 +01:00
u . input . resetForTick ( )
2020-04-02 17:06:42 +02:00
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) SetInitFocused ( focused bool ) {
2020-02-09 15:03:03 +01:00
if u . running {
panic ( "ui: SetInitFocused must be called before the main loop" )
}
u . initFocused = focused
}
2022-03-21 15:00:50 +01:00
func ( u * userInterfaceImpl ) Input ( ) * Input {
2019-04-07 11:55:56 +02:00
return & u . input
2019-04-07 11:28:50 +02:00
}
2019-12-24 16:05:56 +01:00
2022-05-31 18:26:28 +02:00
func ( u * userInterfaceImpl ) Window ( ) Window {
return & nullWindow { }
2019-12-24 16:05:56 +01:00
}