internal/gamepad: port the implementation for Android

This commit is contained in:
Hajime Hoshi 2022-02-04 15:47:22 +09:00
parent ee1b5e2044
commit d0e8efca33
9 changed files with 547 additions and 294 deletions

View File

@ -0,0 +1,167 @@
// Copyright 2022 The Ebiten 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.
package gamepad
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
)
type AndroidHatDirection int
const (
AndroidHatDirectionX AndroidHatDirection = iota
AndroidHatDirectionY
)
func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, buttonCount, hatCount int) {
theGamepads.addAndroidGamepad(androidDeviceID, name, sdlID, axisCount, buttonCount, hatCount)
}
func RemoveAndroidGamepad(androidDeviceID int) {
theGamepads.removeAndroidGamepad(androidDeviceID)
}
func UpdateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
theGamepads.updateAndroidGamepadAxis(androidDeviceID, axis, value)
}
func UpdateAndroidGamepadButton(androidDeviceID int, button driver.GamepadButton, pressed bool) {
theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed)
}
func UpdateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) {
theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, dir, value)
}
func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, buttonCount, hatCount int) {
g.m.Lock()
defer g.m.Unlock()
gp := g.add(name, sdlID)
gp.androidDeviceID = androidDeviceID
gp.axisCount_ = axisCount
gp.buttonCount_ = buttonCount
gp.hatCount_ = hatCount
gp.axes = make([]float64, axisCount)
gp.buttons = make([]bool, buttonCount)
gp.hats = make([]int, hatCount)
}
func (g *gamepads) removeAndroidGamepad(androidDeviceID int) {
g.m.Lock()
defer g.m.Unlock()
g.remove(func(gamepad *Gamepad) bool {
return gamepad.androidDeviceID == androidDeviceID
})
}
func (g *gamepads) updateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadAxis(axis, value)
}
func (g *gamepads) updateAndroidGamepadButton(androidDeviceID int, button driver.GamepadButton, pressed bool) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadButton(button, pressed)
}
func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadHat(hat, dir, value)
}
func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
g.m.Lock()
defer g.m.Unlock()
if axis < 0 || axis >= g.axisCount_ {
return
}
g.axes[axis] = value
}
func (g *Gamepad) updateAndroidGamepadButton(button driver.GamepadButton, pressed bool) {
g.m.Lock()
defer g.m.Unlock()
if button < 0 || int(button) >= g.buttonCount_ {
return
}
g.buttons[button] = pressed
}
func (g *Gamepad) updateAndroidGamepadHat(hat int, dir AndroidHatDirection, value int) {
g.m.Lock()
defer g.m.Unlock()
if hat < 0 || hat >= g.hatCount_ {
return
}
v := g.hats[hat]
switch dir {
case AndroidHatDirectionX:
switch {
case value < 0:
v |= hatLeft
v &^= hatRight
case value > 0:
v &^= hatLeft
v |= hatRight
default:
v &^= (hatLeft | hatRight)
}
case AndroidHatDirectionY:
switch {
case value < 0:
v |= hatUp
v &^= hatDown
case value > 0:
v &^= hatUp
v |= hatDown
default:
v &^= (hatUp | hatDown)
}
default:
panic(fmt.Sprintf("gamepad: invalid direction: %d", dir))
}
g.hats[hat] = v
}

View File

@ -0,0 +1,91 @@
// Copyright 2022 The Ebiten 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.
package gamepad
import (
"time"
)
type nativeGamepads struct{}
func (*nativeGamepads) init(gamepads *gamepads) error {
return nil
}
func (*nativeGamepads) update(gamepads *gamepads) error {
return nil
}
type nativeGamepad struct {
androidDeviceID int
axisCount_ int
buttonCount_ int
hatCount_ int
axes []float64
buttons []bool
hats []int
}
func (*nativeGamepad) update(gamepad *gamepads) error {
// Do nothing. The state of gamepads are given via APIs in extern_android.go.
return nil
}
func (*nativeGamepad) hasOwnStandardLayoutMapping() bool {
return false
}
func (g *nativeGamepad) axisCount() int {
return g.axisCount_
}
func (g *nativeGamepad) buttonCount() int {
return g.buttonCount_
}
func (g *nativeGamepad) hatCount() int {
return g.hatCount_
}
func (g *nativeGamepad) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) {
return 0
}
return g.axes[axis]
}
func (g *nativeGamepad) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttons) {
return false
}
return g.buttons[button]
}
func (*nativeGamepad) buttonValue(button int) float64 {
panic("gamepad: buttonValue is not implemented")
}
func (g *nativeGamepad) hatState(hat int) int {
if hat < 0 || hat >= len(g.hats) {
return 0
}
return g.hats[hat]
}
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build (!darwin || ios) && !js && (!linux || android) && !windows //go:build (!darwin || ios) && !js && !linux && !windows
// +build !darwin ios // +build !darwin ios
// +build !js // +build !js
// +build !linux android // +build !linux
// +build !windows // +build !windows
package gamepad package gamepad

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build android || ios //go:build android
// +build android ios // +build android
package mobile package mobile
@ -21,186 +21,104 @@ import (
"time" "time"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" "github.com/hajimehoshi/ebiten/v2/internal/gamepad"
) )
type Gamepad struct {
ID driver.GamepadID
SDLID string
Name string
Buttons [driver.GamepadButtonNum]bool
ButtonNum int
Axes [32]float32
AxisNum int
Hats [16]int
HatNum int
}
type gamepadState struct {
g *Gamepad
}
func (s gamepadState) Axis(index int) float64 {
return float64(s.g.Axes[index])
}
func (s gamepadState) Button(index int) bool {
return s.g.Buttons[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.Hats[index]
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID { func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
i.ui.m.RLock() return gamepad.AppendGamepadIDs(gamepadIDs)
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
gamepadIDs = append(gamepadIDs, g.ID)
}
return gamepadIDs
} }
func (i *Input) GamepadSDLID(id driver.GamepadID) string { func (i *Input) GamepadSDLID(id driver.GamepadID) string {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.SDLID
}
return "" return ""
}
return g.SDLID()
} }
func (i *Input) GamepadName(id driver.GamepadID) string { func (i *Input) GamepadName(id driver.GamepadID) string {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if name := gamepaddb.Name(g.SDLID); name != "" {
return name
}
return g.Name
}
return "" return ""
}
return g.Name()
} }
func (i *Input) GamepadAxisNum(id driver.GamepadID) int { func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.AxisNum
}
return 0 return 0
}
return g.AxisCount()
} }
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 { func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.AxisNum <= int(axis) {
return 0 return 0
} }
return float64(g.Axes[axis]) return g.Axis(axis)
}
return 0
} }
func (i *Input) GamepadButtonNum(id driver.GamepadID) int { func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.ButtonNum
}
return 0 return 0
}
return g.ButtonCount()
} }
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool { func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.ButtonNum <= int(button) {
return false return false
} }
return g.Buttons[button] return g.Button(int(button))
}
return false
} }
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool { func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.HasStandardLayoutMapping(g.SDLID)
}
return false return false
}
return g.IsStandardLayoutAvailable()
} }
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool { func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.IsButtonPressed(g.SDLID, button, gamepadState{&g})
}
return false return false
}
return g.IsStandardButtonPressed(button)
} }
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 { func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.ButtonValue(g.SDLID, button, gamepadState{&g})
}
return 0 return 0
}
return g.StandardButtonValue(button)
} }
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 { func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
i.ui.m.RLock() g := gamepad.Get(id)
defer i.ui.m.RUnlock() if g == nil {
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.AxisValue(g.SDLID, axis, gamepadState{&g})
}
return 0 return 0
}
return g.StandardAxisValue(axis)
} }
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) { func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452) g := gamepad.Get(id)
if g == nil {
return
}
g.Vibrate(duration, strongMagnitude, weakMagnitude)
}
// TODO: Remove this struct after porting the gamepad part of iOS to internal/gamepad.
type Gamepad struct{}
// TODO: Remove this function after porting the gamepad part of iOS to internal/gamepad.
func (i *Input) updateGamepads() {
} }

View File

@ -1,34 +0,0 @@
// Copyright 2021 The Ebiten 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.
package mobile
import (
"github.com/hajimehoshi/ebiten/v2/internal/driver"
)
// UpdateGamepads updates the gamepad states.
// UpdateGamepads is called when the gamepad states are given from outside like Android.
func (u *UserInterface) UpdateGamepads(gamepads []Gamepad) {
u.input.updateGamepadsFromOutside(gamepads)
}
func (i *Input) updateGamepadsFromOutside(gamepads []Gamepad) {
i.gamepads = i.gamepads[:0]
i.gamepads = append(i.gamepads, gamepads...)
}
func (i *Input) updateGamepads() {
// Do nothing.
}

View File

@ -25,8 +25,10 @@ type Input struct {
keys map[driver.Key]struct{} keys map[driver.Key]struct{}
runes []rune runes []rune
touches []Touch touches []Touch
gamepads []Gamepad
ui *UserInterface ui *UserInterface
// gamepads is used only on iOS.
gamepads []Gamepad
} }
func (i *Input) CursorPosition() (x, y int) { func (i *Input) CursorPosition() (x, y int) {

View File

@ -0,0 +1,206 @@
// Copyright 2022 The Ebiten 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.
//go:build ios
// +build ios
package mobile
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type Gamepad struct {
ID driver.GamepadID
SDLID string
Name string
Buttons [driver.GamepadButtonNum]bool
ButtonNum int
Axes [32]float32
AxisNum int
Hats [16]int
HatNum int
}
type gamepadState struct {
g *Gamepad
}
func (s gamepadState) Axis(index int) float64 {
return float64(s.g.Axes[index])
}
func (s gamepadState) Button(index int) bool {
return s.g.Buttons[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.Hats[index]
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
gamepadIDs = append(gamepadIDs, g.ID)
}
return gamepadIDs
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.SDLID
}
return ""
}
func (i *Input) GamepadName(id driver.GamepadID) string {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if name := gamepaddb.Name(g.SDLID); name != "" {
return name
}
return g.Name
}
return ""
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.AxisNum
}
return 0
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.AxisNum <= int(axis) {
return 0
}
return float64(g.Axes[axis])
}
return 0
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.ButtonNum
}
return 0
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.ButtonNum <= int(button) {
return false
}
return g.Buttons[button]
}
return false
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.HasStandardLayoutMapping(g.SDLID)
}
return false
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.IsButtonPressed(g.SDLID, button, gamepadState{&g})
}
return false
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.ButtonValue(g.SDLID, button, gamepadState{&g})
}
return 0
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.AxisValue(g.SDLID, axis, gamepadState{&g})
}
return 0
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -34,6 +34,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/devicescale" "github.com/hajimehoshi/ebiten/v2/internal/devicescale"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
"github.com/hajimehoshi/ebiten/v2/internal/hooks" "github.com/hajimehoshi/ebiten/v2/internal/hooks"
@ -83,7 +84,9 @@ func (u *UserInterface) Update() error {
return nil return nil
} }
// TODO: Remove this call after porting the gamepad part of iOS to internal/gamepad.
u.input.updateGamepads() u.input.updateGamepads()
gamepad.Update()
renderCh <- struct{}{} renderCh <- struct{}{}
go func() { go func() {

View File

@ -21,7 +21,7 @@ import (
"unicode" "unicode"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/uidriver/mobile" "github.com/hajimehoshi/ebiten/v2/internal/gamepad"
) )
// https://developer.android.com/reference/android/view/KeyEvent // https://developer.android.com/reference/android/view/KeyEvent
@ -180,30 +180,6 @@ var androidAxisIDToHatID2 = map[int]int{
axisHatY: 1, axisHatY: 1,
} }
var (
// deviceIDToGamepadID is a map from Android device IDs to Ebiten gamepad IDs.
// As convention, Ebiten gamepad IDs start with 0, and many applications depend on this fact.
deviceIDToGamepadID = map[int]driver.GamepadID{}
)
func gamepadIDFromDeviceID(deviceID int) driver.GamepadID {
if id, ok := deviceIDToGamepadID[deviceID]; ok {
return id
}
ids := map[driver.GamepadID]struct{}{}
for _, id := range deviceIDToGamepadID {
ids[id] = struct{}{}
}
for i := driver.GamepadID(0); ; i++ {
if _, ok := ids[i]; ok {
continue
}
deviceIDToGamepadID[deviceID] = i
return i
}
panic("ebitenmobileview: a gamepad ID cannot be determined")
}
func UpdateTouchesOnAndroid(action int, id int, x, y int) { func UpdateTouchesOnAndroid(action int, id int, x, y int) {
switch action { switch action {
case 0x00, 0x05, 0x02: // ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE case 0x00, 0x05, 0x02: // ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE
@ -215,27 +191,12 @@ func UpdateTouchesOnAndroid(action int, id int, x, y int) {
} }
} }
func gamepadFromGamepadID(id driver.GamepadID) *mobile.Gamepad {
for i, g := range gamepads {
if g.ID == id {
return &gamepads[i]
}
}
return nil
}
func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int) { func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int) {
switch { switch {
case source&sourceGamepad == sourceGamepad: case source&sourceGamepad == sourceGamepad:
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first. // A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
if button, ok := androidKeyToGamepadButton[keyCode]; ok { if button, ok := androidKeyToGamepadButton[keyCode]; ok {
id := gamepadIDFromDeviceID(deviceID) gamepad.UpdateAndroidGamepadButton(deviceID, button, true)
g := gamepadFromGamepadID(id)
if g == nil {
return
}
g.Buttons[button] = true
updateGamepads()
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them. // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
@ -255,13 +216,7 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
case source&sourceGamepad == sourceGamepad: case source&sourceGamepad == sourceGamepad:
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first. // A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
if button, ok := androidKeyToGamepadButton[keyCode]; ok { if button, ok := androidKeyToGamepadButton[keyCode]; ok {
id := gamepadIDFromDeviceID(deviceID) gamepad.UpdateAndroidGamepadButton(deviceID, button, false)
g := gamepadFromGamepadID(id)
if g == nil {
return
}
g.Buttons[button] = false
updateGamepads()
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them. // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
@ -274,15 +229,8 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
} }
func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) { func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
id := gamepadIDFromDeviceID(deviceID) if axis, ok := androidAxisIDToAxisID[axisID]; ok {
g := gamepadFromGamepadID(id) gamepad.UpdateAndroidGamepadAxis(deviceID, axis, float64(value))
if g == nil {
return
}
if aid, ok := androidAxisIDToAxisID[axisID]; ok {
g.Axes[aid] = value
updateGamepads()
return return
} }
@ -293,38 +241,20 @@ func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
hatDown = 4 hatDown = 4
hatLeft = 8 hatLeft = 8
) )
hid := hid2 / 2 hatID := hid2 / 2
v := g.Hats[hid] var dir gamepad.AndroidHatDirection
if hid2%2 == 0 { switch hid2 % 2 {
hatX := int(math.Round(float64(value))) case 0:
if hatX < 0 { dir = gamepad.AndroidHatDirectionX
v |= hatLeft case 1:
v &^= hatRight dir = gamepad.AndroidHatDirectionY
} else if hatX > 0 {
v &^= hatLeft
v |= hatRight
} else {
v &^= (hatLeft | hatRight)
} }
} else { gamepad.UpdateAndroidGamepadHat(deviceID, hatID, dir, int(math.Round(float64(value))))
hatY := int(math.Round(float64(value)))
if hatY < 0 {
v |= hatUp
v &^= hatDown
} else if hatY > 0 {
v &^= hatUp
v |= hatDown
} else {
v &^= (hatUp | hatDown)
}
}
g.Hats[hid] = v
updateGamepads()
return return
} }
} }
func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, hatNum int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) { func OnGamepadAdded(deviceID int, name string, buttonCount int, axisCount int, hatCount int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) {
// This emulates the implementation of Android_AddJoystick. // This emulates the implementation of Android_AddJoystick.
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/android/SDL_sysjoystick.c#L386 // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/android/SDL_sysjoystick.c#L386
const SDL_HARDWARE_BUS_BLUETOOTH = 0x05 const SDL_HARDWARE_BUS_BLUETOOTH = 0x05
@ -350,39 +280,9 @@ func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, hatNu
sdlid[14] = byte(axisMask) sdlid[14] = byte(axisMask)
sdlid[15] = byte(axisMask >> 8) sdlid[15] = byte(axisMask >> 8)
id := gamepadIDFromDeviceID(deviceID) gamepad.AddAndroidGamepad(deviceID, name, hex.EncodeToString(sdlid[:]), axisCount, buttonCount, hatCount)
gamepads = append(gamepads, mobile.Gamepad{
ID: id,
SDLID: hex.EncodeToString(sdlid[:]),
Name: name,
ButtonNum: buttonNum,
AxisNum: axisNum,
HatNum: hatNum,
})
updateGamepads()
} }
func OnInputDeviceRemoved(deviceID int) { func OnInputDeviceRemoved(deviceID int) {
if id, ok := deviceIDToGamepadID[deviceID]; ok { gamepad.RemoveAndroidGamepad(deviceID)
idx := -1
for i, g := range gamepads {
if g.ID == id {
idx = i
break
}
}
if idx >= 0 {
lastIdx := len(gamepads) - 1
gamepads[idx], gamepads[lastIdx] = gamepads[lastIdx], gamepads[idx]
gamepads = gamepads[:len(gamepads)-1]
}
delete(deviceIDToGamepadID, deviceID)
}
updateGamepads()
}
var gamepads []mobile.Gamepad
func updateGamepads() {
mobile.Get().UpdateGamepads(gamepads)
} }