Add internal/gamepaddb (#1805)

This is basically a revert of 93a156a718.
This implements parsing the SDL gamepad mappings by Ebiten instead
of GLFW, so that Ebiten can handle parsing errors completely.

Closes #1802
This commit is contained in:
Hajime Hoshi 2021-09-11 22:46:05 +09:00 committed by GitHub
parent 28963a66ee
commit f23dadb8ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 541 additions and 188 deletions

View File

@ -16,6 +16,7 @@ package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
) )
// AppendInputChars appends "printable" runes, read from the keyboard at the time update is called, to runes, // AppendInputChars appends "printable" runes, read from the keyboard at the time update is called, to runes,
@ -251,11 +252,11 @@ func IsStandardGamepadLayoutAvailable(id GamepadID) bool {
// //
// On platforms where gamepad mappings are not managed by Ebiten, this always returns false and nil. // On platforms where gamepad mappings are not managed by Ebiten, this always returns false and nil.
// //
// UpdateStandardGamepadLayoutMappings must be called on the main thread before ebiten.Run, and is concurrent-safe after ebiten.Run. // UpdateStandardGamepadLayoutMappings is concurrent-safe.
// //
// Updated mappings take effect immediately even for already connected gamepads. // Updated mappings take effect immediately even for already connected gamepads.
func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) { func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
return uiDriver().Input().UpdateStandardGamepadLayoutMappings(mappings) return gamepaddb.Update([]byte(mappings))
} }
// TouchID represents a touch's identifier. // TouchID represents a touch's identifier.

View File

@ -34,7 +34,6 @@ type Input interface {
IsStandardGamepadButtonPressed(id GamepadID, button StandardGamepadButton) bool IsStandardGamepadButtonPressed(id GamepadID, button StandardGamepadButton) bool
IsStandardGamepadLayoutAvailable(id GamepadID) bool IsStandardGamepadLayoutAvailable(id GamepadID) bool
StandardGamepadAxisValue(id GamepadID, button StandardGamepadAxis) float64 StandardGamepadAxisValue(id GamepadID, button StandardGamepadAxis) float64
UpdateStandardGamepadLayoutMappings(mapping string) (bool, error)
TouchPosition(id TouchID) (x, y int) TouchPosition(id TouchID) (x, y int)
Wheel() (xoff, yoff float64) Wheel() (xoff, yoff float64)
} }

View File

@ -0,0 +1,454 @@
// Copyright 2021 The Ebiten Authors
//
// 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.
// gamecontrollerdb.txt is downloaded at https://github.com/gabomdq/SDL_GameControllerDB.
//go:generate curl --location --remote-name https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt
//go:generate file2byteslice -package gamepaddb -input=./gamecontrollerdb.txt -output=./gamecontrollerdb.txt.go -var=gamecontrollerdbTxt
package gamepaddb
import (
"bufio"
"bytes"
"fmt"
"io"
"runtime"
"strconv"
"strings"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
)
type platform int
const (
platformUnknown platform = iota
platformWindows
platformMacOS
platformUnix
platformAndroid
platformIOS
)
var currentPlatform platform
func init() {
if runtime.GOOS == "windows" {
currentPlatform = platformWindows
return
}
if runtime.GOOS == "aix" ||
runtime.GOOS == "dragonfly" ||
runtime.GOOS == "freebsd" ||
runtime.GOOS == "hurd" ||
runtime.GOOS == "illumos" ||
runtime.GOOS == "linux" ||
runtime.GOOS == "netbsd" ||
runtime.GOOS == "openbsd" ||
runtime.GOOS == "solaris" {
currentPlatform = platformUnix
return
}
if runtime.GOOS == "android" {
currentPlatform = platformAndroid
return
}
if isIOS {
currentPlatform = platformIOS
return
}
if runtime.GOOS == "darwin" {
currentPlatform = platformMacOS
return
}
}
var additionalGLFWGamepads = []byte(`
78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757403000000000000000000,XInput Arcade Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757404000000000000000000,XInput Flight Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757405000000000000000000,XInput Dance Pad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757406000000000000000000,XInput Guitar (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757408000000000000000000,XInput Drum Kit (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
`)
func init() {
if _, err := Update(gamecontrollerdbTxt); err != nil {
panic(err)
}
if _, err := Update(additionalGLFWGamepads); err != nil {
panic(err)
}
}
type mappingType int
const (
mappingTypeButton mappingType = iota
mappingTypeAxis
mappingTypeHat
)
const (
HatUp = 1
HatRight = 2
HatDown = 4
HatLeft = 8
)
type mapping struct {
Type mappingType
Index int
AxisScale int
AxisOffset int
HatState int
}
var (
gamepadButtonMappings = map[string]map[driver.StandardGamepadButton]*mapping{}
gamepadAxisMappings = map[string]map[driver.StandardGamepadAxis]*mapping{}
mappingsM sync.RWMutex
)
func processLine(line string, platform platform) error {
line = strings.TrimSpace(line)
if len(line) == 0 {
return nil
}
if line[0] == '#' {
return nil
}
tokens := strings.Split(line, ",")
id := tokens[0]
for _, token := range tokens[2:] {
if len(token) == 0 {
continue
}
tks := strings.Split(token, ":")
if tks[0] == "platform" {
switch tks[1] {
case "Windows":
if platform != platformWindows {
return nil
}
case "Mac OS X":
if platform != platformMacOS {
return nil
}
case "Linux":
if platform != platformUnix {
return nil
}
case "Android":
if platform != platformAndroid {
return nil
}
case "iOS":
if platform != platformIOS {
return nil
}
default:
return fmt.Errorf("gamepaddb: unexpected platform: %s", tks[1])
}
continue
}
// Found a token without a colon e.g., 'sat' or 'm_nin' on a Saturn controller. Ignore this.
if len(tks) == 1 {
continue
}
gb, err := parseMappingElement(tks[1])
if err != nil {
return err
}
if b, ok := toStandardGamepadButton(tks[0]); ok {
m, ok := gamepadButtonMappings[id]
if !ok {
m = map[driver.StandardGamepadButton]*mapping{}
gamepadButtonMappings[id] = m
}
m[b] = gb
continue
}
if a, ok := toStandardGamepadAxis(tks[0]); ok {
m, ok := gamepadAxisMappings[id]
if !ok {
m = map[driver.StandardGamepadAxis]*mapping{}
gamepadAxisMappings[id] = m
}
m[a] = gb
continue
}
// The buttons like "misc1" are ignored so far.
// There is no corresponding button in the Web standard gamepad layout.
}
return nil
}
func parseMappingElement(str string) (*mapping, error) {
switch {
case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a") || str[0] == '~':
var tilda bool
if str[0] == '~' {
str = str[1:]
tilda = true
}
if str[len(str)-1] == '~' {
str = str[:len(str)-1]
tilda = true
}
min := -1
max := 1
numstr := str[1:]
if str[0] == '+' {
numstr = str[2:]
min = 0
} else if str[0] == '-' {
numstr = str[2:]
max = 0
}
scale := 2 / (max - min)
offset := -(max + min)
if tilda {
scale = -scale
offset = -offset
}
index, err := strconv.Atoi(numstr)
if err != nil {
return nil, err
}
return &mapping{
Type: mappingTypeAxis,
Index: index,
AxisScale: scale,
AxisOffset: offset,
}, nil
case str[0] == 'b':
index, err := strconv.Atoi(str[1:])
if err != nil {
return nil, err
}
return &mapping{
Type: mappingTypeButton,
Index: index,
}, nil
case str[0] == 'h':
tokens := strings.Split(str[1:], ".")
if len(tokens) < 2 {
return nil, fmt.Errorf("gamepaddb: unexpected hat: %s", str)
}
index, err := strconv.Atoi(tokens[0])
if err != nil {
return nil, err
}
hat, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, err
}
return &mapping{
Type: mappingTypeHat,
Index: index,
HatState: hat,
}, nil
}
return nil, fmt.Errorf("gamepaddb: unepxected mapping: %s", str)
}
func toStandardGamepadButton(str string) (driver.StandardGamepadButton, bool) {
switch str {
case "a":
return driver.StandardGamepadButtonRightBottom, true
case "b":
return driver.StandardGamepadButtonRightRight, true
case "x":
return driver.StandardGamepadButtonRightLeft, true
case "y":
return driver.StandardGamepadButtonRightTop, true
case "back":
return driver.StandardGamepadButtonCenterLeft, true
case "start":
return driver.StandardGamepadButtonCenterRight, true
case "guide":
return driver.StandardGamepadButtonCenterCenter, true
case "leftshoulder":
return driver.StandardGamepadButtonFrontTopLeft, true
case "rightshoulder":
return driver.StandardGamepadButtonFrontTopRight, true
case "leftstick":
return driver.StandardGamepadButtonLeftStick, true
case "rightstick":
return driver.StandardGamepadButtonRightStick, true
case "dpup":
return driver.StandardGamepadButtonLeftTop, true
case "dpright":
return driver.StandardGamepadButtonLeftRight, true
case "dpdown":
return driver.StandardGamepadButtonLeftBottom, true
case "dpleft":
return driver.StandardGamepadButtonLeftLeft, true
case "lefttrigger":
return driver.StandardGamepadButtonFrontBottomLeft, true
case "righttrigger":
return driver.StandardGamepadButtonFrontBottomRight, true
default:
return 0, false
}
}
func toStandardGamepadAxis(str string) (driver.StandardGamepadAxis, bool) {
switch str {
case "leftx":
return driver.StandardGamepadAxisLeftStickHorizontal, true
case "lefty":
return driver.StandardGamepadAxisLeftStickVertical, true
case "rightx":
return driver.StandardGamepadAxisRightStickHorizontal, true
case "righty":
return driver.StandardGamepadAxisRightStickVertical, true
default:
return 0, false
}
}
func HasStandardLayoutMapping(id string) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
if _, ok := gamepadButtonMappings[id]; ok {
return true
}
if _, ok := gamepadAxisMappings[id]; ok {
return true
}
return false
}
type GamepadState interface {
Axis(index int) float64
Button(index int) bool
Hat(index int) int
}
func AxisValue(id string, axis driver.StandardGamepadAxis, state GamepadState) float64 {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings, ok := gamepadAxisMappings[id]
if !ok {
return 0
}
switch m := mappings[axis]; m.Type {
case mappingTypeAxis:
v := state.Axis(m.Index)*float64(m.AxisScale) + float64(m.AxisOffset)
if v > 1 {
return 1
} else if v < -1 {
return -1
}
return v
case mappingTypeButton:
if state.Button(m.Index) {
return 1
} else {
return -1
}
case mappingTypeHat:
if state.Hat(m.Index)&m.HatState != 0 {
return 1
} else {
return -1
}
}
return 0
}
func IsButtonPressed(id string, button driver.StandardGamepadButton, state GamepadState) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings, ok := gamepadButtonMappings[id]
if !ok {
return false
}
switch m := mappings[button]; m.Type {
case mappingTypeAxis:
v := state.Axis(m.Index)*float64(m.AxisScale) + float64(m.AxisOffset)
if m.AxisOffset < 0 || m.AxisOffset == 0 && m.AxisScale > 0 {
return v >= 0
} else {
return v <= 0
}
case mappingTypeButton:
return state.Button(m.Index)
case mappingTypeHat:
return state.Hat(m.Index)&m.HatState != 0
}
return false
}
// Update adds new gamepad mappings.
// The string must be in the format of SDL_GameControllerDB.
func Update(mapping []byte) (bool, error) {
if currentPlatform == platformUnknown {
return false, nil
}
// TODO: Implement this (#1557)
if currentPlatform == platformAndroid || currentPlatform == platformIOS {
// Note: NOT returning an error, as mappings also do not matter right now.
return false, nil
}
mappingsM.Lock()
defer mappingsM.Unlock()
buf := bytes.NewBuffer(mapping)
r := bufio.NewReader(buf)
for {
line, err := r.ReadString('\n')
if err != nil && err != io.EOF {
return false, err
}
if err := processLine(line, currentPlatform); err != nil {
return false, err
}
if err == io.EOF {
break
}
}
return true, nil
}

View File

@ -0,0 +1,20 @@
// Copyright 2021 The Ebiten Authors
//
// 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.
//go:build darwin && ios
// +build darwin,ios
package gamepaddb
const isIOS = true

View File

@ -0,0 +1,20 @@
// Copyright 2021 The Ebiten Authors
//
// 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.
//go:build !darwin || !ios
// +build !darwin !ios
package gamepaddb
const isIOS = false

View File

@ -24,8 +24,6 @@ import (
type ( type (
Action int Action int
ErrorCode int ErrorCode int
GamepadAxis int
GamepadButton int
Hint int Hint int
InputMode int InputMode int
Joystick int Joystick int
@ -169,30 +167,3 @@ const (
HatLeftUp = HatLeft | HatUp HatLeftUp = HatLeft | HatUp
HatLeftDown = HatLeft | HatDown HatLeftDown = HatLeft | HatDown
) )
const (
AxisLeftX = GamepadAxis(0)
AxisLeftY = GamepadAxis(1)
AxisRightX = GamepadAxis(2)
AxisRightY = GamepadAxis(3)
AxisLeftTrigger = GamepadAxis(4)
AxisRightTrigger = GamepadAxis(5)
)
const (
ButtonA = GamepadButton(0)
ButtonB = GamepadButton(1)
ButtonX = GamepadButton(2)
ButtonY = GamepadButton(3)
ButtonLeftBumper = GamepadButton(4)
ButtonRightBumper = GamepadButton(5)
ButtonBack = GamepadButton(6)
ButtonStart = GamepadButton(7)
ButtonGuide = GamepadButton(8)
ButtonLeftThumb = GamepadButton(9)
ButtonRightThumb = GamepadButton(10)
ButtonDpadUp = GamepadButton(11)
ButtonDpadRight = GamepadButton(12)
ButtonDpadDown = GamepadButton(13)
ButtonDpadLeft = GamepadButton(14)
)

View File

@ -289,19 +289,6 @@ func (j Joystick) GetHats() []JoystickHatState {
return hats return hats
} }
func (j Joystick) GetGamepadState() *GamepadState {
s := glfw.Joystick(j).GetGamepadState()
if s == nil {
return nil
}
state := &GamepadState{}
for i, b := range s.Buttons {
state.Buttons[i] = Action(b)
}
copy(state.Axes[:], s.Axes[:])
return state
}
func GetMonitors() []*Monitor { func GetMonitors() []*Monitor {
ms := []*Monitor{} ms := []*Monitor{}
for _, m := range glfw.GetMonitors() { for _, m := range glfw.GetMonitors() {
@ -360,10 +347,6 @@ func Terminate() {
glfw.Terminate() glfw.Terminate()
} }
func UpdateGamepadMappings(mapping string) bool {
return glfw.UpdateGamepadMappings(mapping)
}
func WaitEvents() { func WaitEvents() {
glfw.WaitEvents() glfw.WaitEvents()
} }

View File

@ -435,25 +435,6 @@ func (j Joystick) GetHats() []JoystickHatState {
return hats return hats
} }
func (j Joystick) GetGamepadState() *GamepadState {
var s struct {
Buttons [15]uint8
Axes [6]float32
}
r := glfwDLL.call("glfwGetGamepadState", uintptr(j), uintptr(unsafe.Pointer(&s)))
panicError()
if r != True {
return nil
}
state := &GamepadState{}
for i, b := range s.Buttons {
state.Buttons[i] = Action(b)
}
copy(state.Axes[:], s.Axes[:])
return state
}
func GetMonitors() []*Monitor { func GetMonitors() []*Monitor {
var l int32 var l int32
ptr := glfwDLL.call("glfwGetMonitors", uintptr(unsafe.Pointer(&l))) ptr := glfwDLL.call("glfwGetMonitors", uintptr(unsafe.Pointer(&l)))
@ -551,14 +532,6 @@ func Terminate() {
} }
} }
func UpdateGamepadMappings(mapping string) bool {
m := append([]byte(mapping), 0)
defer runtime.KeepAlive(m)
r := glfwDLL.call("glfwUpdateGamepadMappings", uintptr(unsafe.Pointer(&m[0])))
panicError()
return byte(r) == True
}
func WaitEvents() { func WaitEvents() {
glfwDLL.call("glfwWaitEvents") glfwDLL.call("glfwWaitEvents")
panicError() panicError()

View File

@ -33,8 +33,3 @@ type VidMode struct {
BlueBits int BlueBits int
RefreshRate int RefreshRate int
} }
type GamepadState struct {
Buttons [15]Action
Axes [6]float32
}

View File

@ -23,12 +23,12 @@
package glfw package glfw
import ( import (
"fmt"
"math" "math"
"sync" "sync"
"unicode" "unicode"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/glfw" "github.com/hajimehoshi/ebiten/v2/internal/glfw"
) )
@ -40,7 +40,8 @@ type gamepad struct {
axes [16]float64 axes [16]float64
buttonNum int buttonNum int
buttonPressed [256]bool buttonPressed [256]bool
state *glfw.GamepadState hatsNum int
hats [16]int
} }
type Input struct { type Input struct {
@ -319,12 +320,6 @@ func (i *Input) update(window *glfw.Window, context driver.UIContext) {
continue continue
} }
i.gamepads[id].state = id.GetGamepadState()
// Note that GLFW's gamepad GUID follows SDL's GUID.
i.gamepads[id].guid = id.GetGUID()
i.gamepads[id].name = id.GetName()
buttons := id.GetButtons() buttons := id.GetButtons()
// A gamepad can be detected even though there are not. Apparently, some special devices are // A gamepad can be detected even though there are not. Apparently, some special devices are
@ -354,6 +349,20 @@ func (i *Input) update(window *glfw.Window, context driver.UIContext) {
} }
i.gamepads[id].axes[a] = float64(axes32[a]) i.gamepads[id].axes[a] = float64(axes32[a])
} }
hats := id.GetHats()
i.gamepads[id].hatsNum = len(hats)
for h := 0; h < len(i.gamepads[id].hats); h++ {
if len(hats) <= h {
i.gamepads[id].hats[h] = 0
continue
}
i.gamepads[id].hats[h] = int(hats[h])
}
// Note that GLFW's gamepad GUID follows SDL's GUID.
i.gamepads[id].guid = id.GetGUID()
i.gamepads[id].name = id.GetName()
} }
} }
@ -365,7 +374,7 @@ func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
return false return false
} }
g := i.gamepads[int(id)] g := i.gamepads[int(id)]
return g.state != nil return gamepaddb.HasStandardLayoutMapping(g.guid)
} }
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 { func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
@ -376,10 +385,7 @@ func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.Standa
return 0 return 0
} }
g := i.gamepads[int(id)] g := i.gamepads[int(id)]
if g.state == nil { return gamepaddb.AxisValue(g.guid, axis, gamepadState{&g})
return 0
}
return float64(g.state.Axes[standardAxisToGLFWAxis(axis)])
} }
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool { func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
@ -390,89 +396,37 @@ func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button drive
return false return false
} }
g := i.gamepads[int(id)] g := i.gamepads[int(id)]
if g.state == nil { return gamepaddb.IsButtonPressed(g.guid, button, gamepadState{&g})
return false
}
switch button {
case driver.StandardGamepadButtonFrontBottomLeft:
return g.state.Axes[glfw.AxisLeftTrigger] > 0
case driver.StandardGamepadButtonFrontBottomRight:
return g.state.Axes[glfw.AxisRightTrigger] > 0
}
return g.state.Buttons[standardButtonToGLFWButton(button)] == glfw.Press
} }
func standardAxisToGLFWAxis(axis driver.StandardGamepadAxis) glfw.GamepadAxis { func init() {
switch axis { // Confirm that all the hat state values are the same.
case driver.StandardGamepadAxisLeftStickHorizontal: if gamepaddb.HatUp != glfw.HatUp {
return glfw.AxisLeftX panic("glfw: gamepaddb.HatUp must equal to glfw.HatUp but not")
case driver.StandardGamepadAxisLeftStickVertical: }
return glfw.AxisLeftY if gamepaddb.HatRight != glfw.HatRight {
case driver.StandardGamepadAxisRightStickHorizontal: panic("glfw: gamepaddb.HatRight must equal to glfw.HatRight but not")
return glfw.AxisRightX }
case driver.StandardGamepadAxisRightStickVertical: if gamepaddb.HatDown != glfw.HatDown {
return glfw.AxisRightY panic("glfw: gamepaddb.HatDown must equal to glfw.HatDown but not")
default: }
panic(fmt.Sprintf("glfw: invalid or inconvertible StandardGamepadAxis: %d", axis)) if gamepaddb.HatLeft != glfw.HatLeft {
panic("glfw: gamepaddb.HatLeft must equal to glfw.HatLeft but not")
} }
} }
func standardButtonToGLFWButton(button driver.StandardGamepadButton) glfw.GamepadButton { type gamepadState struct {
switch button { g *gamepad
case driver.StandardGamepadButtonRightBottom:
return glfw.ButtonA
case driver.StandardGamepadButtonRightRight:
return glfw.ButtonB
case driver.StandardGamepadButtonRightLeft:
return glfw.ButtonX
case driver.StandardGamepadButtonRightTop:
return glfw.ButtonY
case driver.StandardGamepadButtonFrontTopLeft:
return glfw.ButtonLeftBumper
case driver.StandardGamepadButtonFrontTopRight:
return glfw.ButtonRightBumper
case driver.StandardGamepadButtonCenterLeft:
return glfw.ButtonBack
case driver.StandardGamepadButtonCenterRight:
return glfw.ButtonStart
case driver.StandardGamepadButtonLeftStick:
return glfw.ButtonLeftThumb
case driver.StandardGamepadButtonRightStick:
return glfw.ButtonRightThumb
case driver.StandardGamepadButtonLeftTop:
return glfw.ButtonDpadUp
case driver.StandardGamepadButtonLeftBottom:
return glfw.ButtonDpadDown
case driver.StandardGamepadButtonLeftLeft:
return glfw.ButtonDpadLeft
case driver.StandardGamepadButtonLeftRight:
return glfw.ButtonDpadRight
case driver.StandardGamepadButtonCenterCenter:
return glfw.ButtonGuide
default:
panic(fmt.Sprintf("glfw: invalid or inconvertible StandardGamepadButton: %d", button))
}
} }
// UpdateStandardGamepadLayoutMappings can be used to provide new gamepad mappings to Ebiten. func (s gamepadState) Axis(index int) float64 {
// The string must be in the format of SDL_GameControllerDB. return s.g.axes[index]
func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) {
var err error
if i.ui.isRunning() {
err = i.ui.t.Call(func() error {
return i.updateStandardGamepadLayoutMappings(mapping)
})
} else {
err = i.updateStandardGamepadLayoutMappings(mapping)
}
return err == nil, err
} }
func (i *Input) updateStandardGamepadLayoutMappings(mapping string) error { func (s gamepadState) Button(index int) bool {
if !glfw.UpdateGamepadMappings(mapping) { return s.g.buttonPressed[index]
// Would be nice if we could actually get the error string. }
// However, go-gl currently does not provide it in any way.
return fmt.Errorf("glfw: could not parse or update gamepad mappings") func (s gamepadState) Hat(index int) int {
} return s.g.hats[index]
return nil
} }

View File

@ -172,10 +172,6 @@ func initialize() error {
return err return err
} }
if !glfw.UpdateGamepadMappings(string(gamecontrollerdbTxt)) {
return fmt.Errorf("glfw: UpdateGamepadMappings failed")
}
glfw.WindowHint(glfw.Visible, glfw.False) glfw.WindowHint(glfw.Visible, glfw.False)
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)

View File

@ -496,9 +496,3 @@ func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button drive
} }
return g.standardButtonPressed[button] return g.standardButtonPressed[button]
} }
// UpdateStandardGamepadLayoutMappings is not supported for JS - the browser maintains the mappings.
func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) {
// All OK - browser owns this though.
return false, nil
}

View File

@ -147,13 +147,6 @@ func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.Standa
return 0 return 0
} }
// UpdateStandardGamepadLayoutMappings is not supported on mobile - this still needs to be implemented.
func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) {
// TODO: Implement this (#1557)
// Note: NOT returning an error, as mappings also do not matter right now (all functions above return nothing is pressed anyway).
return false, nil
}
func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID { func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID {
i.ui.m.RLock() i.ui.m.RLock()
defer i.ui.m.RUnlock() defer i.ui.m.RUnlock()