mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
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:
parent
051b0c7238
commit
809ad991c2
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user