From d0e8efca33c5244d85dbc7ea826425f513a4e5c5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 4 Feb 2022 15:47:22 +0900 Subject: [PATCH] internal/gamepad: port the implementation for Android --- internal/gamepad/extern_android.go | 167 +++++++++++++++ internal/gamepad/gamepad_android.go | 91 ++++++++ internal/gamepad/gamepad_null.go | 4 +- internal/uidriver/mobile/gamepad.go | 194 +++++------------ internal/uidriver/mobile/gamepad_android.go | 34 --- internal/uidriver/mobile/input.go | 10 +- internal/uidriver/mobile/inputgamepad_ios.go | 206 +++++++++++++++++++ internal/uidriver/mobile/ui.go | 3 + mobile/ebitenmobileview/input_android.go | 132 ++---------- 9 files changed, 547 insertions(+), 294 deletions(-) create mode 100644 internal/gamepad/extern_android.go create mode 100644 internal/gamepad/gamepad_android.go delete mode 100644 internal/uidriver/mobile/gamepad_android.go create mode 100644 internal/uidriver/mobile/inputgamepad_ios.go diff --git a/internal/gamepad/extern_android.go b/internal/gamepad/extern_android.go new file mode 100644 index 000000000..289e083bd --- /dev/null +++ b/internal/gamepad/extern_android.go @@ -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 +} diff --git a/internal/gamepad/gamepad_android.go b/internal/gamepad/gamepad_android.go new file mode 100644 index 000000000..b3d9b901b --- /dev/null +++ b/internal/gamepad/gamepad_android.go @@ -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) +} diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index 2324cd57c..eab84e239 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build (!darwin || ios) && !js && (!linux || android) && !windows +//go:build (!darwin || ios) && !js && !linux && !windows // +build !darwin ios // +build !js -// +build !linux android +// +build !linux // +build !windows package gamepad diff --git a/internal/uidriver/mobile/gamepad.go b/internal/uidriver/mobile/gamepad.go index 3fdd19d66..3bffdf77f 100644 --- a/internal/uidriver/mobile/gamepad.go +++ b/internal/uidriver/mobile/gamepad.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build android || ios -// +build android ios +//go:build android +// +build android package mobile @@ -21,186 +21,104 @@ import ( "time" "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 { - i.ui.m.RLock() - defer i.ui.m.RUnlock() - - for _, g := range i.gamepads { - gamepadIDs = append(gamepadIDs, g.ID) - } - return gamepadIDs + return gamepad.AppendGamepadIDs(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 + g := gamepad.Get(id) + if g == nil { + return "" } - return "" + return g.SDLID() } 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 + g := gamepad.Get(id) + if g == nil { + return "" } - return "" + return g.Name() } 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 + g := gamepad.Get(id) + if g == nil { + return 0 } - return 0 + return g.AxisCount() } 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]) + g := gamepad.Get(id) + if g == nil { + return 0 } - return 0 + return g.Axis(axis) } 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 + g := gamepad.Get(id) + if g == nil { + return 0 } - return 0 + return g.ButtonCount() } 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] + g := gamepad.Get(id) + if g == nil { + return false } - return false + return g.Button(int(button)) } 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) + g := gamepad.Get(id) + if g == nil { + return false } - return false + return g.IsStandardLayoutAvailable() } 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}) + g := gamepad.Get(id) + if g == nil { + return false } - return false + return g.IsStandardButtonPressed(button) } 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}) + g := gamepad.Get(id) + if g == nil { + return 0 } - return 0 + return g.StandardButtonValue(button) } 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}) + g := gamepad.Get(id) + if g == nil { + return 0 } - return 0 + return g.StandardAxisValue(axis) } 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() { } diff --git a/internal/uidriver/mobile/gamepad_android.go b/internal/uidriver/mobile/gamepad_android.go deleted file mode 100644 index b6de1b7a9..000000000 --- a/internal/uidriver/mobile/gamepad_android.go +++ /dev/null @@ -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. -} diff --git a/internal/uidriver/mobile/input.go b/internal/uidriver/mobile/input.go index e37d17581..96158c60e 100644 --- a/internal/uidriver/mobile/input.go +++ b/internal/uidriver/mobile/input.go @@ -22,11 +22,13 @@ import ( ) type Input struct { - keys map[driver.Key]struct{} - runes []rune - touches []Touch + keys map[driver.Key]struct{} + runes []rune + touches []Touch + ui *UserInterface + + // gamepads is used only on iOS. gamepads []Gamepad - ui *UserInterface } func (i *Input) CursorPosition() (x, y int) { diff --git a/internal/uidriver/mobile/inputgamepad_ios.go b/internal/uidriver/mobile/inputgamepad_ios.go new file mode 100644 index 000000000..dea8b0a1f --- /dev/null +++ b/internal/uidriver/mobile/inputgamepad_ios.go @@ -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) +} diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index a9fe5ac9b..23aa28f32 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -34,6 +34,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/devicescale" "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/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/hooks" @@ -83,7 +84,9 @@ func (u *UserInterface) Update() error { return nil } + // TODO: Remove this call after porting the gamepad part of iOS to internal/gamepad. u.input.updateGamepads() + gamepad.Update() renderCh <- struct{}{} go func() { diff --git a/mobile/ebitenmobileview/input_android.go b/mobile/ebitenmobileview/input_android.go index ac344d0e0..ef1c18559 100644 --- a/mobile/ebitenmobileview/input_android.go +++ b/mobile/ebitenmobileview/input_android.go @@ -21,7 +21,7 @@ import ( "unicode" "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 @@ -180,30 +180,6 @@ var androidAxisIDToHatID2 = map[int]int{ 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) { switch action { 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) { switch { case source&sourceGamepad == sourceGamepad: // A gamepad can be detected as a keyboard. Detect the device as a gamepad first. if button, ok := androidKeyToGamepadButton[keyCode]; ok { - id := gamepadIDFromDeviceID(deviceID) - g := gamepadFromGamepadID(id) - if g == nil { - return - } - g.Buttons[button] = true - updateGamepads() + gamepad.UpdateAndroidGamepadButton(deviceID, button, true) } case source&sourceJoystick == sourceJoystick: // 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: // A gamepad can be detected as a keyboard. Detect the device as a gamepad first. if button, ok := androidKeyToGamepadButton[keyCode]; ok { - id := gamepadIDFromDeviceID(deviceID) - g := gamepadFromGamepadID(id) - if g == nil { - return - } - g.Buttons[button] = false - updateGamepads() + gamepad.UpdateAndroidGamepadButton(deviceID, button, false) } case source&sourceJoystick == sourceJoystick: // 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) { - id := gamepadIDFromDeviceID(deviceID) - g := gamepadFromGamepadID(id) - if g == nil { - return - } - - if aid, ok := androidAxisIDToAxisID[axisID]; ok { - g.Axes[aid] = value - updateGamepads() + if axis, ok := androidAxisIDToAxisID[axisID]; ok { + gamepad.UpdateAndroidGamepadAxis(deviceID, axis, float64(value)) return } @@ -293,38 +241,20 @@ func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) { hatDown = 4 hatLeft = 8 ) - hid := hid2 / 2 - v := g.Hats[hid] - if hid2%2 == 0 { - hatX := int(math.Round(float64(value))) - if hatX < 0 { - v |= hatLeft - v &^= hatRight - } else if hatX > 0 { - v &^= hatLeft - v |= hatRight - } else { - v &^= (hatLeft | hatRight) - } - } else { - 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) - } + hatID := hid2 / 2 + var dir gamepad.AndroidHatDirection + switch hid2 % 2 { + case 0: + dir = gamepad.AndroidHatDirectionX + case 1: + dir = gamepad.AndroidHatDirectionY } - g.Hats[hid] = v - updateGamepads() + gamepad.UpdateAndroidGamepadHat(deviceID, hatID, dir, int(math.Round(float64(value)))) 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. // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/android/SDL_sysjoystick.c#L386 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[15] = byte(axisMask >> 8) - id := gamepadIDFromDeviceID(deviceID) - gamepads = append(gamepads, mobile.Gamepad{ - ID: id, - SDLID: hex.EncodeToString(sdlid[:]), - Name: name, - ButtonNum: buttonNum, - AxisNum: axisNum, - HatNum: hatNum, - }) - updateGamepads() + gamepad.AddAndroidGamepad(deviceID, name, hex.EncodeToString(sdlid[:]), axisCount, buttonCount, hatCount) } func OnInputDeviceRemoved(deviceID int) { - if id, ok := deviceIDToGamepadID[deviceID]; ok { - 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) + gamepad.RemoveAndroidGamepad(deviceID) }