mobile/ebitenmobileview: Handle keyboard keys on Android (ebitenmobile)

Updates #237
This commit is contained in:
Hajime Hoshi 2020-02-19 00:57:19 +09:00
parent 6faad68931
commit 5b7151595b
9 changed files with 268 additions and 18 deletions

View File

@ -338,6 +338,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewGroup; import android.view.ViewGroup;
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview; import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
@ -354,10 +355,6 @@ public class EbitenView extends ViewGroup {
return x / getDeviceScale(); return x / getDeviceScale();
} }
private double dpToPx(double x) {
return x * getDeviceScale();
}
private double deviceScale_ = 0.0; private double deviceScale_ = 0.0;
public EbitenView(Context context) { public EbitenView(Context context) {
@ -384,6 +381,18 @@ public class EbitenView extends ViewGroup {
Ebitenmobileview.layout(widthInDp, heightInDp); Ebitenmobileview.layout(widthInDp, heightInDp);
} }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Ebitenmobileview.onKeyDownOnAndroid(keyCode);
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Ebitenmobileview.onKeyUpOnAndroid(keyCode);
return true;
}
// suspendGame suspends the game. // suspendGame suspends the game.
// It is recommended to call this when the application is being suspended e.g., // It is recommended to call this when the application is being suspended e.g.,
// Activity's onPause is called. // Activity's onPause is called.

File diff suppressed because one or more lines are too long

View File

@ -35,6 +35,7 @@ import (
var ( var (
nameToGLFWKey map[string]glfw.Key nameToGLFWKey map[string]glfw.Key
nameToAndroidKey map[string]int
nameToJSKey map[string]string nameToJSKey map[string]string
edgeKeyCodeToName map[int]string edgeKeyCodeToName map[int]string
) )
@ -93,6 +94,56 @@ func init() {
"KPEqual": glfw.KeyKPEqual, "KPEqual": glfw.KeyKPEqual,
"Last": glfw.KeyLast, "Last": glfw.KeyLast,
} }
// https://developer.android.com/reference/android/view/KeyEvent
nameToAndroidKey = map[string]int{
"Comma": 55,
"Period": 56,
"LeftAlt": 57,
"RightAlt": 58,
"CapsLock": 115,
"LeftControl": 113,
"RightControl": 114,
"LeftShift": 59,
"RightShift": 60,
"Enter": 66,
"Space": 62,
"Tab": 61,
"Delete": 112, // KEYCODE_FORWARD_DEL
"End": 123,
"Home": 122,
"Insert": 124,
"PageDown": 93,
"PageUp": 92,
"Down": 20,
"Left": 21,
"Right": 22,
"Up": 19,
"Escape": 111,
"Backspace": 67, // KEYCODE_DEL
"Apostrophe": 75,
"Minus": 69,
"Slash": 76,
"Semicolon": 74,
"Equal": 70,
"LeftBracket": 71,
"Backslash": 73,
"RightBracket": 72,
"GraveAccent": 68,
"NumLock": 143,
"Pause": 121, // KEYCODE_BREAK
"PrintScreen": 120, // KEYCODE_SYSRQ
"ScrollLock": 116,
"Menu": 82,
"KPDecimal": 158,
"KPDivide": 154,
"KPMultiply": 155,
"KPSubtract": 156,
"KPAdd": 157,
"KPEnter": 160,
"KPEqual": 161,
}
nameToJSKey = map[string]string{ nameToJSKey = map[string]string{
"Comma": "Comma", "Comma": "Comma",
"Period": "Period", "Period": "Period",
@ -140,20 +191,24 @@ func init() {
"KPEnter": "NumpadEnter", "KPEnter": "NumpadEnter",
"KPEqual": "NumpadEqual", "KPEqual": "NumpadEqual",
} }
// ASCII: 0 - 9 // ASCII: 0 - 9
for c := '0'; c <= '9'; c++ { for c := '0'; c <= '9'; c++ {
nameToGLFWKey[string(c)] = glfw.Key0 + glfw.Key(c) - '0' nameToGLFWKey[string(c)] = glfw.Key0 + glfw.Key(c) - '0'
nameToAndroidKey[string(c)] = int(7 + c - '0')
nameToJSKey[string(c)] = "Digit" + string(c) nameToJSKey[string(c)] = "Digit" + string(c)
} }
// ASCII: A - Z // ASCII: A - Z
for c := 'A'; c <= 'Z'; c++ { for c := 'A'; c <= 'Z'; c++ {
nameToGLFWKey[string(c)] = glfw.KeyA + glfw.Key(c) - 'A' nameToGLFWKey[string(c)] = glfw.KeyA + glfw.Key(c) - 'A'
nameToAndroidKey[string(c)] = int(29 + c - 'A')
nameToJSKey[string(c)] = "Key" + string(c) nameToJSKey[string(c)] = "Key" + string(c)
} }
// Function keys // Function keys
for i := 1; i <= 12; i++ { for i := 1; i <= 12; i++ {
name := "F" + strconv.Itoa(i) name := "F" + strconv.Itoa(i)
nameToGLFWKey[name] = glfw.KeyF1 + glfw.Key(i) - 1 nameToGLFWKey[name] = glfw.KeyF1 + glfw.Key(i) - 1
nameToAndroidKey[name] = 131 + i - 1
nameToJSKey[name] = name nameToJSKey[name] = name
} }
// Numpad // Numpad
@ -161,6 +216,7 @@ func init() {
for c := '0'; c <= '9'; c++ { for c := '0'; c <= '9'; c++ {
name := "KP" + string(c) name := "KP" + string(c)
nameToGLFWKey[name] = glfw.KeyKP0 + glfw.Key(c) - '0' nameToGLFWKey[name] = glfw.KeyKP0 + glfw.Key(c) - '0'
nameToAndroidKey[name] = int(144 + c - '0')
nameToJSKey[name] = "Numpad" + string(c) nameToJSKey[name] = "Numpad" + string(c)
} }
} }
@ -384,6 +440,24 @@ const (
) )
` `
const mobileAndroidKeysTmpl = `{{.License}}
{{.DoNotEdit}}
{{.BuildTag}}
package ebitenmobileview
import (
"github.com/hajimehoshi/ebiten/internal/driver"
)
var androidKeyToDriverKey = map[int]driver.Key{
{{range $name, $code := .NameToAndroidKey}}{{$code}}: driver.Key{{$name}},
{{end}}
}
`
func digitKey(name string) int { func digitKey(name string) int {
if len(name) != 1 { if len(name) != 1 {
return -1 return -1
@ -502,12 +576,13 @@ func main() {
sort.Slice(driverKeyNames, keyNamesLess(driverKeyNames)) sort.Slice(driverKeyNames, keyNamesLess(driverKeyNames))
for path, tmpl := range map[string]string{ for path, tmpl := range map[string]string{
filepath.Join("event", "keys.go"): eventKeysTmpl, filepath.Join("event", "keys.go"): eventKeysTmpl,
filepath.Join("internal", "driver", "keys.go"): driverKeysTmpl, filepath.Join("internal", "driver", "keys.go"): driverKeysTmpl,
filepath.Join("internal", "glfw", "keys.go"): glfwKeysTmpl, filepath.Join("internal", "glfw", "keys.go"): glfwKeysTmpl,
filepath.Join("internal", "uidriver", "glfw", "keys.go"): uidriverGlfwKeysTmpl, filepath.Join("internal", "uidriver", "glfw", "keys.go"): uidriverGlfwKeysTmpl,
filepath.Join("internal", "uidriver", "js", "keys.go"): uidriverJsKeysTmpl, filepath.Join("internal", "uidriver", "js", "keys.go"): uidriverJsKeysTmpl,
filepath.Join("keys.go"): ebitenKeysTmpl, filepath.Join("keys.go"): ebitenKeysTmpl,
filepath.Join("mobile", "ebitenmobileview", "keys_android.go"): mobileAndroidKeysTmpl,
} { } {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
@ -546,6 +621,7 @@ func main() {
EbitenKeyNamesWithoutMods []string EbitenKeyNamesWithoutMods []string
DriverKeyNames []string DriverKeyNames []string
NameToGLFWKey map[string]glfw.Key NameToGLFWKey map[string]glfw.Key
NameToAndroidKey map[string]int
}{ }{
License: license, License: license,
DoNotEdit: doNotEdit, DoNotEdit: doNotEdit,
@ -556,6 +632,7 @@ func main() {
EbitenKeyNamesWithoutMods: ebitenKeyNamesWithoutMods, EbitenKeyNamesWithoutMods: ebitenKeyNamesWithoutMods,
DriverKeyNames: driverKeyNames, DriverKeyNames: driverKeyNames,
NameToGLFWKey: nameToGLFWKey, NameToGLFWKey: nameToGLFWKey,
NameToAndroidKey: nameToAndroidKey,
}); err != nil { }); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -39,6 +39,8 @@ func InputChars() []rune {
// - KeyKPEnter and KeyKPEqual are recognized as KeyEnter and KeyEqual. // - KeyKPEnter and KeyKPEqual are recognized as KeyEnter and KeyEqual.
// - KeyPrintScreen is only treated at keyup event. // - KeyPrintScreen is only treated at keyup event.
// //
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keys.
//
// IsKeyPressed is concurrent-safe. // IsKeyPressed is concurrent-safe.
func IsKeyPressed(key Key) bool { func IsKeyPressed(key Key) bool {
// There are keys that are invalid values as ebiten.Key (e.g., driver.KeyLeftAlt). // There are keys that are invalid values as ebiten.Key (e.g., driver.KeyLeftAlt).

View File

@ -28,6 +28,7 @@ type pos struct {
type Input struct { type Input struct {
cursorX int cursorX int
cursorY int cursorY int
keys map[driver.Key]struct{}
touches map[int]pos touches map[int]pos
ui *UserInterface ui *UserInterface
} }
@ -98,7 +99,14 @@ func (i *Input) RuneBuffer() []rune {
} }
func (i *Input) IsKeyPressed(key driver.Key) bool { func (i *Input) IsKeyPressed(key driver.Key) bool {
return false i.ui.m.RLock()
defer i.ui.m.RUnlock()
if i.keys == nil {
return false
}
_, ok := i.keys[key]
return ok
} }
func (i *Input) Wheel() (xoff, yoff float64) { func (i *Input) Wheel() (xoff, yoff float64) {
@ -109,8 +117,15 @@ func (i *Input) IsMouseButtonPressed(key driver.MouseButton) bool {
return false return false
} }
func (i *Input) update(touches []*Touch) { func (i *Input) update(keys map[driver.Key]struct{}, touches []*Touch) {
i.ui.m.Lock() i.ui.m.Lock()
defer i.ui.m.Unlock()
i.keys = map[driver.Key]struct{}{}
for k := range keys {
i.keys[k] = struct{}{}
}
i.touches = map[int]pos{} i.touches = map[int]pos{}
for _, t := range touches { for _, t := range touches {
i.touches[t.ID] = pos{ i.touches[t.ID] = pos{
@ -118,7 +133,6 @@ func (i *Input) update(touches []*Touch) {
Y: t.Y, Y: t.Y,
} }
} }
i.ui.m.Unlock()
} }
func (i *Input) ResetForFrame() { func (i *Input) ResetForFrame() {

View File

@ -199,7 +199,8 @@ func (u *UserInterface) appMain(a app.App) {
for _, t := range touches { for _, t := range touches {
ts = append(ts, t) ts = append(ts, t)
} }
u.input.update(ts) // TODO: Give keyboard keys.
u.input.update(nil, ts)
} }
} }
} }
@ -421,6 +422,6 @@ type Touch struct {
Y int Y int
} }
func (u *UserInterface) UpdateInput(touches []*Touch) { func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, touches []*Touch) {
u.input.update(touches) u.input.update(keys, touches)
} }

View File

@ -17,6 +17,7 @@
package ebitenmobileview package ebitenmobileview
import ( import (
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/uidriver/mobile" "github.com/hajimehoshi/ebiten/internal/uidriver/mobile"
) )
@ -26,6 +27,7 @@ type position struct {
} }
var ( var (
keys = map[driver.Key]struct{}{}
touches = map[int]position{} touches = map[int]position{}
) )
@ -38,5 +40,5 @@ func updateInput() {
Y: position.y, Y: position.y,
}) })
} }
mobile.Get().UpdateInput(ts) mobile.Get().UpdateInput(keys, ts)
} }

View File

@ -30,3 +30,21 @@ func UpdateTouchesOnAndroid(action int, id int, x, y int) {
func UpdateTouchesOnIOS(phase int, ptr int64, x, y int) { func UpdateTouchesOnIOS(phase int, ptr int64, x, y int) {
panic("ebitenmobileview: updateTouchesOnIOSImpl must not be called on Android") panic("ebitenmobileview: updateTouchesOnIOSImpl must not be called on Android")
} }
func OnKeyDownOnAndroid(keyCode int) {
key, ok := androidKeyToDriverKey[keyCode]
if !ok {
return
}
keys[key] = struct{}{}
updateInput()
}
func OnKeyUpOnAndroid(keyCode int) {
key, ok := androidKeyToDriverKey[keyCode]
if !ok {
return
}
delete(keys, key)
updateInput()
}

View File

@ -0,0 +1,127 @@
// Copyright 2013 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.
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
package ebitenmobileview
import (
"github.com/hajimehoshi/ebiten/internal/driver"
)
var androidKeyToDriverKey = map[int]driver.Key{
7: driver.Key0,
8: driver.Key1,
9: driver.Key2,
10: driver.Key3,
11: driver.Key4,
12: driver.Key5,
13: driver.Key6,
14: driver.Key7,
15: driver.Key8,
16: driver.Key9,
29: driver.KeyA,
75: driver.KeyApostrophe,
30: driver.KeyB,
73: driver.KeyBackslash,
67: driver.KeyBackspace,
31: driver.KeyC,
115: driver.KeyCapsLock,
55: driver.KeyComma,
32: driver.KeyD,
112: driver.KeyDelete,
20: driver.KeyDown,
33: driver.KeyE,
123: driver.KeyEnd,
66: driver.KeyEnter,
70: driver.KeyEqual,
111: driver.KeyEscape,
34: driver.KeyF,
131: driver.KeyF1,
140: driver.KeyF10,
141: driver.KeyF11,
142: driver.KeyF12,
132: driver.KeyF2,
133: driver.KeyF3,
134: driver.KeyF4,
135: driver.KeyF5,
136: driver.KeyF6,
137: driver.KeyF7,
138: driver.KeyF8,
139: driver.KeyF9,
35: driver.KeyG,
68: driver.KeyGraveAccent,
36: driver.KeyH,
122: driver.KeyHome,
37: driver.KeyI,
124: driver.KeyInsert,
38: driver.KeyJ,
39: driver.KeyK,
144: driver.KeyKP0,
145: driver.KeyKP1,
146: driver.KeyKP2,
147: driver.KeyKP3,
148: driver.KeyKP4,
149: driver.KeyKP5,
150: driver.KeyKP6,
151: driver.KeyKP7,
152: driver.KeyKP8,
153: driver.KeyKP9,
157: driver.KeyKPAdd,
158: driver.KeyKPDecimal,
154: driver.KeyKPDivide,
160: driver.KeyKPEnter,
161: driver.KeyKPEqual,
155: driver.KeyKPMultiply,
156: driver.KeyKPSubtract,
40: driver.KeyL,
21: driver.KeyLeft,
57: driver.KeyLeftAlt,
71: driver.KeyLeftBracket,
113: driver.KeyLeftControl,
59: driver.KeyLeftShift,
41: driver.KeyM,
82: driver.KeyMenu,
69: driver.KeyMinus,
42: driver.KeyN,
143: driver.KeyNumLock,
43: driver.KeyO,
44: driver.KeyP,
93: driver.KeyPageDown,
92: driver.KeyPageUp,
121: driver.KeyPause,
56: driver.KeyPeriod,
120: driver.KeyPrintScreen,
45: driver.KeyQ,
46: driver.KeyR,
22: driver.KeyRight,
58: driver.KeyRightAlt,
72: driver.KeyRightBracket,
114: driver.KeyRightControl,
60: driver.KeyRightShift,
47: driver.KeyS,
116: driver.KeyScrollLock,
74: driver.KeySemicolon,
76: driver.KeySlash,
62: driver.KeySpace,
48: driver.KeyT,
61: driver.KeyTab,
49: driver.KeyU,
19: driver.KeyUp,
50: driver.KeyV,
51: driver.KeyW,
52: driver.KeyX,
53: driver.KeyY,
54: driver.KeyZ,
}