diff --git a/genkeys.go b/genkeys.go index e3b31c149..167d89824 100644 --- a/genkeys.go +++ b/genkeys.go @@ -20,6 +20,7 @@ package main import ( + "fmt" "log" "os" "path/filepath" @@ -38,6 +39,7 @@ var ( androidKeyToUIKeyName map[int]string gbuildKeyToUIKeyName map[key.Code]string uiKeyNameToJSKey map[string]string + uiKeyNameToCGKey map[string]string edgeKeyCodeToName map[int]string oldEbitenKeyNameToUIKeyName map[string]string ) @@ -301,6 +303,64 @@ func init() { "MetaRight": "MetaRight", } + // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values + // Missing keys: + // + // - kVK_Function + // - kVK_VolumeUp + // - kVK_VolumeDown + // - kVK_Mute + // - kVK_Help + // - kVK_ISO_Section ("IntlBackslash") + // - kVK_JIS_Yen ("IntlYen") + // - kVK_JIS_Underscore ("IntlRo") + // - kVK_JIS_KeypadComma ("NumpadComma") + // - kVK_JIS_Eisu + // - kVK_JIS_Kana + uiKeyNameToCGKey = map[string]string{ + "Equal": "kVK_ANSI_Equal", + "Minus": "kVK_ANSI_Minus", + "BracketRight": "kVK_ANSI_RightBracket", + "BracketLeft": "kVK_ANSI_LeftBracket", + "Quote": "kVK_ANSI_Quote", + "Semicolon": "kVK_ANSI_Semicolon", + "Backslash": "kVK_ANSI_Backslash", + "Comma": "kVK_ANSI_Comma", + "Slash": "kVK_ANSI_Slash", + "Period": "kVK_ANSI_Period", + "Backquote": "kVK_ANSI_Grave", + "NumpadDecimal": "kVK_ANSI_KeypadDecimal", + "NumpadMultiply": "kVK_ANSI_KeypadMultiply", + "NumpadAdd": "kVK_ANSI_KeypadPlus", + "NumLock": "kVK_ANSI_KeypadClear", + "NumpadDivide": "kVK_ANSI_KeypadDivide", + "NumpadEnter": "kVK_ANSI_KeypadEnter", + "NumpadSubtract": "kVK_ANSI_KeypadMinus", + "NumpadEqual": "kVK_ANSI_KeypadEquals", + "Enter": "kVK_Return", + "Tab": "kVK_Tab", + "Space": "kVK_Space", + "Backspace": "kVK_Delete", + "Escape": "kVK_Escape", + "MetaLeft": "kVK_Command", + "ShiftLeft": "kVK_Shift", + "CapsLock": "kVK_CapsLock", + "AltLeft": "kVK_Option", + "ControlLeft": "kVK_Control", + "ShiftRight": "kVK_RightShift", + "AltRight": "kVK_RightOption", + "ControlRight": "kVK_RightControl", + "Home": "kVK_Home", + "PageUp": "kVK_PageUp", + "Delete": "kVK_ForwardDelete", + "End": "kVK_End", + "PageDown": "kVK_PageDown", + "ArrowLeft": "kVK_LeftArrow", + "ArrowRight": "kVK_RightArrow", + "ArrowDown": "kVK_DownArrow", + "ArrowUp": "kVK_UpArrow", + } + // ASCII: 0 - 9 for c := '0'; c <= '9'; c++ { glfwKeyNameToGLFWKey[string(c)] = glfw.Key0 + glfw.Key(c) - '0' @@ -315,6 +375,7 @@ func init() { gbuildKeyToUIKeyName[key.Code1+key.Code(c)-'1'] = name } uiKeyNameToJSKey[name] = name + uiKeyNameToCGKey[name] = fmt.Sprintf("kVK_ANSI_%d", c-'0') } // ASCII: A - Z for c := 'A'; c <= 'Z'; c++ { @@ -323,6 +384,7 @@ func init() { androidKeyToUIKeyName[29+int(c)-'A'] = string(c) gbuildKeyToUIKeyName[key.CodeA+key.Code(c)-'A'] = string(c) uiKeyNameToJSKey[string(c)] = "Key" + string(c) + uiKeyNameToCGKey[string(c)] = "kVK_ANSI_" + string(c) } // Function keys for i := 1; i <= 12; i++ { @@ -332,6 +394,7 @@ func init() { androidKeyToUIKeyName[131+i-1] = name gbuildKeyToUIKeyName[key.CodeF1+key.Code(i)-1] = name uiKeyNameToJSKey[name] = name + uiKeyNameToCGKey[name] = fmt.Sprintf("kVK_F%d", i) } // Numpad // https://www.w3.org/TR/uievents-code/#key-numpad-section @@ -348,6 +411,7 @@ func init() { gbuildKeyToUIKeyName[key.CodeKeypad1+key.Code(c)-'1'] = name } uiKeyNameToJSKey[name] = name + uiKeyNameToCGKey[name] = fmt.Sprintf("kVK_ANSI_Keypad%d", c-'0') } // Keys for backward compatibility @@ -633,6 +697,18 @@ var edgeKeyCodeToUIKey = map[int]Key{ } ` +const uiDarwinKeysTmpl = `{{.License}} + +{{.DoNotEdit}} + +package ui + +var uiKeyToCGKey = map[Key]int{ +{{range $uname, $cname := .UIKeyNameToCGKey}}Key{{$uname}}: {{$cname}}, +{{end}} +} +` + const glfwKeysTmpl = `{{.License}} {{.DoNotEdit}} @@ -805,6 +881,7 @@ func main() { filepath.Join("internal", "ui", "keys_glfw.go"): uiGLFWKeysTmpl, filepath.Join("internal", "ui", "keys_mobile.go"): uiMobileKeysTmpl, filepath.Join("internal", "ui", "keys_js.go"): uiJSKeysTmpl, + filepath.Join("internal", "ui", "keys_darwin.go"): uiDarwinKeysTmpl, filepath.Join("keys.go"): ebitenKeysTmpl, filepath.Join("mobile", "ebitenmobileview", "keys_android.go"): mobileAndroidKeysTmpl, } { @@ -836,6 +913,9 @@ func main() { case filepath.Join("internal", "ui", "keys_glfw.go"): buildTag = "//go:build !android && !ios && !js && !ebitenginecbackend && !ebitencbackend" + "\n// +build !android,!ios,!js,!ebitenginecbackend && !ebitencbackend" + case filepath.Join("internal", "ui", "keys_darwin.go"): + buildTag = "//go:build !ios" + + "\n// +build !ios" } // NOTE: According to godoc, maps are automatically sorted by key. if err := tmpl.Execute(f, struct { @@ -843,6 +923,7 @@ func main() { DoNotEdit string BuildTag string UIKeyNameToJSKey map[string]string + UIKeyNameToCGKey map[string]string EdgeKeyCodeToName map[int]string EbitenKeyNames []string EbitenKeyNamesWithoutOld []string @@ -858,6 +939,7 @@ func main() { DoNotEdit: doNotEdit, BuildTag: buildTag, UIKeyNameToJSKey: uiKeyNameToJSKey, + UIKeyNameToCGKey: uiKeyNameToCGKey, EdgeKeyCodeToName: edgeKeyCodeToName, EbitenKeyNames: ebitenKeyNames, EbitenKeyNamesWithoutOld: ebitenKeyNamesWithoutOld, diff --git a/internal/ui/input_glfw.go b/internal/ui/input_glfw.go index d94099c5c..b915d9970 100644 --- a/internal/ui/input_glfw.go +++ b/internal/ui/input_glfw.go @@ -27,7 +27,6 @@ import ( ) type Input struct { - keyPressed map[glfw.Key]bool mouseButtonPressed map[glfw.MouseButton]bool onceCallback sync.Once scrollX float64 @@ -37,6 +36,8 @@ type Input struct { touches map[TouchID]pos // TODO: Implement this (#417) runeBuffer []rune ui *userInterfaceImpl + + nativeInput } type pos struct { @@ -103,20 +104,6 @@ func (i *Input) resetForTick() { i.scrollX, i.scrollY = 0, 0 } -func (i *Input) IsKeyPressed(key Key) bool { - if !i.ui.isRunning() { - return false - } - - i.ui.m.Lock() - defer i.ui.m.Unlock() - if i.keyPressed == nil { - i.keyPressed = map[glfw.Key]bool{} - } - gk, ok := uiKeyToGLFWKey[key] - return ok && i.keyPressed[gk] -} - func (i *Input) IsMouseButtonPressed(button MouseButton) bool { if !i.ui.isRunning() { return false @@ -178,12 +165,7 @@ func (i *Input) update(window *glfw.Window, context *context) error { i.scrollY = yoff })) }) - if i.keyPressed == nil { - i.keyPressed = map[glfw.Key]bool{} - } - for gk := range glfwKeyToUIKey { - i.keyPressed[gk] = window.GetKey(gk) == glfw.Press - } + i.updateKeys(window) if i.mouseButtonPressed == nil { i.mouseButtonPressed = map[glfw.MouseButton]bool{} } diff --git a/internal/ui/input_glfw_darwin.go b/internal/ui/input_glfw_darwin.go new file mode 100644 index 000000000..ece8c421a --- /dev/null +++ b/internal/ui/input_glfw_darwin.go @@ -0,0 +1,187 @@ +// Copyright 2022 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. + +//go:build !ios +// +build !ios + +package ui + +// #cgo CFLAGS: -x objective-c +// #cgo LDFLAGS: -framework CoreGraphics +// +// #import "CoreGraphics/CoreGraphics.h" +import "C" + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/glfw" +) + +const ( + kVK_ANSI_A = 0x00 + kVK_ANSI_S = 0x01 + kVK_ANSI_D = 0x02 + kVK_ANSI_F = 0x03 + kVK_ANSI_H = 0x04 + kVK_ANSI_G = 0x05 + kVK_ANSI_Z = 0x06 + kVK_ANSI_X = 0x07 + kVK_ANSI_C = 0x08 + kVK_ANSI_V = 0x09 + kVK_ANSI_B = 0x0B + kVK_ANSI_Q = 0x0C + kVK_ANSI_W = 0x0D + kVK_ANSI_E = 0x0E + kVK_ANSI_R = 0x0F + kVK_ANSI_Y = 0x10 + kVK_ANSI_T = 0x11 + kVK_ANSI_1 = 0x12 + kVK_ANSI_2 = 0x13 + kVK_ANSI_3 = 0x14 + kVK_ANSI_4 = 0x15 + kVK_ANSI_6 = 0x16 + kVK_ANSI_5 = 0x17 + kVK_ANSI_Equal = 0x18 + kVK_ANSI_9 = 0x19 + kVK_ANSI_7 = 0x1A + kVK_ANSI_Minus = 0x1B + kVK_ANSI_8 = 0x1C + kVK_ANSI_0 = 0x1D + kVK_ANSI_RightBracket = 0x1E + kVK_ANSI_O = 0x1F + kVK_ANSI_U = 0x20 + kVK_ANSI_LeftBracket = 0x21 + kVK_ANSI_I = 0x22 + kVK_ANSI_P = 0x23 + kVK_ANSI_L = 0x25 + kVK_ANSI_J = 0x26 + kVK_ANSI_Quote = 0x27 + kVK_ANSI_K = 0x28 + kVK_ANSI_Semicolon = 0x29 + kVK_ANSI_Backslash = 0x2A + kVK_ANSI_Comma = 0x2B + kVK_ANSI_Slash = 0x2C + kVK_ANSI_N = 0x2D + kVK_ANSI_M = 0x2E + kVK_ANSI_Period = 0x2F + kVK_ANSI_Grave = 0x32 + kVK_ANSI_KeypadDecimal = 0x41 + kVK_ANSI_KeypadMultiply = 0x43 + kVK_ANSI_KeypadPlus = 0x45 + kVK_ANSI_KeypadClear = 0x47 + kVK_ANSI_KeypadDivide = 0x4B + kVK_ANSI_KeypadEnter = 0x4C + kVK_ANSI_KeypadMinus = 0x4E + kVK_ANSI_KeypadEquals = 0x51 + kVK_ANSI_Keypad0 = 0x52 + kVK_ANSI_Keypad1 = 0x53 + kVK_ANSI_Keypad2 = 0x54 + kVK_ANSI_Keypad3 = 0x55 + kVK_ANSI_Keypad4 = 0x56 + kVK_ANSI_Keypad5 = 0x57 + kVK_ANSI_Keypad6 = 0x58 + kVK_ANSI_Keypad7 = 0x59 + kVK_ANSI_Keypad8 = 0x5B + kVK_ANSI_Keypad9 = 0x5C + + // keycodes for keys that are independent of keyboard layout + kVK_Return = 0x24 + kVK_Tab = 0x30 + kVK_Space = 0x31 + kVK_Delete = 0x33 + kVK_Escape = 0x35 + kVK_Command = 0x37 + kVK_Shift = 0x38 + kVK_CapsLock = 0x39 + kVK_Option = 0x3A + kVK_Control = 0x3B + kVK_RightShift = 0x3C + kVK_RightOption = 0x3D + kVK_RightControl = 0x3E + kVK_Function = 0x3F + kVK_F17 = 0x40 + kVK_VolumeUp = 0x48 + kVK_VolumeDown = 0x49 + kVK_Mute = 0x4A + kVK_F18 = 0x4F + kVK_F19 = 0x50 + kVK_F20 = 0x5A + kVK_F5 = 0x60 + kVK_F6 = 0x61 + kVK_F7 = 0x62 + kVK_F3 = 0x63 + kVK_F8 = 0x64 + kVK_F9 = 0x65 + kVK_F11 = 0x67 + kVK_F13 = 0x69 + kVK_F16 = 0x6A + kVK_F14 = 0x6B + kVK_F10 = 0x6D + kVK_F12 = 0x6F + kVK_F15 = 0x71 + kVK_Help = 0x72 + kVK_Home = 0x73 + kVK_PageUp = 0x74 + kVK_ForwardDelete = 0x75 + kVK_F4 = 0x76 + kVK_End = 0x77 + kVK_F2 = 0x78 + kVK_PageDown = 0x79 + kVK_F1 = 0x7A + kVK_LeftArrow = 0x7B + kVK_RightArrow = 0x7C + kVK_DownArrow = 0x7D + kVK_UpArrow = 0x7E + + // ISO keyboards only + kVK_ISO_Section = 0x0A + + // JIS keyboards only + kVK_JIS_Yen = 0x5D + kVK_JIS_Underscore = 0x5E + kVK_JIS_KeypadComma = 0x5F + kVK_JIS_Eisu = 0x66 + kVK_JIS_Kana = 0x68 +) + +type nativeInput struct { + keyPressed map[C.CGKeyCode]bool +} + +func (i *Input) updateKeys(window *glfw.Window) { + if i.keyPressed == nil { + i.keyPressed = map[C.CGKeyCode]bool{} + } + + // Record the key states instead of calling CGEventSourceKeyState every time at IsKeyPressed. + // There is an assumption that the key states never change during one tick. + // Without this assumption, some functions in inpututil would not work correctly. + for _, cgKey := range uiKeyToCGKey { + i.keyPressed[C.CGKeyCode(cgKey)] = bool(C.CGEventSourceKeyState(C.kCGEventSourceStateCombinedSessionState, C.CGKeyCode(cgKey))) + } +} + +func (i *Input) IsKeyPressed(key Key) bool { + if !i.ui.isRunning() { + return false + } + + i.ui.m.Lock() + defer i.ui.m.Unlock() + + k, ok := uiKeyToCGKey[key] + if !ok { + return false + } + return i.keyPressed[C.CGKeyCode(k)] +} diff --git a/internal/ui/input_glfw_notdarwin.go b/internal/ui/input_glfw_notdarwin.go new file mode 100644 index 000000000..583e9e54b --- /dev/null +++ b/internal/ui/input_glfw_notdarwin.go @@ -0,0 +1,50 @@ +// Copyright 2022 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. + +//go:build !android && !darwin && !js && !ebitenginecbackend && !ebitencbackend +// +build !android,!darwin,!js,!ebitenginecbackend,!ebitencbackend + +package ui + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/glfw" +) + +type nativeInput struct { + keyPressed map[glfw.Key]bool +} + +func (i *Input) updateKeys(window *glfw.Window) { + if i.keyPressed == nil { + i.keyPressed = map[glfw.Key]bool{} + } + for gk := range glfwKeyToUIKey { + i.keyPressed[gk] = window.GetKey(gk) == glfw.Press + } +} + +func (i *Input) IsKeyPressed(key Key) bool { + if !i.ui.isRunning() { + return false + } + + i.ui.m.Lock() + defer i.ui.m.Unlock() + + gk, ok := uiKeyToGLFWKey[key] + if !ok { + return false + } + return i.keyPressed[gk] +} diff --git a/internal/ui/keys_darwin.go b/internal/ui/keys_darwin.go new file mode 100644 index 000000000..eb0ff2b75 --- /dev/null +++ b/internal/ui/keys_darwin.go @@ -0,0 +1,119 @@ +// 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 ui + +var uiKeyToCGKey = map[Key]int{ + KeyA: kVK_ANSI_A, + KeyAltLeft: kVK_Option, + KeyAltRight: kVK_RightOption, + KeyArrowDown: kVK_DownArrow, + KeyArrowLeft: kVK_LeftArrow, + KeyArrowRight: kVK_RightArrow, + KeyArrowUp: kVK_UpArrow, + KeyB: kVK_ANSI_B, + KeyBackquote: kVK_ANSI_Grave, + KeyBackslash: kVK_ANSI_Backslash, + KeyBackspace: kVK_Delete, + KeyBracketLeft: kVK_ANSI_LeftBracket, + KeyBracketRight: kVK_ANSI_RightBracket, + KeyC: kVK_ANSI_C, + KeyCapsLock: kVK_CapsLock, + KeyComma: kVK_ANSI_Comma, + KeyControlLeft: kVK_Control, + KeyControlRight: kVK_RightControl, + KeyD: kVK_ANSI_D, + KeyDelete: kVK_ForwardDelete, + KeyDigit0: kVK_ANSI_0, + KeyDigit1: kVK_ANSI_1, + KeyDigit2: kVK_ANSI_2, + KeyDigit3: kVK_ANSI_3, + KeyDigit4: kVK_ANSI_4, + KeyDigit5: kVK_ANSI_5, + KeyDigit6: kVK_ANSI_6, + KeyDigit7: kVK_ANSI_7, + KeyDigit8: kVK_ANSI_8, + KeyDigit9: kVK_ANSI_9, + KeyE: kVK_ANSI_E, + KeyEnd: kVK_End, + KeyEnter: kVK_Return, + KeyEqual: kVK_ANSI_Equal, + KeyEscape: kVK_Escape, + KeyF: kVK_ANSI_F, + KeyF1: kVK_F1, + KeyF10: kVK_F10, + KeyF11: kVK_F11, + KeyF12: kVK_F12, + KeyF2: kVK_F2, + KeyF3: kVK_F3, + KeyF4: kVK_F4, + KeyF5: kVK_F5, + KeyF6: kVK_F6, + KeyF7: kVK_F7, + KeyF8: kVK_F8, + KeyF9: kVK_F9, + KeyG: kVK_ANSI_G, + KeyH: kVK_ANSI_H, + KeyHome: kVK_Home, + KeyI: kVK_ANSI_I, + KeyJ: kVK_ANSI_J, + KeyK: kVK_ANSI_K, + KeyL: kVK_ANSI_L, + KeyM: kVK_ANSI_M, + KeyMetaLeft: kVK_Command, + KeyMinus: kVK_ANSI_Minus, + KeyN: kVK_ANSI_N, + KeyNumLock: kVK_ANSI_KeypadClear, + KeyNumpad0: kVK_ANSI_Keypad0, + KeyNumpad1: kVK_ANSI_Keypad1, + KeyNumpad2: kVK_ANSI_Keypad2, + KeyNumpad3: kVK_ANSI_Keypad3, + KeyNumpad4: kVK_ANSI_Keypad4, + KeyNumpad5: kVK_ANSI_Keypad5, + KeyNumpad6: kVK_ANSI_Keypad6, + KeyNumpad7: kVK_ANSI_Keypad7, + KeyNumpad8: kVK_ANSI_Keypad8, + KeyNumpad9: kVK_ANSI_Keypad9, + KeyNumpadAdd: kVK_ANSI_KeypadPlus, + KeyNumpadDecimal: kVK_ANSI_KeypadDecimal, + KeyNumpadDivide: kVK_ANSI_KeypadDivide, + KeyNumpadEnter: kVK_ANSI_KeypadEnter, + KeyNumpadEqual: kVK_ANSI_KeypadEquals, + KeyNumpadMultiply: kVK_ANSI_KeypadMultiply, + KeyNumpadSubtract: kVK_ANSI_KeypadMinus, + KeyO: kVK_ANSI_O, + KeyP: kVK_ANSI_P, + KeyPageDown: kVK_PageDown, + KeyPageUp: kVK_PageUp, + KeyPeriod: kVK_ANSI_Period, + KeyQ: kVK_ANSI_Q, + KeyQuote: kVK_ANSI_Quote, + KeyR: kVK_ANSI_R, + KeyS: kVK_ANSI_S, + KeySemicolon: kVK_ANSI_Semicolon, + KeyShiftLeft: kVK_Shift, + KeyShiftRight: kVK_RightShift, + KeySlash: kVK_ANSI_Slash, + KeySpace: kVK_Space, + KeyT: kVK_ANSI_T, + KeyTab: kVK_Tab, + KeyU: kVK_ANSI_U, + KeyV: kVK_ANSI_V, + KeyW: kVK_ANSI_W, + KeyX: kVK_ANSI_X, + KeyY: kVK_ANSI_Y, + KeyZ: kVK_ANSI_Z, +}