twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

ui_js.go (12109B)


      1 // Copyright 2015 Hajime Hoshi
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package js
     16 
     17 import (
     18 	"syscall/js"
     19 	"time"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
     25 	"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
     26 	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
     27 )
     28 
     29 type UserInterface struct {
     30 	runnableOnUnfocused bool
     31 	vsync               bool
     32 	running             bool
     33 	initFocused         bool
     34 
     35 	sizeChanged bool
     36 	contextLost bool
     37 
     38 	lastDeviceScaleFactor float64
     39 
     40 	context driver.UIContext
     41 	input   Input
     42 }
     43 
     44 var theUI = &UserInterface{
     45 	runnableOnUnfocused: true,
     46 	sizeChanged:         true,
     47 	vsync:               true,
     48 	initFocused:         true,
     49 }
     50 
     51 func init() {
     52 	theUI.input.ui = theUI
     53 }
     54 
     55 func Get() *UserInterface {
     56 	return theUI
     57 }
     58 
     59 var (
     60 	window                = js.Global().Get("window")
     61 	document              = js.Global().Get("document")
     62 	canvas                js.Value
     63 	requestAnimationFrame = window.Get("requestAnimationFrame")
     64 	setTimeout            = window.Get("setTimeout")
     65 )
     66 
     67 func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
     68 	return window.Get("innerWidth").Int(), window.Get("innerHeight").Int()
     69 }
     70 
     71 func (u *UserInterface) SetFullscreen(fullscreen bool) {
     72 	// Do nothing
     73 }
     74 
     75 func (u *UserInterface) IsFullscreen() bool {
     76 	return false
     77 }
     78 
     79 func (u *UserInterface) IsFocused() bool {
     80 	return u.isFocused()
     81 }
     82 
     83 func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
     84 	u.runnableOnUnfocused = runnableOnUnfocused
     85 }
     86 
     87 func (u *UserInterface) IsRunnableOnUnfocused() bool {
     88 	return u.runnableOnUnfocused
     89 }
     90 
     91 func (u *UserInterface) SetVsyncEnabled(enabled bool) {
     92 	u.vsync = enabled
     93 }
     94 
     95 func (u *UserInterface) IsVsyncEnabled() bool {
     96 	return u.vsync
     97 }
     98 
     99 func (u *UserInterface) CursorMode() driver.CursorMode {
    100 	if canvas.Get("style").Get("cursor").String() != "none" {
    101 		return driver.CursorModeVisible
    102 	}
    103 	return driver.CursorModeHidden
    104 }
    105 
    106 func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
    107 	var visible bool
    108 	switch mode {
    109 	case driver.CursorModeVisible:
    110 		visible = true
    111 	case driver.CursorModeHidden:
    112 		visible = false
    113 	default:
    114 		return
    115 	}
    116 
    117 	if visible {
    118 		canvas.Get("style").Set("cursor", "auto")
    119 	} else {
    120 		canvas.Get("style").Set("cursor", "none")
    121 	}
    122 }
    123 
    124 func (u *UserInterface) DeviceScaleFactor() float64 {
    125 	return devicescale.GetAt(0, 0)
    126 }
    127 
    128 func (u *UserInterface) updateSize() {
    129 	a := u.DeviceScaleFactor()
    130 	if u.lastDeviceScaleFactor != a {
    131 		u.updateScreenSize()
    132 	}
    133 	u.lastDeviceScaleFactor = a
    134 
    135 	if u.sizeChanged {
    136 		u.sizeChanged = false
    137 		body := document.Get("body")
    138 		bw := body.Get("clientWidth").Float()
    139 		bh := body.Get("clientHeight").Float()
    140 		u.context.Layout(bw, bh)
    141 	}
    142 }
    143 
    144 func (u *UserInterface) suspended() bool {
    145 	if u.runnableOnUnfocused {
    146 		return false
    147 	}
    148 	return !u.isFocused()
    149 }
    150 
    151 func (u *UserInterface) isFocused() bool {
    152 	if !document.Call("hasFocus").Bool() {
    153 		return false
    154 	}
    155 	if document.Get("hidden").Bool() {
    156 		return false
    157 	}
    158 	return true
    159 }
    160 
    161 func (u *UserInterface) update() error {
    162 	if u.suspended() {
    163 		hooks.SuspendAudio()
    164 		return nil
    165 	}
    166 	hooks.ResumeAudio()
    167 
    168 	u.input.UpdateGamepads()
    169 	u.updateSize()
    170 	if err := u.context.Update(); err != nil {
    171 		return err
    172 	}
    173 	if err := u.context.Draw(); err != nil {
    174 		return err
    175 	}
    176 	return nil
    177 }
    178 
    179 func (u *UserInterface) loop(context driver.UIContext) <-chan error {
    180 	u.context = context
    181 
    182 	errCh := make(chan error)
    183 	reqStopAudioCh := make(chan struct{})
    184 	resStopAudioCh := make(chan struct{})
    185 
    186 	var cf js.Func
    187 	f := func() {
    188 		if u.contextLost {
    189 			requestAnimationFrame.Invoke(cf)
    190 			return
    191 		}
    192 
    193 		if err := u.update(); err != nil {
    194 			close(reqStopAudioCh)
    195 			<-resStopAudioCh
    196 
    197 			errCh <- err
    198 			close(errCh)
    199 			return
    200 		}
    201 		if u.vsync {
    202 			requestAnimationFrame.Invoke(cf)
    203 		} else {
    204 			setTimeout.Invoke(cf, 0)
    205 		}
    206 	}
    207 
    208 	// TODO: Should cf be released after the game ends?
    209 	cf = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    210 		// f can be blocked but callbacks must not be blocked. Create a goroutine (#1161).
    211 		go f()
    212 		return nil
    213 	})
    214 
    215 	// Call f asyncly to be async since ch is used in f.
    216 	go f()
    217 
    218 	// Run another loop to watch suspended() as the above update function is never called when the tab is hidden.
    219 	// To check the document's visiblity, visibilitychange event should usually be used. However, this event is
    220 	// not reliable and sometimes it is not fired (#961). Then, watch the state regularly instead.
    221 	go func() {
    222 		defer close(resStopAudioCh)
    223 
    224 		const interval = 100 * time.Millisecond
    225 		t := time.NewTicker(interval)
    226 		defer func() {
    227 			t.Stop()
    228 
    229 			// This is a dirty hack. (*time.Ticker).Stop() just marks the timer 'deleted' [1] and
    230 			// something might run even after Stop. On Wasm, this causes an issue to execute Go program
    231 			// even after finishing (#1027). Sleep for the interval time duration to ensure that
    232 			// everything related to the timer is finished.
    233 			//
    234 			// [1] runtime.deltimer
    235 			time.Sleep(interval)
    236 		}()
    237 
    238 		for {
    239 			select {
    240 			case <-t.C:
    241 				if u.suspended() {
    242 					hooks.SuspendAudio()
    243 				} else {
    244 					hooks.ResumeAudio()
    245 				}
    246 			case <-reqStopAudioCh:
    247 				return
    248 			}
    249 		}
    250 	}()
    251 
    252 	return errCh
    253 }
    254 
    255 func init() {
    256 	if jsutil.Equal(document.Get("body"), js.Null()) {
    257 		ch := make(chan struct{})
    258 		window.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    259 			close(ch)
    260 			return nil
    261 		}))
    262 		<-ch
    263 	}
    264 
    265 	window.Call("addEventListener", "resize", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    266 		theUI.updateScreenSize()
    267 		return nil
    268 	}))
    269 
    270 	// Adjust the initial scale to 1.
    271 	// https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
    272 	meta := document.Call("createElement", "meta")
    273 	meta.Set("name", "viewport")
    274 	meta.Set("content", "width=device-width, initial-scale=1")
    275 	document.Get("head").Call("appendChild", meta)
    276 
    277 	canvas = document.Call("createElement", "canvas")
    278 	canvas.Set("width", 16)
    279 	canvas.Set("height", 16)
    280 
    281 	document.Get("body").Call("appendChild", canvas)
    282 
    283 	htmlStyle := document.Get("documentElement").Get("style")
    284 	htmlStyle.Set("height", "100%")
    285 	htmlStyle.Set("margin", "0")
    286 	htmlStyle.Set("padding", "0")
    287 
    288 	bodyStyle := document.Get("body").Get("style")
    289 	bodyStyle.Set("backgroundColor", "#000")
    290 	bodyStyle.Set("height", "100%")
    291 	bodyStyle.Set("margin", "0")
    292 	bodyStyle.Set("padding", "0")
    293 
    294 	canvasStyle := canvas.Get("style")
    295 	canvasStyle.Set("width", "100%")
    296 	canvasStyle.Set("height", "100%")
    297 	canvasStyle.Set("margin", "0")
    298 	canvasStyle.Set("padding", "0")
    299 
    300 	// Make the canvas focusable.
    301 	canvas.Call("setAttribute", "tabindex", 1)
    302 	canvas.Get("style").Set("outline", "none")
    303 
    304 	// Keyboard
    305 	canvas.Call("addEventListener", "keydown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    306 		// Focus the canvas explicitly to activate tha game (#961).
    307 		canvas.Call("focus")
    308 
    309 		e := args[0]
    310 		// Don't 'preventDefault' on keydown events or keypress events wouldn't work (#715).
    311 		theUI.input.Update(e)
    312 		return nil
    313 	}))
    314 	canvas.Call("addEventListener", "keypress", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    315 		e := args[0]
    316 		e.Call("preventDefault")
    317 		theUI.input.Update(e)
    318 		return nil
    319 	}))
    320 	canvas.Call("addEventListener", "keyup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    321 		e := args[0]
    322 		e.Call("preventDefault")
    323 		theUI.input.Update(e)
    324 		return nil
    325 	}))
    326 
    327 	// Mouse
    328 	canvas.Call("addEventListener", "mousedown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    329 		// Focus the canvas explicitly to activate tha game (#961).
    330 		canvas.Call("focus")
    331 
    332 		e := args[0]
    333 		e.Call("preventDefault")
    334 		theUI.input.Update(e)
    335 		return nil
    336 	}))
    337 	canvas.Call("addEventListener", "mouseup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    338 		e := args[0]
    339 		e.Call("preventDefault")
    340 		theUI.input.Update(e)
    341 		return nil
    342 	}))
    343 	canvas.Call("addEventListener", "mousemove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    344 		e := args[0]
    345 		e.Call("preventDefault")
    346 		theUI.input.Update(e)
    347 		return nil
    348 	}))
    349 	canvas.Call("addEventListener", "wheel", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    350 		e := args[0]
    351 		e.Call("preventDefault")
    352 		theUI.input.Update(e)
    353 		return nil
    354 	}))
    355 
    356 	// Touch
    357 	canvas.Call("addEventListener", "touchstart", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    358 		// Focus the canvas explicitly to activate tha game (#961).
    359 		canvas.Call("focus")
    360 
    361 		e := args[0]
    362 		e.Call("preventDefault")
    363 		theUI.input.Update(e)
    364 		return nil
    365 	}))
    366 	canvas.Call("addEventListener", "touchend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    367 		e := args[0]
    368 		e.Call("preventDefault")
    369 		theUI.input.Update(e)
    370 		return nil
    371 	}))
    372 	canvas.Call("addEventListener", "touchmove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    373 		e := args[0]
    374 		e.Call("preventDefault")
    375 		theUI.input.Update(e)
    376 		return nil
    377 	}))
    378 
    379 	// Gamepad
    380 	window.Call("addEventListener", "gamepadconnected", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    381 		// Do nothing.
    382 		return nil
    383 	}))
    384 
    385 	canvas.Call("addEventListener", "contextmenu", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    386 		e := args[0]
    387 		e.Call("preventDefault")
    388 		return nil
    389 	}))
    390 
    391 	// Context
    392 	canvas.Call("addEventListener", "webglcontextlost", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    393 		e := args[0]
    394 		e.Call("preventDefault")
    395 		theUI.contextLost = true
    396 		restorable.OnContextLost()
    397 		return nil
    398 	}))
    399 	canvas.Call("addEventListener", "webglcontextrestored", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    400 		theUI.contextLost = false
    401 		return nil
    402 	}))
    403 }
    404 
    405 func (u *UserInterface) Run(context driver.UIContext) error {
    406 	if u.initFocused {
    407 		// Do not focus the canvas when the current document is in an iframe.
    408 		// Otherwise, the parent page tries to focus the iframe on every loading, which is annoying (#1373).
    409 		isInIframe := !jsutil.Equal(window.Get("location"), window.Get("parent").Get("location"))
    410 		if !isInIframe {
    411 			canvas.Call("focus")
    412 		}
    413 	}
    414 	u.running = true
    415 	return <-u.loop(context)
    416 }
    417 
    418 func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
    419 	panic("js: RunWithoutMainLoop is not implemented")
    420 }
    421 
    422 func (u *UserInterface) updateScreenSize() {
    423 	body := document.Get("body")
    424 	bw := int(body.Get("clientWidth").Float() * u.DeviceScaleFactor())
    425 	bh := int(body.Get("clientHeight").Float() * u.DeviceScaleFactor())
    426 	canvas.Set("width", bw)
    427 	canvas.Set("height", bh)
    428 	u.sizeChanged = true
    429 }
    430 
    431 func (u *UserInterface) SetScreenTransparent(transparent bool) {
    432 	if u.running {
    433 		panic("js: SetScreenTransparent can't be called after the main loop starts")
    434 	}
    435 
    436 	bodyStyle := document.Get("body").Get("style")
    437 	if transparent {
    438 		bodyStyle.Set("backgroundColor", "transparent")
    439 	} else {
    440 		bodyStyle.Set("backgroundColor", "#000")
    441 	}
    442 }
    443 
    444 func (u *UserInterface) IsScreenTransparent() bool {
    445 	bodyStyle := document.Get("body").Get("style")
    446 	return bodyStyle.Get("backgroundColor").String() == "transparent"
    447 }
    448 
    449 func (u *UserInterface) ResetForFrame() {
    450 	u.updateSize()
    451 	u.input.resetForFrame()
    452 }
    453 
    454 func (u *UserInterface) SetInitFocused(focused bool) {
    455 	if u.running {
    456 		panic("ui: SetInitFocused must be called before the main loop")
    457 	}
    458 	u.initFocused = focused
    459 }
    460 
    461 func (u *UserInterface) Input() driver.Input {
    462 	return &u.input
    463 }
    464 
    465 func (u *UserInterface) Window() driver.Window {
    466 	return nil
    467 }
    468 
    469 func (*UserInterface) Graphics() driver.Graphics {
    470 	return opengl.Get()
    471 }