mobile/ebitenmobileview: add iOS Keyboard support. (#2678)

Closes #1090
This commit is contained in:
divVerent 2023-06-23 16:00:13 -04:00 committed by GitHub
parent b96aea70f1
commit 1789f509e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 305 additions and 6 deletions

View File

@ -259,6 +259,28 @@
[self updateTouches:touches];
}
- (void)updatePresses:(NSSet<UIPress *> *)presses {
if (@available(iOS 13.4, *)) {
// Note: before iOS 13.4, this just can return UIPressType, which is
// insufficient for games.
for (UIPress *press in presses) {
UIKey *key = press.key;
if (key == nil) {
continue;
}
EbitenmobileviewUpdatePressesOnIOS(press.phase, key.keyCode, key.characters);
}
}
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self updatePresses:presses];
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self updatePresses:presses];
}
- (void)suspendGame {
NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called");

View File

@ -35,6 +35,7 @@ var (
glfwKeyNameToGLFWKey map[string]glfw.Key
uiKeyNameToGLFWKeyName map[string]string
androidKeyToUIKeyName map[int]string
iosKeyToUIKeyName map[int]string
gbuildKeyToUIKeyName map[key.Code]string
uiKeyNameToJSKey map[string]string
oldEbitengineKeyNameToUIKeyName map[string]string
@ -196,6 +197,90 @@ func init() {
118: "MetaRight",
}
// https://developer.apple.com/documentation/uikit/uikeyboardhidusage?language=objc
iosKeyToUIKeyName = map[int]string{
0xE2: "AltLeft",
0xE6: "AltRight",
0x51: "ArrowDown",
0x50: "ArrowLeft",
0x4F: "ArrowRight",
0x52: "ArrowUp",
0x35: "Backquote",
// These three keys are:
// - US backslash-pipe key (above return),
// - non-US backslash key (next to left shift; on German layout this is the <>| key), and
// - non-US hashmark key (bottom left of return; on German layout, this is the #' key).
// On US layout configurations, they all map to the same characters - the backslash.
//
// See also: https://www.w3.org/TR/uievents-code/#keyboard-102
0x31: "Backslash", // UIKeyboardHIDUsageKeyboardBackslash
0x64: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash
0x32: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSPound
0x2A: "Backspace",
0x2F: "BracketLeft",
0x30: "BracketRight",
// Caps Lock can either be a normal key or a hardware toggle.
0x39: "CapsLock", // UIKeyboardHIDUsageKeyboardCapsLock
0x82: "CapsLock", // UIKeyboardHIDUsageKeyboardLockingCapsLock
0x36: "Comma",
0xE0: "ControlLeft",
0xE4: "ControlRight",
0x4C: "Delete",
0x4D: "End",
0x28: "Enter",
0x2E: "Equal",
0x29: "Escape",
0x4A: "Home",
0x49: "Insert",
0x76: "ContextMenu",
0xE3: "MetaLeft",
0xE7: "MetaRight",
0x2D: "Minus",
// Num Lock can either be a normal key or a hardware toggle.
0x53: "NumLock", // UIKeyboardHIDUsageKeyboardNumLock
0x83: "NumLock", // UIKeyboardHIDUsageKeyboardLockingNumLock
0x57: "NumpadAdd",
// Some keyboard layouts have a comma, some a period on the numeric pad.
// They are the same key, though.
0x63: "NumpadDecimal", // UIKeyboardHIDUsageKeypadPeriod
0x85: "NumpadDecimal", // UIKeyboardHIDUsageKeypadComma
0x54: "NumpadDivide",
0x58: "NumpadEnter",
// Some numeric keypads also have an equals sign.
// There appear to be two separate keycodes for that.
0x67: "NumpadEqual", // UIKeyboardHIDUsageKeypadEqualSign
0x86: "NumpadEqual", // UIKeyboardHIDUsageKeypadEqualSignAS400
0x55: "NumpadMultiply",
0x56: "NumpadSubtract",
0x4E: "PageDown",
0x4B: "PageUp",
0x48: "Pause",
0x37: "Period",
0x46: "PrintScreen",
0x34: "Quote",
// Scroll Lock can either be a normal key or a hardware toggle.
0x47: "ScrollLock", // UIKeyboardHIDUsageKeyboardScrollLock
0x84: "ScrollLock", // UIKeyboardHIDUsageKeyboardLockingScrollLock
0x33: "Semicolon",
0xE1: "ShiftLeft",
0xE5: "ShiftRight",
0x38: "Slash",
0x2C: "Space",
0x2B: "Tab",
}
gbuildKeyToUIKeyName = map[key.Code]string{
key.CodeComma: "Comma",
key.CodeFullStop: "Period",
@ -306,19 +391,23 @@ func init() {
uiKeyNameToGLFWKeyName[name] = string(c)
androidKeyToUIKeyName[7+int(c)-'0'] = name
// Gomobile's key code (= USB HID key codes) has successive key codes for 1, 2, ..., 9, 0
// in this order.
// in this order. Same for iOS.
if c == '0' {
iosKeyToUIKeyName[0x27] = name
gbuildKeyToUIKeyName[key.Code0] = name
} else {
iosKeyToUIKeyName[0x1E+int(c)-'1'] = name
gbuildKeyToUIKeyName[key.Code1+key.Code(c)-'1'] = name
}
uiKeyNameToJSKey[name] = name
}
// ASCII: A - Z
for c := 'A'; c <= 'Z'; c++ {
glfwKeyNameToGLFWKey[string(c)] = glfw.KeyA + glfw.Key(c) - 'A'
uiKeyNameToGLFWKeyName[string(c)] = string(c)
androidKeyToUIKeyName[29+int(c)-'A'] = string(c)
iosKeyToUIKeyName[0x04+int(c)-'A'] = string(c)
gbuildKeyToUIKeyName[key.CodeA+key.Code(c)-'A'] = string(c)
uiKeyNameToJSKey[string(c)] = "Key" + string(c)
}
@ -328,6 +417,9 @@ func init() {
glfwKeyNameToGLFWKey[name] = glfw.KeyF1 + glfw.Key(i) - 1
uiKeyNameToGLFWKeyName[name] = name
androidKeyToUIKeyName[131+i-1] = name
// Note: iOS keys go up to F24 (with F13 being 0x68 and increasing from there),
// but Ebitengine currently only goes to F12.
iosKeyToUIKeyName[0x3A+i-1] = name
gbuildKeyToUIKeyName[key.CodeF1+key.Code(i)-1] = name
uiKeyNameToJSKey[name] = name
}
@ -339,10 +431,12 @@ func init() {
uiKeyNameToGLFWKeyName[name] = "KP" + string(c)
androidKeyToUIKeyName[144+int(c)-'0'] = name
// Gomobile's key code (= USB HID key codes) has successive key codes for 1, 2, ..., 9, 0
// in this order.
// in this order. Same for iOS.
if c == '0' {
iosKeyToUIKeyName[0x62] = name
gbuildKeyToUIKeyName[key.CodeKeypad0] = name
} else {
iosKeyToUIKeyName[0x59+int(c)-'1'] = name
gbuildKeyToUIKeyName[key.CodeKeypad1+key.Code(c)-'1'] = name
}
uiKeyNameToJSKey[name] = name
@ -583,6 +677,24 @@ var androidKeyToUIKey = map[int]ui.Key{
}
`
const mobileIOSKeysTmpl = `{{.License}}
{{.DoNotEdit}}
{{.BuildTag}}
package ebitenmobileview
import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
var iosKeyToUIKey = map[int]ui.Key{
{{range $key, $name := .IOSKeyToUIKeyName}}{{$key}}: ui.Key{{$name}},
{{end}}
}
`
const uiMobileKeysTmpl = `{{.License}}
{{.DoNotEdit}}
@ -725,6 +837,7 @@ func main() {
filepath.Join("internal", "ui", "keys_js.go"): uiJSKeysTmpl,
filepath.Join("keys.go"): ebitengineKeysTmpl,
filepath.Join("mobile", "ebitenmobileview", "keys_android.go"): mobileAndroidKeysTmpl,
filepath.Join("mobile", "ebitenmobileview", "keys_ios.go"): mobileIOSKeysTmpl,
} {
f, err := os.Create(path)
if err != nil {
@ -764,6 +877,7 @@ func main() {
UIKeyNames []string
UIKeyNameToGLFWKeyName map[string]string
AndroidKeyToUIKeyName map[int]string
IOSKeyToUIKeyName map[int]string
GBuildKeyToUIKeyName map[key.Code]string
OldEbitengineKeyNameToUIKeyName map[string]string
}{
@ -778,6 +892,7 @@ func main() {
UIKeyNames: uiKeyNames,
UIKeyNameToGLFWKeyName: uiKeyNameToGLFWKeyName,
AndroidKeyToUIKeyName: androidKeyToUIKeyName,
IOSKeyToUIKeyName: iosKeyToUIKeyName,
GBuildKeyToUIKeyName: gbuildKeyToUIKeyName,
OldEbitengineKeyNameToUIKeyName: oldEbitengineKeyNameToUIKeyName,
}); err != nil {

View File

@ -35,8 +35,6 @@ import (
// AppendInputChars is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
//
// Keyboards don't work on iOS yet (#1090).
func AppendInputChars(runes []rune) []rune {
return theInputState.appendInputChars(runes)
}
@ -64,8 +62,6 @@ func InputChars() []rune {
// IsKeyPressed is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
//
// Keyboards don't work on iOS yet (#1090).
func IsKeyPressed(key Key) bool {
return theInputState.isKeyPressed(key)
}

View File

@ -16,6 +16,7 @@ package ebitenmobileview
import (
"fmt"
"unicode"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
@ -48,11 +49,40 @@ func UpdateTouchesOnIOS(phase int, ptr int64, x, y int) {
case C.UITouchPhaseBegan, C.UITouchPhaseMoved, C.UITouchPhaseStationary:
id := getIDFromPtr(ptr)
touches[ui.TouchID(id)] = position{x, y}
runes = nil
updateInput()
case C.UITouchPhaseEnded, C.UITouchPhaseCancelled:
id := getIDFromPtr(ptr)
delete(ptrToID, ptr)
delete(touches, ui.TouchID(id))
runes = nil
updateInput()
default:
panic(fmt.Sprintf("ebitenmobileview: invalid phase: %d", phase))
}
}
func UpdatePressesOnIOS(phase int, keyCode int, keyString string) {
switch phase {
case C.UITouchPhaseBegan, C.UITouchPhaseMoved, C.UITouchPhaseStationary:
if key, ok := iosKeyToUIKey[keyCode]; ok {
keys[key] = struct{}{}
}
runes = nil
if phase == C.UITouchPhaseBegan {
for _, r := range keyString {
if !unicode.IsPrint(r) {
continue
}
runes = append(runes, r)
}
}
updateInput()
case C.UITouchPhaseEnded, C.UITouchPhaseCancelled:
if key, ok := iosKeyToUIKey[keyCode]; ok {
delete(keys, key)
}
runes = nil
updateInput()
default:
panic(fmt.Sprintf("ebitenmobileview: invalid phase: %d", phase))

View File

@ -0,0 +1,136 @@
// Copyright 2013 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.
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
package ebitenmobileview
import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
var iosKeyToUIKey = map[int]ui.Key{
4: ui.KeyA,
5: ui.KeyB,
6: ui.KeyC,
7: ui.KeyD,
8: ui.KeyE,
9: ui.KeyF,
10: ui.KeyG,
11: ui.KeyH,
12: ui.KeyI,
13: ui.KeyJ,
14: ui.KeyK,
15: ui.KeyL,
16: ui.KeyM,
17: ui.KeyN,
18: ui.KeyO,
19: ui.KeyP,
20: ui.KeyQ,
21: ui.KeyR,
22: ui.KeyS,
23: ui.KeyT,
24: ui.KeyU,
25: ui.KeyV,
26: ui.KeyW,
27: ui.KeyX,
28: ui.KeyY,
29: ui.KeyZ,
30: ui.KeyDigit1,
31: ui.KeyDigit2,
32: ui.KeyDigit3,
33: ui.KeyDigit4,
34: ui.KeyDigit5,
35: ui.KeyDigit6,
36: ui.KeyDigit7,
37: ui.KeyDigit8,
38: ui.KeyDigit9,
39: ui.KeyDigit0,
40: ui.KeyEnter,
41: ui.KeyEscape,
42: ui.KeyBackspace,
43: ui.KeyTab,
44: ui.KeySpace,
45: ui.KeyMinus,
46: ui.KeyEqual,
47: ui.KeyBracketLeft,
48: ui.KeyBracketRight,
49: ui.KeyBackslash,
50: ui.KeyBackslash,
51: ui.KeySemicolon,
52: ui.KeyQuote,
53: ui.KeyBackquote,
54: ui.KeyComma,
55: ui.KeyPeriod,
56: ui.KeySlash,
57: ui.KeyCapsLock,
58: ui.KeyF1,
59: ui.KeyF2,
60: ui.KeyF3,
61: ui.KeyF4,
62: ui.KeyF5,
63: ui.KeyF6,
64: ui.KeyF7,
65: ui.KeyF8,
66: ui.KeyF9,
67: ui.KeyF10,
68: ui.KeyF11,
69: ui.KeyF12,
70: ui.KeyPrintScreen,
71: ui.KeyScrollLock,
72: ui.KeyPause,
73: ui.KeyInsert,
74: ui.KeyHome,
75: ui.KeyPageUp,
76: ui.KeyDelete,
77: ui.KeyEnd,
78: ui.KeyPageDown,
79: ui.KeyArrowRight,
80: ui.KeyArrowLeft,
81: ui.KeyArrowDown,
82: ui.KeyArrowUp,
83: ui.KeyNumLock,
84: ui.KeyNumpadDivide,
85: ui.KeyNumpadMultiply,
86: ui.KeyNumpadSubtract,
87: ui.KeyNumpadAdd,
88: ui.KeyNumpadEnter,
89: ui.KeyNumpad1,
90: ui.KeyNumpad2,
91: ui.KeyNumpad3,
92: ui.KeyNumpad4,
93: ui.KeyNumpad5,
94: ui.KeyNumpad6,
95: ui.KeyNumpad7,
96: ui.KeyNumpad8,
97: ui.KeyNumpad9,
98: ui.KeyNumpad0,
99: ui.KeyNumpadDecimal,
100: ui.KeyBackslash,
103: ui.KeyNumpadEqual,
118: ui.KeyContextMenu,
130: ui.KeyCapsLock,
131: ui.KeyNumLock,
132: ui.KeyScrollLock,
133: ui.KeyNumpadDecimal,
134: ui.KeyNumpadEqual,
224: ui.KeyControlLeft,
225: ui.KeyShiftLeft,
226: ui.KeyAltLeft,
227: ui.KeyMetaLeft,
228: ui.KeyControlRight,
229: ui.KeyShiftRight,
230: ui.KeyAltRight,
231: ui.KeyMetaRight,
}