input_js.go (12503B)
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 "encoding/hex" 19 "syscall/js" 20 "unicode" 21 22 "github.com/hajimehoshi/ebiten/v2/internal/driver" 23 ) 24 25 var ( 26 stringKeydown = js.ValueOf("keydown") 27 stringKeypress = js.ValueOf("keypress") 28 stringKeyup = js.ValueOf("keyup") 29 stringMousedown = js.ValueOf("mousedown") 30 stringMouseup = js.ValueOf("mouseup") 31 stringMousemove = js.ValueOf("mousemove") 32 stringWheel = js.ValueOf("wheel") 33 stringTouchstart = js.ValueOf("touchstart") 34 stringTouchend = js.ValueOf("touchend") 35 stringTouchmove = js.ValueOf("touchmove") 36 ) 37 38 var jsKeys []js.Value 39 40 func init() { 41 for _, k := range driverKeyToJSKey { 42 jsKeys = append(jsKeys, k) 43 } 44 } 45 46 func jsKeyToID(key js.Value) int { 47 // js.Value cannot be used as a map key. 48 // As the number of keys is around 100, just a dumb loop should work. 49 for i, k := range jsKeys { 50 if k.Equal(key) { 51 return i 52 } 53 } 54 return -1 55 } 56 57 type pos struct { 58 X int 59 Y int 60 } 61 62 type gamepad struct { 63 name string 64 mapping string 65 axisNum int 66 axes [16]float64 67 buttonNum int 68 buttonPressed [256]bool 69 buttonValues [256]float64 70 71 standardButtonPressed [driver.StandardGamepadButtonMax + 1]bool 72 standardButtonValues [driver.StandardGamepadButtonMax + 1]float64 73 standardAxisValues [driver.StandardGamepadAxisMax + 1]float64 74 } 75 76 func (g *gamepad) hasStandardLayoutMapping() bool { 77 // With go2cpp, the controller must have the standard 78 if go2cpp.Truthy() { 79 return true 80 } 81 return g.mapping == "standard" 82 } 83 84 type Input struct { 85 keyPressed map[int]bool 86 keyPressedEdge map[int]bool 87 mouseButtonPressed map[int]bool 88 cursorX int 89 cursorY int 90 origCursorX int 91 origCursorY int 92 wheelX float64 93 wheelY float64 94 gamepads map[driver.GamepadID]gamepad 95 touches map[driver.TouchID]pos 96 runeBuffer []rune 97 ui *UserInterface 98 } 99 100 func (i *Input) CursorPosition() (x, y int) { 101 if i.ui.context == nil { 102 return 0, 0 103 } 104 xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor()) 105 return int(xf), int(yf) 106 } 107 108 func (i *Input) GamepadSDLID(id driver.GamepadID) string { 109 // This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID. 110 // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/emscripten/SDL_sysjoystick.c#L385 111 g, ok := i.gamepads[id] 112 if !ok { 113 return "" 114 } 115 var sdlid [16]byte 116 copy(sdlid[:], []byte(g.name)) 117 return hex.EncodeToString(sdlid[:]) 118 } 119 120 // GamepadName returns a string containing some information about the controller. 121 // A PS2 controller returned "810-3-USB Gamepad" on Firefox 122 // A Xbox 360 controller returned "xinput" on Firefox and "Xbox 360 Controller (XInput STANDARD GAMEPAD)" on Chrome 123 func (i *Input) GamepadName(id driver.GamepadID) string { 124 g, ok := i.gamepads[id] 125 if !ok { 126 return "" 127 } 128 return g.name 129 } 130 131 func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID { 132 for id := range i.gamepads { 133 gamepadIDs = append(gamepadIDs, id) 134 } 135 return gamepadIDs 136 } 137 138 func (i *Input) GamepadAxisNum(id driver.GamepadID) int { 139 g, ok := i.gamepads[id] 140 if !ok { 141 return 0 142 } 143 return g.axisNum 144 } 145 146 func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 { 147 g, ok := i.gamepads[id] 148 if !ok { 149 return 0 150 } 151 if g.axisNum <= axis { 152 return 0 153 } 154 return g.axes[axis] 155 } 156 157 func (i *Input) GamepadButtonNum(id driver.GamepadID) int { 158 g, ok := i.gamepads[id] 159 if !ok { 160 return 0 161 } 162 return g.buttonNum 163 } 164 165 func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool { 166 g, ok := i.gamepads[id] 167 if !ok { 168 return false 169 } 170 if g.buttonNum <= int(button) { 171 return false 172 } 173 return g.buttonPressed[button] 174 } 175 176 func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID { 177 for id := range i.touches { 178 touchIDs = append(touchIDs, id) 179 } 180 return touchIDs 181 } 182 183 func (i *Input) TouchPosition(id driver.TouchID) (x, y int) { 184 d := i.ui.DeviceScaleFactor() 185 for tid, pos := range i.touches { 186 if id == tid { 187 x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d) 188 return int(x), int(y) 189 } 190 } 191 return 0, 0 192 } 193 194 func (i *Input) AppendInputChars(runes []rune) []rune { 195 return append(runes, i.runeBuffer...) 196 } 197 198 func (i *Input) resetForFrame() { 199 i.runeBuffer = nil 200 i.wheelX = 0 201 i.wheelY = 0 202 } 203 204 func (i *Input) IsKeyPressed(key driver.Key) bool { 205 if i.keyPressed != nil { 206 if i.keyPressed[jsKeyToID(driverKeyToJSKey[key])] { 207 return true 208 } 209 } 210 if i.keyPressedEdge != nil { 211 for c, k := range edgeKeyCodeToDriverKey { 212 if k != key { 213 continue 214 } 215 if i.keyPressedEdge[c] { 216 return true 217 } 218 } 219 } 220 return false 221 } 222 223 var codeToMouseButton = map[int]driver.MouseButton{ 224 0: driver.MouseButtonLeft, 225 1: driver.MouseButtonMiddle, 226 2: driver.MouseButtonRight, 227 } 228 229 func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool { 230 if i.mouseButtonPressed == nil { 231 i.mouseButtonPressed = map[int]bool{} 232 } 233 for c, b := range codeToMouseButton { 234 if b != button { 235 continue 236 } 237 if i.mouseButtonPressed[c] { 238 return true 239 } 240 } 241 return false 242 } 243 244 func (i *Input) Wheel() (xoff, yoff float64) { 245 return i.wheelX, i.wheelY 246 } 247 248 func (i *Input) keyDown(code js.Value) { 249 if i.keyPressed == nil { 250 i.keyPressed = map[int]bool{} 251 } 252 i.keyPressed[jsKeyToID(code)] = true 253 } 254 255 func (i *Input) keyUp(code js.Value) { 256 if i.keyPressed == nil { 257 i.keyPressed = map[int]bool{} 258 } 259 i.keyPressed[jsKeyToID(code)] = false 260 } 261 262 func (i *Input) keyDownEdge(code int) { 263 if i.keyPressedEdge == nil { 264 i.keyPressedEdge = map[int]bool{} 265 } 266 i.keyPressedEdge[code] = true 267 } 268 269 func (i *Input) keyUpEdge(code int) { 270 if i.keyPressedEdge == nil { 271 i.keyPressedEdge = map[int]bool{} 272 } 273 i.keyPressedEdge[code] = false 274 } 275 276 func (i *Input) mouseDown(code int) { 277 if i.mouseButtonPressed == nil { 278 i.mouseButtonPressed = map[int]bool{} 279 } 280 i.mouseButtonPressed[code] = true 281 } 282 283 func (i *Input) mouseUp(code int) { 284 if i.mouseButtonPressed == nil { 285 i.mouseButtonPressed = map[int]bool{} 286 } 287 i.mouseButtonPressed[code] = false 288 } 289 290 func (i *Input) updateGamepads() { 291 nav := js.Global().Get("navigator") 292 if !nav.Truthy() { 293 return 294 } 295 296 if !nav.Get("getGamepads").Truthy() { 297 return 298 } 299 300 for k := range i.gamepads { 301 delete(i.gamepads, k) 302 } 303 304 gamepads := nav.Call("getGamepads") 305 l := gamepads.Length() 306 for idx := 0; idx < l; idx++ { 307 gp := gamepads.Index(idx) 308 if !gp.Truthy() { 309 continue 310 } 311 312 id := driver.GamepadID(gp.Get("index").Int()) 313 g := gamepad{} 314 g.name = gp.Get("id").String() 315 g.mapping = gp.Get("mapping").String() 316 317 axes := gp.Get("axes") 318 axesNum := axes.Length() 319 g.axisNum = axesNum 320 for a := 0; a < axesNum; a++ { 321 g.axes[a] = axes.Index(a).Float() 322 } 323 324 buttons := gp.Get("buttons") 325 buttonsNum := buttons.Length() 326 g.buttonNum = buttonsNum 327 for b := 0; b < buttonsNum; b++ { 328 btn := buttons.Index(b) 329 g.buttonPressed[b] = btn.Get("pressed").Bool() 330 g.buttonValues[b] = btn.Get("value").Float() 331 } 332 333 if g.mapping == "standard" { 334 // When the gamepad's mapping is "standard", the button and axis IDs are already mapped as the standard layout. 335 // See https://www.w3.org/TR/gamepad/#remapping. 336 copy(g.standardButtonPressed[:], g.buttonPressed[:]) 337 copy(g.standardButtonValues[:], g.buttonValues[:]) 338 copy(g.standardAxisValues[:], g.axes[:]) 339 } 340 341 if i.gamepads == nil { 342 i.gamepads = map[driver.GamepadID]gamepad{} 343 } 344 i.gamepads[id] = g 345 } 346 } 347 348 func (i *Input) updateFromEvent(e js.Value) { 349 // Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy 350 // overhead (#1437). 351 switch t := e.Get("type"); { 352 case t.Equal(stringKeydown): 353 c := e.Get("code") 354 if c.Type() != js.TypeString { 355 code := e.Get("keyCode").Int() 356 if edgeKeyCodeToDriverKey[code] == driver.KeyArrowUp || 357 edgeKeyCodeToDriverKey[code] == driver.KeyArrowDown || 358 edgeKeyCodeToDriverKey[code] == driver.KeyArrowLeft || 359 edgeKeyCodeToDriverKey[code] == driver.KeyArrowRight || 360 edgeKeyCodeToDriverKey[code] == driver.KeyBackspace || 361 edgeKeyCodeToDriverKey[code] == driver.KeyTab { 362 e.Call("preventDefault") 363 } 364 i.keyDownEdge(code) 365 return 366 } 367 if c.Equal(driverKeyToJSKey[driver.KeyArrowUp]) || 368 c.Equal(driverKeyToJSKey[driver.KeyArrowDown]) || 369 c.Equal(driverKeyToJSKey[driver.KeyArrowLeft]) || 370 c.Equal(driverKeyToJSKey[driver.KeyArrowRight]) || 371 c.Equal(driverKeyToJSKey[driver.KeyBackspace]) || 372 c.Equal(driverKeyToJSKey[driver.KeyTab]) { 373 e.Call("preventDefault") 374 } 375 i.keyDown(c) 376 case t.Equal(stringKeypress): 377 if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) { 378 i.runeBuffer = append(i.runeBuffer, r) 379 } 380 case t.Equal(stringKeyup): 381 if e.Get("code").Type() != js.TypeString { 382 // Assume that UA is Edge. 383 code := e.Get("keyCode").Int() 384 i.keyUpEdge(code) 385 return 386 } 387 i.keyUp(e.Get("code")) 388 case t.Equal(stringMousedown): 389 button := e.Get("button").Int() 390 i.mouseDown(button) 391 i.setMouseCursorFromEvent(e) 392 case t.Equal(stringMouseup): 393 button := e.Get("button").Int() 394 i.mouseUp(button) 395 i.setMouseCursorFromEvent(e) 396 case t.Equal(stringMousemove): 397 i.setMouseCursorFromEvent(e) 398 case t.Equal(stringWheel): 399 // TODO: What if e.deltaMode is not DOM_DELTA_PIXEL? 400 i.wheelX = -e.Get("deltaX").Float() 401 i.wheelY = -e.Get("deltaY").Float() 402 case t.Equal(stringTouchstart) || t.Equal(stringTouchend) || t.Equal(stringTouchmove): 403 i.updateTouchesFromEvent(e) 404 } 405 406 i.ui.forceUpdateOnMinimumFPSMode() 407 } 408 409 func (i *Input) setMouseCursorFromEvent(e js.Value) { 410 if i.ui.cursorMode == driver.CursorModeCaptured { 411 x, y := e.Get("clientX").Int(), e.Get("clientY").Int() 412 i.origCursorX, i.origCursorY = x, y 413 dx, dy := e.Get("movementX").Int(), e.Get("movementY").Int() 414 i.cursorX += dx 415 i.cursorY += dy 416 return 417 } 418 419 x, y := e.Get("clientX").Int(), e.Get("clientY").Int() 420 i.cursorX, i.cursorY = x, y 421 i.origCursorX, i.origCursorY = x, y 422 } 423 424 func (i *Input) recoverCursorPosition() { 425 i.cursorX, i.cursorY = i.origCursorX, i.origCursorY 426 } 427 428 func (in *Input) updateTouchesFromEvent(e js.Value) { 429 j := e.Get("targetTouches") 430 for k := range in.touches { 431 delete(in.touches, k) 432 } 433 for i := 0; i < j.Length(); i++ { 434 jj := j.Call("item", i) 435 id := driver.TouchID(jj.Get("identifier").Int()) 436 if in.touches == nil { 437 in.touches = map[driver.TouchID]pos{} 438 } 439 in.touches[id] = pos{ 440 X: jj.Get("clientX").Int(), 441 Y: jj.Get("clientY").Int(), 442 } 443 } 444 } 445 446 func (i *Input) updateForGo2Cpp() { 447 if !go2cpp.Truthy() { 448 return 449 } 450 451 for k := range i.touches { 452 delete(i.touches, k) 453 } 454 touchCount := go2cpp.Get("touchCount").Int() 455 for idx := 0; idx < touchCount; idx++ { 456 id := go2cpp.Call("getTouchId", idx) 457 x := go2cpp.Call("getTouchX", idx) 458 y := go2cpp.Call("getTouchY", idx) 459 if i.touches == nil { 460 i.touches = map[driver.TouchID]pos{} 461 } 462 i.touches[driver.TouchID(id.Int())] = pos{ 463 X: x.Int(), 464 Y: y.Int(), 465 } 466 } 467 } 468 469 func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool { 470 g, ok := i.gamepads[id] 471 if !ok { 472 return false 473 } 474 return g.hasStandardLayoutMapping() 475 } 476 477 func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 { 478 g, ok := i.gamepads[id] 479 if !ok { 480 return 0 481 } 482 if !g.hasStandardLayoutMapping() { 483 return 0 484 } 485 return g.standardAxisValues[axis] 486 } 487 488 func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 { 489 g, ok := i.gamepads[id] 490 if !ok { 491 return 0 492 } 493 if !g.hasStandardLayoutMapping() { 494 return 0 495 } 496 return g.standardButtonValues[button] 497 } 498 499 func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool { 500 g, ok := i.gamepads[id] 501 if !ok { 502 return false 503 } 504 if !g.hasStandardLayoutMapping() { 505 return false 506 } 507 return g.standardButtonPressed[button] 508 }