diff --git a/cmd/ebitenmobile/_files/EbitenViewController.m b/cmd/ebitenmobile/_files/EbitenViewController.m index eaedd703f..3340b5ee8 100644 --- a/cmd/ebitenmobile/_files/EbitenViewController.m +++ b/cmd/ebitenmobile/_files/EbitenViewController.m @@ -259,6 +259,28 @@ [self updateTouches:touches]; } +- (void)updatePresses:(NSSet *)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 *)presses withEvent:(UIPressesEvent *)event { + [self updatePresses:presses]; +} + +- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { + [self updatePresses:presses]; +} + - (void)suspendGame { NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called"); diff --git a/genkeys.go b/genkeys.go index 342f0d5d0..831675bdd 100644 --- a/genkeys.go +++ b/genkeys.go @@ -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 { diff --git a/input.go b/input.go index ded1467ca..fa9d9ff71 100644 --- a/input.go +++ b/input.go @@ -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) } diff --git a/mobile/ebitenmobileview/input_ios.go b/mobile/ebitenmobileview/input_ios.go index 4f5b1c5e8..92ebcbda6 100644 --- a/mobile/ebitenmobileview/input_ios.go +++ b/mobile/ebitenmobileview/input_ios.go @@ -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)) diff --git a/mobile/ebitenmobileview/keys_ios.go b/mobile/ebitenmobileview/keys_ios.go new file mode 100644 index 000000000..d7eab2c26 --- /dev/null +++ b/mobile/ebitenmobileview/keys_ios.go @@ -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, +}