2022-06-24 15:18:19 +02:00
// Copyright 2022 The Ebitengine 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.
2022-07-09 08:19:47 +02:00
//go:build !ebitenginecbackend && !ebitencbackend
// +build !ebitenginecbackend,!ebitencbackend
2022-06-24 15:18:19 +02:00
package gamepad
2022-06-28 17:10:27 +02:00
import (
2022-07-01 13:37:45 +02:00
"time"
2022-06-28 17:10:27 +02:00
"unsafe"
"golang.org/x/sys/windows"
2022-07-01 14:44:46 +02:00
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
2022-06-28 17:10:27 +02:00
)
2022-07-01 14:44:46 +02:00
func standardButtonToGamepadInputGamepadButton ( b gamepaddb . StandardButton ) ( _GameInputGamepadButtons , bool ) {
switch b {
case gamepaddb . StandardButtonRightBottom :
return _GameInputGamepadA , true
case gamepaddb . StandardButtonRightRight :
return _GameInputGamepadB , true
case gamepaddb . StandardButtonRightLeft :
return _GameInputGamepadX , true
case gamepaddb . StandardButtonRightTop :
return _GameInputGamepadY , true
case gamepaddb . StandardButtonFrontTopLeft :
return _GameInputGamepadLeftShoulder , true
case gamepaddb . StandardButtonFrontTopRight :
return _GameInputGamepadRightShoulder , true
case gamepaddb . StandardButtonFrontBottomLeft :
return 0 , false // Use leftTrigger instead.
case gamepaddb . StandardButtonFrontBottomRight :
return 0 , false // Use rightTrigger instead.
case gamepaddb . StandardButtonCenterLeft :
return _GameInputGamepadView , true
case gamepaddb . StandardButtonCenterRight :
return _GameInputGamepadMenu , true
case gamepaddb . StandardButtonLeftStick :
return _GameInputGamepadLeftThumbstick , true
case gamepaddb . StandardButtonRightStick :
return _GameInputGamepadRightThumbstick , true
case gamepaddb . StandardButtonLeftTop :
return _GameInputGamepadDPadUp , true
case gamepaddb . StandardButtonLeftBottom :
return _GameInputGamepadDPadDown , true
case gamepaddb . StandardButtonLeftLeft :
return _GameInputGamepadDPadLeft , true
case gamepaddb . StandardButtonLeftRight :
return _GameInputGamepadDPadRight , true
case gamepaddb . StandardButtonCenterCenter :
return 0 , false
}
return 0 , false
}
2022-06-24 17:02:11 +02:00
type nativeGamepadsXbox struct {
2022-06-28 17:10:27 +02:00
gameInput * _IGameInput
deviceCallbackPtr uintptr
token _GameInputCallbackToken
}
2022-06-24 15:18:19 +02:00
func ( n * nativeGamepadsXbox ) init ( gamepads * gamepads ) error {
2022-06-24 17:02:11 +02:00
g , err := _GameInputCreate ( )
if err != nil {
return err
}
2022-06-28 17:10:27 +02:00
2022-06-24 17:02:11 +02:00
n . gameInput = g
2022-07-01 13:37:45 +02:00
n . deviceCallbackPtr = windows . NewCallbackCDecl ( n . deviceCallback )
2022-06-28 17:10:27 +02:00
if err := n . gameInput . RegisterDeviceCallback (
nil ,
2022-07-01 14:44:46 +02:00
_GameInputKindGamepad ,
2022-06-28 17:10:27 +02:00
_GameInputDeviceConnected ,
_GameInputBlockingEnumeration ,
2022-07-01 13:37:45 +02:00
unsafe . Pointer ( gamepads ) ,
2022-06-28 17:10:27 +02:00
n . deviceCallbackPtr ,
& n . token ,
) ; err != nil {
return err
}
2022-06-24 15:18:19 +02:00
return nil
}
func ( n * nativeGamepadsXbox ) update ( gamepads * gamepads ) error {
return nil
}
2022-07-01 13:37:45 +02:00
func ( n * nativeGamepadsXbox ) deviceCallback ( callbackToken _GameInputCallbackToken , context unsafe . Pointer , device * _IGameInputDevice , timestamp uint64 , currentStatus _GameInputDeviceStatus , previousStatus _GameInputDeviceStatus ) uintptr {
gps := ( * gamepads ) ( context )
// Connected.
if currentStatus & _GameInputDeviceConnected != 0 {
// TODO: Give a good name and a SDL ID.
gp := gps . add ( "" , "00000000000000000000000000000000" )
gp . native = & nativeGamepadXbox {
gameInputDevice : device ,
}
return 0
}
// Disconnected.
gps . remove ( func ( gamepad * Gamepad ) bool {
return gamepad . native . ( * nativeGamepadXbox ) . gameInputDevice == device
} )
return 0
}
type nativeGamepadXbox struct {
gameInputDevice * _IGameInputDevice
2022-07-01 14:44:46 +02:00
state _GameInputGamepadState
2022-07-15 14:30:35 +02:00
vib bool
vibEnd time . Time
2022-07-01 13:37:45 +02:00
}
func ( n * nativeGamepadXbox ) update ( gamepads * gamepads ) error {
2022-07-01 14:44:46 +02:00
gameInput := gamepads . native . ( * nativeGamepadsXbox ) . gameInput
r , err := gameInput . GetCurrentReading ( _GameInputKindGamepad , n . gameInputDevice )
if err != nil {
return err
}
2022-07-15 08:12:54 +02:00
defer r . Release ( )
2022-07-01 14:44:46 +02:00
state , ok := r . GetGamepadState ( )
if ! ok {
n . state = _GameInputGamepadState { }
return nil
}
n . state = state
2022-07-15 14:30:35 +02:00
if n . vib && time . Now ( ) . Sub ( n . vibEnd ) >= 0 {
n . gameInputDevice . SetRumbleState ( & _GameInputRumbleParams {
lowFrequency : 0 ,
highFrequency : 0 ,
} , 0 )
n . vib = false
}
2022-07-01 13:37:45 +02:00
return nil
}
func ( n * nativeGamepadXbox ) hasOwnStandardLayoutMapping ( ) bool {
return true
}
func ( n * nativeGamepadXbox ) axisCount ( ) int {
2022-07-01 14:44:46 +02:00
return int ( gamepaddb . StandardAxisMax ) + 1
2022-07-01 13:37:45 +02:00
}
func ( n * nativeGamepadXbox ) buttonCount ( ) int {
2022-07-01 14:44:46 +02:00
return int ( gamepaddb . StandardButtonMax ) + 1
2022-07-01 13:37:45 +02:00
}
func ( n * nativeGamepadXbox ) hatCount ( ) int {
return 0
}
func ( n * nativeGamepadXbox ) axisValue ( axis int ) float64 {
2022-07-01 14:44:46 +02:00
switch gamepaddb . StandardAxis ( axis ) {
case gamepaddb . StandardAxisLeftStickHorizontal :
return float64 ( n . state . leftThumbstickX )
case gamepaddb . StandardAxisLeftStickVertical :
2022-07-15 07:55:28 +02:00
return - float64 ( n . state . leftThumbstickY )
2022-07-01 14:44:46 +02:00
case gamepaddb . StandardAxisRightStickHorizontal :
return float64 ( n . state . rightThumbstickX )
case gamepaddb . StandardAxisRightStickVertical :
2022-07-15 07:55:28 +02:00
return - float64 ( n . state . rightThumbstickY )
2022-07-01 14:44:46 +02:00
}
2022-07-01 13:37:45 +02:00
return 0
}
func ( n * nativeGamepadXbox ) buttonValue ( button int ) float64 {
2022-07-01 14:44:46 +02:00
switch gamepaddb . StandardButton ( button ) {
case gamepaddb . StandardButtonFrontBottomLeft :
return float64 ( n . state . leftTrigger )
case gamepaddb . StandardButtonFrontBottomRight :
return float64 ( n . state . rightTrigger )
}
b , ok := standardButtonToGamepadInputGamepadButton ( gamepaddb . StandardButton ( button ) )
if ! ok {
return 0
}
if n . state . buttons & b != 0 {
return 1
}
2022-07-01 13:37:45 +02:00
return 0
}
func ( n * nativeGamepadXbox ) isButtonPressed ( button int ) bool {
2022-07-01 14:44:46 +02:00
// 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
case gamepaddb . StandardButtonFrontBottomRight :
return n . state . rightTrigger >= threshold
}
b , ok := standardButtonToGamepadInputGamepadButton ( gamepaddb . StandardButton ( button ) )
if ! ok {
return false
}
if n . state . buttons & b != 0 {
return true
}
2022-07-01 13:37:45 +02:00
return false
}
func ( n * nativeGamepadXbox ) hatState ( hat int ) int {
return 0
}
func ( n * nativeGamepadXbox ) vibrate ( duration time . Duration , strongMagnitude float64 , weakMagnitude float64 ) {
2022-07-15 14:30:35 +02:00
if strongMagnitude <= 0 && weakMagnitude <= 0 {
2022-07-15 19:44:54 +02:00
n . vib = false
2022-07-15 14:30:35 +02:00
n . gameInputDevice . SetRumbleState ( & _GameInputRumbleParams {
lowFrequency : 0 ,
highFrequency : 0 ,
} , 0 )
return
}
n . vib = true
n . vibEnd = time . Now ( ) . Add ( duration )
n . gameInputDevice . SetRumbleState ( & _GameInputRumbleParams {
lowFrequency : float32 ( strongMagnitude ) ,
highFrequency : float32 ( weakMagnitude ) ,
} , 0 )
2022-07-01 13:37:45 +02:00
}