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 (
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"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
2022-02-05 14:21:41 +01:00
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
2020-10-03 19:35:13 +02:00
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
2015-01-02 07:20:05 +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"
}
2019-04-07 03:42:55 +02:00
type UserInterface struct {
2020-03-20 16:34:12 +01:00
runnableOnUnfocused bool
2022-02-06 09:43:52 +01:00
fpsMode FPSMode
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
2017-01-20 18:47:31 +01:00
sizeChanged bool
2018-10-02 04:38:11 +02:00
2019-12-09 18:37:10 +01:00
lastDeviceScaleFactor float64
2019-04-06 16:03:21 +02:00
2022-02-06 09:43:52 +01:00
context Context
2019-04-09 05:03:56 +02:00
input Input
2016-03-24 17:15:47 +01:00
}
2016-03-24 16:38:30 +01:00
2019-04-07 03:42:55 +02:00
var theUI = & UserInterface {
2020-10-06 17:30:46 +02:00
runnableOnUnfocused : true ,
sizeChanged : true ,
initFocused : true ,
2016-05-18 04:56:43 +02:00
}
2015-01-26 17:10:17 +01:00
2019-04-07 12:27:30 +02:00
func init ( ) {
theUI . input . ui = theUI
}
2019-04-07 03:42:55 +02:00
func Get ( ) * UserInterface {
return theUI
}
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" )
2020-12-09 02:10:34 +01:00
go2cpp = js . Global ( ) . Get ( "go2cpp" )
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 ( ) {
if go2cpp . Truthy ( ) {
return
}
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 )
}
2019-04-07 03:42:55 +02:00
func ( u * UserInterface ) 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
}
2019-04-07 03:42:55 +02:00
func ( u * UserInterface ) SetFullscreen ( fullscreen bool ) {
2021-04-27 16:58:32 +02:00
if ! canvas . Truthy ( ) {
return
}
if ! document . Truthy ( ) {
return
}
2021-07-22 19:15:41 +02:00
if fullscreen == document . Get ( "fullscreenElement" ) . Truthy ( ) {
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
}
2019-04-07 03:42:55 +02:00
func ( u * UserInterface ) 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
}
2020-03-20 16:08:40 +01:00
func ( u * UserInterface ) IsFocused ( ) bool {
return u . isFocused ( )
2020-01-16 02:47:23 +01:00
}
2020-03-20 16:34:12 +01:00
func ( u * UserInterface ) SetRunnableOnUnfocused ( runnableOnUnfocused bool ) {
u . runnableOnUnfocused = runnableOnUnfocused
2017-08-02 16:37:50 +02:00
}
2020-03-20 16:34:12 +01:00
func ( u * UserInterface ) IsRunnableOnUnfocused ( ) bool {
return u . runnableOnUnfocused
2017-08-02 16:37:50 +02:00
}
2022-02-06 09:43:52 +01:00
func ( u * UserInterface ) SetFPSMode ( mode FPSMode ) {
2021-07-22 16:55:26 +02:00
u . fpsMode = mode
2018-07-14 13:50:09 +02:00
}
2022-02-06 09:43:52 +01:00
func ( u * UserInterface ) FPSMode ( ) FPSMode {
2021-07-22 16:55:26 +02:00
return u . fpsMode
2018-07-14 13:50:09 +02:00
}
2021-07-22 18:07:28 +02:00
func ( u * UserInterface ) ScheduleFrame ( ) {
u . renderingScheduled = true
}
2022-02-06 09:43:52 +01:00
func ( u * UserInterface ) 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-02-06 09:43:52 +01:00
func ( u * UserInterface ) 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
}
}
2021-04-15 19:26:10 +02:00
func ( u * UserInterface ) 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-02-06 09:43:52 +01:00
func ( u * UserInterface ) 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-02-06 09:43:52 +01:00
func ( u * UserInterface ) 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
}
}
2019-12-09 18:37:10 +01:00
func ( u * UserInterface ) DeviceScaleFactor ( ) float64 {
return devicescale . GetAt ( 0 , 0 )
2016-05-19 17:07:04 +02:00
}
2019-04-09 05:49:31 +02:00
func ( u * UserInterface ) updateSize ( ) {
2019-12-09 18:37:10 +01:00
a := u . DeviceScaleFactor ( )
if u . lastDeviceScaleFactor != a {
2018-10-02 04:38:11 +02:00
u . updateScreenSize ( )
}
2019-12-09 18:37:10 +01:00
u . lastDeviceScaleFactor = a
2018-10-02 04:38:11 +02:00
2018-02-01 18:08:03 +01:00
if u . sizeChanged {
u . sizeChanged = false
2020-12-09 02:10:34 +01:00
switch {
case document . Truthy ( ) :
2020-11-22 10:22:52 +01:00
body := document . Get ( "body" )
bw := body . Get ( "clientWidth" ) . Float ( )
bh := body . Get ( "clientHeight" ) . Float ( )
u . context . Layout ( bw , bh )
2020-12-09 02:10:34 +01:00
case go2cpp . Truthy ( ) :
w := go2cpp . Get ( "screenWidth" ) . Float ( )
h := go2cpp . Get ( "screenHeight" ) . Float ( )
u . context . Layout ( w , h )
default :
// Node.js
2020-11-22 10:22:52 +01:00
u . context . Layout ( 640 , 480 )
}
2018-02-01 18:08:03 +01:00
}
}
2019-04-07 03:42:55 +02:00
func ( u * UserInterface ) 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
2020-03-20 16:08:40 +01:00
func ( u * UserInterface ) isFocused ( ) bool {
2021-01-03 09:53:21 +01:00
if go2cpp . Truthy ( ) {
return true
}
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
}
2019-04-09 05:03:56 +02:00
func ( u * UserInterface ) 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
2021-01-17 17:17:55 +01:00
func ( u * UserInterface ) updateImpl ( force bool ) error {
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-02-05 14:21:41 +01:00
gamepad . Update ( )
2020-12-20 12:25:18 +01:00
u . input . updateForGo2Cpp ( )
2019-04-09 05:49:31 +02:00
u . updateSize ( )
2021-01-17 17:17:55 +01:00
if force {
2021-08-04 18:29:45 +02:00
if err := u . context . ForceUpdateFrame ( ) ; err != nil {
2021-01-17 17:17:55 +01:00
return err
}
} else {
2021-08-04 18:29:45 +02:00
if err := u . context . UpdateFrame ( ) ; 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
}
2021-07-22 18:07:28 +02:00
func ( u * UserInterface ) 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-02-06 09:43:52 +01:00
func ( u * UserInterface ) loop ( context Context ) <- chan error {
2019-10-20 17:02:35 +02:00
u . context = context
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 ( ) {
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 { } {
2021-04-28 16:28:55 +02:00
js . Global ( ) . Get ( "console" ) . Call ( "error" , "fullscreenerror event is fired. 'sandbox=\"fullscreen\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change." )
return nil
} ) )
document . Call ( "addEventListener" , "webkitfullscreenerror" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
js . Global ( ) . Get ( "console" ) . Call ( "error" , "webkitfullscreenerror event is fired. 'sandbox=\"fullscreen\"' 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 ( )
2021-01-17 17:17:55 +01:00
if err := theUI . updateImpl ( true ) ; err != nil {
panic ( err )
}
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 ]
// Don't 'preventDefault' on keydown events or keypress events wouldn't work (#715).
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" , "keypress" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) interface { } {
2019-04-30 19:15:28 +02:00
e := args [ 0 ]
e . Call ( "preventDefault" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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" )
2020-12-20 10:17:08 +01:00
theUI . input . updateFromEvent ( e )
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
}
2021-07-22 18:07:28 +02:00
func ( u * UserInterface ) forceUpdateOnMinimumFPSMode ( ) {
2022-02-06 09:43:52 +01:00
if u . fpsMode != FPSModeVsyncOffMinimum {
2021-07-22 18:07:28 +02:00
return
}
u . updateImpl ( true )
}
2022-02-06 09:43:52 +01:00
func ( u * UserInterface ) Run ( context Context ) 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
2020-10-06 18:03:19 +02:00
return <- u . loop ( context )
2015-02-09 02:25:00 +01:00
}
2022-02-06 09:43:52 +01:00
func ( u * UserInterface ) RunWithoutMainLoop ( context Context ) {
2022-02-06 08:08:22 +01:00
panic ( "ui: RunWithoutMainLoop is not implemented" )
2019-04-10 03:44:02 +02:00
}
2019-04-07 03:42:55 +02:00
func ( u * UserInterface ) updateScreenSize ( ) {
2020-12-09 02:10:34 +01:00
switch {
case 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 )
2020-12-09 02:10:34 +01:00
case go2cpp . Truthy ( ) :
// TODO: Implement this
2020-11-22 10:22:52 +01:00
}
2016-05-18 04:56:43 +02:00
u . sizeChanged = true
2015-01-02 07:20:05 +01:00
}
2019-04-07 11:28:50 +02:00
2019-11-30 16:07:41 +01:00
func ( u * UserInterface ) SetScreenTransparent ( transparent bool ) {
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" )
}
}
func ( u * UserInterface ) IsScreenTransparent ( ) bool {
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
}
2020-04-02 17:06:42 +02:00
func ( u * UserInterface ) ResetForFrame ( ) {
u . updateSize ( )
u . input . resetForFrame ( )
}
2020-02-09 15:03:03 +01:00
func ( u * UserInterface ) SetInitFocused ( focused bool ) {
if u . running {
panic ( "ui: SetInitFocused must be called before the main loop" )
}
u . initFocused = focused
}
2022-01-10 08:01:57 +01:00
func ( u * UserInterface ) Vibrate ( duration time . Duration , magnitude float64 ) {
// magnitude is ignored.
2021-12-04 14:14:02 +01:00
2021-10-23 11:27:27 +02:00
if js . Global ( ) . Get ( "navigator" ) . Get ( "vibrate" ) . Truthy ( ) {
js . Global ( ) . Get ( "navigator" ) . Call ( "vibrate" , float64 ( duration / time . Millisecond ) )
}
}
2022-02-06 10:30:31 +01:00
func ( u * UserInterface ) 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
func ( u * UserInterface ) Window ( ) driver . Window {
return nil
}