From 809ad991c278ef43ed04270847536ad1924d8d57 Mon Sep 17 00:00:00 2001 From: divVerent Date: Fri, 3 Mar 2023 09:29:04 -0500 Subject: [PATCH] internal/gamepad: refactor standard layout support to allow remapping in the per-platform implementations. (#2588) Refactors native standard layout so standard axes and buttons can be implemented using any of axes, buttons or hats. This is required to be able to implement a native standard layout mapping for Linux, as e.g. shoulder buttons can be backed either by an analog or digital input. Precedes #2587 Updates #2052 --- internal/gamepad/gamepad.go | 68 ++++++++++++++++++--- internal/gamepad/gamepad_android.go | 15 +++-- internal/gamepad/gamepad_darwin.go | 13 ++-- internal/gamepad/gamepad_desktop_windows.go | 13 ++-- internal/gamepad/gamepad_ios.go | 15 +++-- internal/gamepad/gamepad_js.go | 18 ++++-- internal/gamepad/gamepad_linux.go | 15 +++-- internal/gamepad/gamepad_nintendosdk.go | 14 +++-- internal/gamepad/gamepad_null.go | 10 +-- internal/gamepad/gamepad_xbox_windows.go | 24 ++++---- internal/gamepaddb/gamepaddb.go | 12 ++-- 11 files changed, 146 insertions(+), 71 deletions(-) diff --git a/internal/gamepad/gamepad.go b/internal/gamepad/gamepad.go index 4cb96940a..27f503af9 100644 --- a/internal/gamepad/gamepad.go +++ b/internal/gamepad/gamepad.go @@ -187,11 +187,59 @@ type Gamepad struct { native nativeGamepad } +type mappingInput interface { + Pressed() bool + Value() float64 // Normalized to range: 0..1. +} + +type axisMappingInput struct { + g nativeGamepad + axis int +} + +func (a axisMappingInput) Pressed() bool { + return a.g.axisValue(a.axis) > gamepaddb.ButtonPressedThreshold +} + +func (a axisMappingInput) Value() float64 { + return a.g.axisValue(a.axis)*0.5 + 0.5 +} + +type buttonMappingInput struct { + g nativeGamepad + button int +} + +func (b buttonMappingInput) Pressed() bool { + return b.g.isButtonPressed(b.button) +} + +func (b buttonMappingInput) Value() float64 { + return b.g.buttonValue(b.button) +} + +type hatMappingInput struct { + g nativeGamepad + hat int + direction int +} + +func (h hatMappingInput) Pressed() bool { + return h.g.hatState(h.hat)&h.direction != 0 +} + +func (h hatMappingInput) Value() float64 { + if h.Pressed() { + return 1 + } + return 0 +} + type nativeGamepad interface { update(gamepads *gamepads) error hasOwnStandardLayoutMapping() bool - isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool - isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool + standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput + standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput axisCount() int buttonCount() int hatCount() int @@ -291,7 +339,7 @@ func (g *Gamepad) IsStandardAxisAvailable(axis gamepaddb.StandardAxis) bool { if gamepaddb.HasStandardLayoutMapping(g.sdlID) { return gamepaddb.HasStandardAxis(g.sdlID, axis) } - return g.native.isStandardAxisAvailableInOwnMapping(axis) + return g.native.standardAxisInOwnMapping(axis) != nil } // IsStandardButtonAvailable is concurrent safe. @@ -302,7 +350,7 @@ func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) boo if gamepaddb.HasStandardLayoutMapping(g.sdlID) { return gamepaddb.HasStandardButton(g.sdlID, button) } - return g.native.isStandardButtonAvailableInOwnMapping(button) + return g.native.standardButtonInOwnMapping(button) != nil } // StandardAxisValue is concurrent-safe. @@ -310,8 +358,8 @@ func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 { if gamepaddb.HasStandardLayoutMapping(g.sdlID) { return gamepaddb.AxisValue(g.sdlID, axis, g) } - if g.native.hasOwnStandardLayoutMapping() { - return g.native.axisValue(int(axis)) + if m := g.native.standardAxisInOwnMapping(axis); m != nil { + return m.Value()*2 - 1 } return 0 } @@ -321,8 +369,8 @@ func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 { if gamepaddb.HasStandardLayoutMapping(g.sdlID) { return gamepaddb.ButtonValue(g.sdlID, button, g) } - if g.native.hasOwnStandardLayoutMapping() { - return g.native.buttonValue(int(button)) + if m := g.native.standardButtonInOwnMapping(button); m != nil { + return m.Value() } return 0 } @@ -332,8 +380,8 @@ func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool if gamepaddb.HasStandardLayoutMapping(g.sdlID) { return gamepaddb.IsButtonPressed(g.sdlID, button, g) } - if g.native.hasOwnStandardLayoutMapping() { - return g.native.isButtonPressed(int(button)) + if m := g.native.standardButtonInOwnMapping(button); m != nil { + return m.Pressed() } return false } diff --git a/internal/gamepad/gamepad_android.go b/internal/gamepad/gamepad_android.go index ea01f574a..914a35194 100644 --- a/internal/gamepad/gamepad_android.go +++ b/internal/gamepad/gamepad_android.go @@ -53,12 +53,12 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } -func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (g *nativeGamepadImpl) axisCount() int { @@ -87,8 +87,11 @@ func (g *nativeGamepadImpl) isButtonPressed(button int) bool { return g.buttons[button] } -func (*nativeGamepadImpl) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") +func (g *nativeGamepadImpl) buttonValue(button int) float64 { + if g.isButtonPressed(button) { + return 1 + } + return 0 } func (g *nativeGamepadImpl) hatState(hat int) int { diff --git a/internal/gamepad/gamepad_darwin.go b/internal/gamepad/gamepad_darwin.go index fa457447a..d573032f8 100644 --- a/internal/gamepad/gamepad_darwin.go +++ b/internal/gamepad/gamepad_darwin.go @@ -372,12 +372,12 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } -func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (g *nativeGamepadImpl) axisCount() int { @@ -400,7 +400,10 @@ func (g *nativeGamepadImpl) axisValue(axis int) float64 { } func (g *nativeGamepadImpl) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") + if g.isButtonPressed(button) { + return 1 + } + return 0 } func (g *nativeGamepadImpl) isButtonPressed(button int) bool { diff --git a/internal/gamepad/gamepad_desktop_windows.go b/internal/gamepad/gamepad_desktop_windows.go index b686d1762..a32505641 100644 --- a/internal/gamepad/gamepad_desktop_windows.go +++ b/internal/gamepad/gamepad_desktop_windows.go @@ -572,12 +572,12 @@ func (*nativeGamepadDesktop) hasOwnStandardLayoutMapping() bool { return false } -func (*nativeGamepadDesktop) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (*nativeGamepadDesktop) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (*nativeGamepadDesktop) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (*nativeGamepadDesktop) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (g *nativeGamepadDesktop) usesDInput() bool { @@ -757,7 +757,10 @@ func (g *nativeGamepadDesktop) isButtonPressed(button int) bool { } func (g *nativeGamepadDesktop) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") + if g.isButtonPressed(button) { + return 1 + } + return 0 } func (g *nativeGamepadDesktop) hatState(hat int) int { diff --git a/internal/gamepad/gamepad_ios.go b/internal/gamepad/gamepad_ios.go index 2700683d9..209b1760f 100644 --- a/internal/gamepad/gamepad_ios.go +++ b/internal/gamepad/gamepad_ios.go @@ -58,12 +58,12 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } -func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (g *nativeGamepadImpl) axisCount() int { @@ -92,8 +92,11 @@ func (g *nativeGamepadImpl) isButtonPressed(button int) bool { return g.buttons[button] } -func (*nativeGamepadImpl) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") +func (g *nativeGamepadImpl) buttonValue(button int) float64 { + if g.isButtonPressed(button) { + return 1 + } + return 0 } func (g *nativeGamepadImpl) hatState(hat int) int { diff --git a/internal/gamepad/gamepad_js.go b/internal/gamepad/gamepad_js.go index 0c26e310a..203ce5e98 100644 --- a/internal/gamepad/gamepad_js.go +++ b/internal/gamepad/gamepad_js.go @@ -116,18 +116,24 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return g.mapping == "standard" } -func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { +func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { if !g.hasOwnStandardLayoutMapping() { - return false + return nil } - return axis >= 0 && int(axis) < g.axisCount() + if axis < 0 || int(axis) >= g.axisCount() { + return nil + } + return axisMappingInput{g: g, axis: int(axis)} } -func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { +func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { if !g.hasOwnStandardLayoutMapping() { - return false + return nil } - return button >= 0 && int(button) < g.buttonCount() + if button < 0 || int(button) >= g.buttonCount() { + return nil + } + return buttonMappingInput{g: g, button: int(button)} } func (g *nativeGamepadImpl) update(gamepads *gamepads) error { diff --git a/internal/gamepad/gamepad_linux.go b/internal/gamepad/gamepad_linux.go index e88164062..25c89167d 100644 --- a/internal/gamepad/gamepad_linux.go +++ b/internal/gamepad/gamepad_linux.go @@ -423,12 +423,12 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } -func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (g *nativeGamepadImpl) axisCount() int { @@ -457,8 +457,11 @@ func (g *nativeGamepadImpl) isButtonPressed(button int) bool { return g.buttons[button] } -func (*nativeGamepadImpl) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") +func (g *nativeGamepadImpl) buttonValue(button int) float64 { + if g.isButtonPressed(button) { + return 1 + } + return 0 } func (g *nativeGamepadImpl) hatState(hat int) int { diff --git a/internal/gamepad/gamepad_nintendosdk.go b/internal/gamepad/gamepad_nintendosdk.go index c4f39de31..c09ca22b7 100644 --- a/internal/gamepad/gamepad_nintendosdk.go +++ b/internal/gamepad/gamepad_nintendosdk.go @@ -118,14 +118,20 @@ func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return g.standard } -func (g *nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { +func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { // TODO: Implement this on the C side. - return axis >= 0 && int(axis) < len(g.axisValues) + if axis < 0 || int(axis) >= len(g.axisValues) { + return nil + } + return axisMappingInput{g: g, axis: int(axis)} } -func (g *nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { +func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { // TODO: Implement this on the C side. - return button >= 0 && int(button) < len(g.buttonValues) + if button < 0 || int(button) >= len(g.buttonValues) { + return nil + } + return buttonMappingInput{g: g, button: int(button)} } func (g *nativeGamepadImpl) axisCount() int { diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index 7ec164538..6ebfc7a52 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -46,12 +46,12 @@ func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool { return false } -func (*nativeGamepadImpl) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { - return false +func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { + return nil } -func (*nativeGamepadImpl) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { - return false +func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { + return nil } func (*nativeGamepadImpl) axisCount() int { @@ -75,7 +75,7 @@ func (*nativeGamepadImpl) isButtonPressed(button int) bool { } func (*nativeGamepadImpl) buttonValue(button int) float64 { - panic("gamepad: buttonValue is not implemented") + return 0 } func (*nativeGamepadImpl) hatState(hat int) int { diff --git a/internal/gamepad/gamepad_xbox_windows.go b/internal/gamepad/gamepad_xbox_windows.go index 84b3bcc24..4e210317a 100644 --- a/internal/gamepad/gamepad_xbox_windows.go +++ b/internal/gamepad/gamepad_xbox_windows.go @@ -157,25 +157,27 @@ func (n *nativeGamepadXbox) hasOwnStandardLayoutMapping() bool { return true } -func (n *nativeGamepadXbox) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { +func (n *nativeGamepadXbox) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput { switch axis { case gamepaddb.StandardAxisLeftStickHorizontal, gamepaddb.StandardAxisLeftStickVertical, gamepaddb.StandardAxisRightStickHorizontal, gamepaddb.StandardAxisRightStickVertical: - return true + return axisMappingInput{g: n, axis: int(axis)} } - return false + return nil } -func (n *nativeGamepadXbox) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { +func (n *nativeGamepadXbox) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput { switch button { case gamepaddb.StandardButtonFrontBottomLeft, gamepaddb.StandardButtonFrontBottomRight: - return true + return buttonMappingInput{g: n, button: int(button)} } - _, ok := standardButtonToGamepadInputGamepadButton(button) - return ok + if _, ok := standardButtonToGamepadInputGamepadButton(button); !ok { + return nil + } + return buttonMappingInput{g: n, button: int(button)} } func (n *nativeGamepadXbox) axisCount() int { @@ -222,15 +224,11 @@ func (n *nativeGamepadXbox) buttonValue(button int) float64 { } func (n *nativeGamepadXbox) isButtonPressed(button int) bool { - // Use XInput's trigger dead zone. - // See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242 - // TODO: Integrate this value with the same one in the package gamepaddb. - const threshold = 30.0 / 255.0 switch gamepaddb.StandardButton(button) { case gamepaddb.StandardButtonFrontBottomLeft: - return n.state.leftTrigger >= threshold + return n.state.leftTrigger > gamepaddb.ButtonPressedThreshold case gamepaddb.StandardButtonFrontBottomRight: - return n.state.rightTrigger >= threshold + return n.state.rightTrigger > gamepaddb.ButtonPressedThreshold } b, ok := standardButtonToGamepadInputGamepadButton(gamepaddb.StandardButton(button)) diff --git a/internal/gamepaddb/gamepaddb.go b/internal/gamepaddb/gamepaddb.go index 43ce1ebd1..0c9472654 100644 --- a/internal/gamepaddb/gamepaddb.go +++ b/internal/gamepaddb/gamepaddb.go @@ -519,11 +519,13 @@ func buttonValue(id string, button StandardButton, state GamepadState) float64 { return 0 } -func IsButtonPressed(id string, button StandardButton, state GamepadState) bool { - // Use XInput's trigger dead zone. - // See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242 - const threshold = 30.0 / 255.0 +// ButtonPressedThreshold represents the value up to which a button counts as not yet pressed. +// This has been set to match XInput's trigger dead zone. +// See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242 +// Note: should be used with >, not >=, comparisons. +const ButtonPressedThreshold = 30.0 / 255.0 +func IsButtonPressed(id string, button StandardButton, state GamepadState) bool { mappingsM.RLock() defer mappingsM.RUnlock() @@ -540,7 +542,7 @@ func IsButtonPressed(id string, button StandardButton, state GamepadState) bool switch mapping.Type { case mappingTypeAxis: v := buttonValue(id, button, state) - return v > threshold + return v > ButtonPressedThreshold case mappingTypeButton: return state.Button(mapping.Index) case mappingTypeHat: