diff --git a/examples/gamepad/main.go b/examples/gamepad/main.go index b22271276..e238eb516 100644 --- a/examples/gamepad/main.go +++ b/examples/gamepad/main.go @@ -154,13 +154,17 @@ func standardMap(id ebiten.GamepadID) string { for b, str := range standardButtonToString { placeholder := "[" + str + strings.Repeat(" ", 4-len(str)) + "]" v := ebiten.StandardGamepadButtonValue(id, b) - if ebiten.IsStandardGamepadButtonPressed(id, b) { + switch { + case !ebiten.IsStandardGamepadButtonAvailable(id, b): + m = strings.Replace(m, placeholder, " -- ", 1) + case ebiten.IsStandardGamepadButtonPressed(id, b): m = strings.Replace(m, placeholder, fmt.Sprintf("[%0.2f]", v), 1) - } else { + default: m = strings.Replace(m, placeholder, fmt.Sprintf(" %0.2f ", v), 1) } } + // TODO: Use ebiten.IsStandardGamepadAxisAvailable m += fmt.Sprintf(" Left Stick: X: %+0.2f, Y: %+0.2f\n Right Stick: X: %+0.2f, Y: %+0.2f", ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisLeftStickHorizontal), ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisLeftStickVertical), diff --git a/input.go b/input.go index 2aa61750e..e21f3eaa2 100644 --- a/input.go +++ b/input.go @@ -303,6 +303,28 @@ func IsStandardGamepadLayoutAvailable(id GamepadID) bool { return g.IsStandardLayoutAvailable() } +// IsStandardGamepadAxisAvailable reports whether the standard gamepad axis is available on the gamepad (id). +// +// IsStandardGamepadAxisAvailable is concurrent-safe. +func IsStandardGamepadAxisAvailable(id GamepadID, axis StandardGamepadAxis) bool { + g := gamepad.Get(id) + if g == nil { + return false + } + return g.IsStandardAxisAvailable(axis) +} + +// IsStandardGamepadButtonAvailable reports whether the standard gamepad button is available on the gamepad (id). +// +// IsStandardGamepadButtonAvailable is concurrent-safe. +func IsStandardGamepadButtonAvailable(id GamepadID, button StandardGamepadButton) bool { + g := gamepad.Get(id) + if g == nil { + return false + } + return g.IsStandardButtonAvailable(button) +} + // UpdateStandardGamepadLayoutMappings parses the specified string mappings in SDL_GameControllerDB format and // updates the gamepad layout definitions. // diff --git a/internal/gamepad/gamepad.go b/internal/gamepad/gamepad.go index f71d3c30e..fb1f15125 100644 --- a/internal/gamepad/gamepad.go +++ b/internal/gamepad/gamepad.go @@ -190,6 +190,8 @@ type Gamepad struct { type nativeGamepad interface { update(gamepads *gamepads) error hasOwnStandardLayoutMapping() bool + isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool + isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool axisCount() int buttonCount() int hatCount() int @@ -281,6 +283,28 @@ func (g *Gamepad) IsStandardLayoutAvailable() bool { return g.native.hasOwnStandardLayoutMapping() } +// IsStandardAxisAvailable is concurrent safe. +func (g *Gamepad) IsStandardAxisAvailable(button gamepaddb.StandardAxis) bool { + g.m.Lock() + defer g.m.Unlock() + + if gamepaddb.HasStandardLayoutMapping(g.sdlID) { + return gamepaddb.HasStandardAxis(g.sdlID, button) + } + return g.native.isStandardAxisAvailableInOwnMapping(button) +} + +// IsStandardButtonAvailable is concurrent safe. +func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) bool { + g.m.Lock() + defer g.m.Unlock() + + if gamepaddb.HasStandardLayoutMapping(g.sdlID) { + return gamepaddb.HasStandardButton(g.sdlID, button) + } + return g.native.isStandardButtonAvailableInOwnMapping(button) +} + // StandardAxisValue is concurrent-safe. func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 { if gamepaddb.HasStandardLayoutMapping(g.sdlID) { diff --git a/internal/gamepad/gamepad_android.go b/internal/gamepad/gamepad_android.go index e109fe70b..251bab1c2 100644 --- a/internal/gamepad/gamepad_android.go +++ b/internal/gamepad/gamepad_android.go @@ -19,6 +19,8 @@ package gamepad import ( "time" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) type nativeGamepadsImpl struct{} @@ -52,6 +54,14 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } +func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (g *nativeGamepadImpl) axisCount() int { return len(g.axes) } diff --git a/internal/gamepad/gamepad_cbackend.go b/internal/gamepad/gamepad_cbackend.go index b6a9bd215..7680adc32 100644 --- a/internal/gamepad/gamepad_cbackend.go +++ b/internal/gamepad/gamepad_cbackend.go @@ -21,6 +21,7 @@ import ( "time" "github.com/hajimehoshi/ebiten/v2/internal/cbackend" + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) type nativeGamepadsImpl struct { @@ -98,6 +99,16 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return g.standard } +func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + // TODO: Implement this on the C side. + return axis >= 0 && int(axis) < len(g.axisValues) +} + +func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + // TODO: Implement this on the C side. + return button >= 0 && int(button) < len(g.buttonValues) +} + func (g *nativeGamepadImpl) axisCount() int { return len(g.axisValues) } diff --git a/internal/gamepad/gamepad_darwin.go b/internal/gamepad/gamepad_darwin.go index 85768ac75..a3018905e 100644 --- a/internal/gamepad/gamepad_darwin.go +++ b/internal/gamepad/gamepad_darwin.go @@ -24,6 +24,8 @@ import ( "sync" "time" "unsafe" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) // #cgo LDFLAGS: -framework CoreFoundation -framework IOKit @@ -406,6 +408,14 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } +func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (g *nativeGamepadImpl) axisCount() int { return len(g.axisValues) } diff --git a/internal/gamepad/gamepad_desktop_windows.go b/internal/gamepad/gamepad_desktop_windows.go index c00548a34..5559436cf 100644 --- a/internal/gamepad/gamepad_desktop_windows.go +++ b/internal/gamepad/gamepad_desktop_windows.go @@ -28,6 +28,8 @@ import ( "unsafe" "golang.org/x/sys/windows" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) type dinputObjectType int @@ -571,6 +573,14 @@ func (*nativeGamepadDesktop) hasOwnStandardLayoutMapping() bool { return false } +func (*nativeGamepadDesktop) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (*nativeGamepadDesktop) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (g *nativeGamepadDesktop) usesDInput() bool { return g.dinputDevice != nil } diff --git a/internal/gamepad/gamepad_ios.go b/internal/gamepad/gamepad_ios.go index 49c2e8bae..a66857b08 100644 --- a/internal/gamepad/gamepad_ios.go +++ b/internal/gamepad/gamepad_ios.go @@ -19,6 +19,8 @@ package gamepad import ( "time" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) type nativeGamepadsImpl struct{} @@ -57,6 +59,14 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } +func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (g *nativeGamepadImpl) axisCount() int { return len(g.axes) } diff --git a/internal/gamepad/gamepad_js.go b/internal/gamepad/gamepad_js.go index 2ec341314..0c26e310a 100644 --- a/internal/gamepad/gamepad_js.go +++ b/internal/gamepad/gamepad_js.go @@ -18,6 +18,8 @@ import ( "encoding/hex" "syscall/js" "time" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) var ( @@ -114,6 +116,20 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return g.mapping == "standard" } +func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + if !g.hasOwnStandardLayoutMapping() { + return false + } + return axis >= 0 && int(axis) < g.axisCount() +} + +func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + if !g.hasOwnStandardLayoutMapping() { + return false + } + return button >= 0 && int(button) < g.buttonCount() +} + func (g *nativeGamepadImpl) update(gamepads *gamepads) error { return nil } diff --git a/internal/gamepad/gamepad_linux.go b/internal/gamepad/gamepad_linux.go index 603a4f233..c9bc6b0ad 100644 --- a/internal/gamepad/gamepad_linux.go +++ b/internal/gamepad/gamepad_linux.go @@ -27,6 +27,8 @@ import ( "unsafe" "golang.org/x/sys/unix" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) const dirName = "/dev/input" @@ -410,6 +412,14 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } +func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (g *nativeGamepadImpl) axisCount() int { return g.axisCount_ } diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index 39b285727..56cbe2886 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -19,6 +19,8 @@ package gamepad import ( "time" + + "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" ) type nativeGamepadsImpl struct{} @@ -45,6 +47,14 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } +func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + return false +} + +func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + return false +} + func (*nativeGamepadImpl) axisCount() int { return 0 } diff --git a/internal/gamepad/gamepad_xbox_windows.go b/internal/gamepad/gamepad_xbox_windows.go index b1a8f3e57..88f474441 100644 --- a/internal/gamepad/gamepad_xbox_windows.go +++ b/internal/gamepad/gamepad_xbox_windows.go @@ -158,6 +158,27 @@ func (n *nativeGamepadXbox) hasOwnStandardLayoutMapping() bool { return true } +func (n *nativeGamepadXbox) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { + switch gamepaddb.StandardAxis(axis) { + case gamepaddb.StandardAxisLeftStickHorizontal, + gamepaddb.StandardAxisLeftStickVertical, + gamepaddb.StandardAxisRightStickHorizontal, + gamepaddb.StandardAxisRightStickVertical: + return true + } + return false +} + +func (n *nativeGamepadXbox) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { + switch gamepaddb.StandardButton(button) { + case gamepaddb.StandardButtonFrontBottomLeft, + gamepaddb.StandardButtonFrontBottomRight: + return true + } + _, ok := standardButtonToGamepadInputGamepadButton(button) + return ok +} + func (n *nativeGamepadXbox) axisCount() int { return int(gamepaddb.StandardAxisMax) + 1 } diff --git a/internal/gamepaddb/gamepaddb.go b/internal/gamepaddb/gamepaddb.go index a170184e3..bda1f926d 100644 --- a/internal/gamepaddb/gamepaddb.go +++ b/internal/gamepaddb/gamepaddb.go @@ -389,6 +389,23 @@ func Name(id string) string { return gamepadNames[id] } +func HasStandardAxis(id string, axis StandardAxis) bool { + mappingsM.RLock() + defer mappingsM.RUnlock() + + mappings := axisMappings(id) + if mappings == nil { + return false + } + + mapping := mappings[axis] + if mapping == nil { + return false + } + + return true +} + func AxisValue(id string, axis StandardAxis, state GamepadState) float64 { mappingsM.RLock() defer mappingsM.RUnlock() @@ -429,6 +446,23 @@ func AxisValue(id string, axis StandardAxis, state GamepadState) float64 { return 0 } +func HasStandardButton(id string, button StandardButton) bool { + mappingsM.RLock() + defer mappingsM.RUnlock() + + mappings := buttonMappings(id) + if mappings == nil { + return false + } + + mapping := mappings[button] + if mapping == nil { + return false + } + + return true +} + func ButtonValue(id string, button StandardButton, state GamepadState) float64 { mappingsM.RLock() defer mappingsM.RUnlock()