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
This commit is contained in:
divVerent 2023-03-03 09:29:04 -05:00 committed by GitHub
parent 051b0c7238
commit 809ad991c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 71 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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))

View File

@ -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.
// 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
const threshold = 30.0 / 255.0
// 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: