From aaafc369e604674ac2fb8c9b3babbdbfdd4867c1 Mon Sep 17 00:00:00 2001 From: David North Date: Fri, 25 Oct 2024 16:38:05 +0100 Subject: [PATCH] Keyboard events now use the `code` property **This is a breaking change** Fixes #766 by adding a new `KeyCode` enum, which is populated with the values from the `code` property of the KeyboardEvent. Additionally, the `Key` class also has a new `location` property, which maps to the `location` property of the KeyboardEvent. The KeyboardEvent itself now also supports some new additional properties: * `isRepeat` - a boolean indicating if the key is being held down * `isCtrlKey` - a boolean indicating if the Ctrl key is pressed * `isMetaKey` - a boolean indicating if the Meta key is pressed * `isAltKey` - a boolean indicating if the Alt key is pressed * `isShiftKey` - a boolean indicating if the Shift key is pressed --- .../indigo/platform/events/WorldEvents.scala | 123 +++++--- .../scala/indigo/shared/constants/Key.scala | 279 +++++++----------- .../indigo/shared/constants/KeyCode.scala | 254 ++++++++++++++++ .../indigo/shared/constants/KeyLocation.scala | 17 ++ .../indigo/shared/events/GlobalEvent.scala | 82 ++++- .../scala/indigo/shared/input/Keyboard.scala | 23 +- .../scala/indigo/shared/constants/Key.scala | 4 +- 7 files changed, 544 insertions(+), 238 deletions(-) create mode 100644 indigo/indigo/src/main/scala/indigo/shared/constants/KeyCode.scala create mode 100644 indigo/indigo/src/main/scala/indigo/shared/constants/KeyLocation.scala diff --git a/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala b/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala index 7acfe2d2f..45202f003 100644 --- a/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala +++ b/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala @@ -3,6 +3,8 @@ package indigo.platform.events import indigo.shared.collections.Batch import indigo.shared.config.ResizePolicy import indigo.shared.constants.Key +import indigo.shared.constants.KeyCode +import indigo.shared.constants.KeyLocation import indigo.shared.datatypes.Point import indigo.shared.datatypes.Radians import indigo.shared.datatypes.Size @@ -164,10 +166,36 @@ final class WorldEvents: globalEventStream.pushGlobalEvent(wheel) }, onKeyDown = { e => - globalEventStream.pushGlobalEvent(KeyboardEvent.KeyDown(Key(e.keyCode, e.key))) + globalEventStream.pushGlobalEvent( + KeyboardEvent.KeyDown( + Key( + KeyCode.fromString(e.code), + e.key, + KeyLocation.fromInt(e.location) + ), + e.repeat, + e.altKey, + e.ctrlKey, + e.metaKey, + e.shiftKey + ) + ) }, onKeyUp = { e => - globalEventStream.pushGlobalEvent(KeyboardEvent.KeyUp(Key(e.keyCode, e.key))) + globalEventStream.pushGlobalEvent( + KeyboardEvent.KeyUp( + Key( + KeyCode.fromString(e.code), + e.key, + KeyLocation.fromInt(e.location) + ), + e.repeat, + e.altKey, + e.ctrlKey, + e.metaKey, + e.shiftKey + ) + ) }, // Prevent right mouse button from popping up the context menu onContextMenu = if disableContextMenu then Some((e: dom.MouseEvent) => e.preventDefault()) else None, @@ -442,51 +470,54 @@ final class WorldEvents: resizeObserver = new dom.ResizeObserver((entries, _) => entries.foreach { entry => entry.target.childNodes.foreach { child => - if child.attributes.getNamedItem("id").value == canvas.attributes.getNamedItem("id").value then - val containerSize = new Size( - Math.floor(entry.contentRect.width).toInt, - Math.floor(entry.contentRect.height).toInt - ) - val canvasSize = new Size(canvas.width, canvas.height) - if resizePolicy != ResizePolicy.NoResize then - val newSize = resizePolicy match { - case ResizePolicy.Resize => containerSize - case ResizePolicy.ResizePreserveAspect => - val width = canvas.width.toDouble - val height = canvas.height.toDouble - val aspectRatio = Math.min(width, height) / Math.max(width, height) - - if width > height then - val newHeight = containerSize.width.toDouble * aspectRatio - if newHeight > containerSize.height then - Size( - (containerSize.height / aspectRatio).toInt, - containerSize.height - ) - else - Size( - containerSize.width, - newHeight.toInt - ) - else - val newWidth = containerSize.height.toDouble * aspectRatio - if newWidth > containerSize.width then - Size( - containerSize.width, - (containerSize.width / aspectRatio).toInt - ) + child match { + case child: dom.Element + if child.attributes.getNamedItem("id").value == canvas.attributes.getNamedItem("id").value => + val containerSize = new Size( + Math.floor(entry.contentRect.width).toInt, + Math.floor(entry.contentRect.height).toInt + ) + val canvasSize = new Size(canvas.width, canvas.height) + if resizePolicy != ResizePolicy.NoResize then + val newSize = resizePolicy match { + case ResizePolicy.Resize => containerSize + case ResizePolicy.ResizePreserveAspect => + val width = canvas.width.toDouble + val height = canvas.height.toDouble + val aspectRatio = Math.min(width, height) / Math.max(width, height) + + if width > height then + val newHeight = containerSize.width.toDouble * aspectRatio + if newHeight > containerSize.height then + Size( + (containerSize.height / aspectRatio).toInt, + containerSize.height + ) + else + Size( + containerSize.width, + newHeight.toInt + ) else - Size( - newWidth.toInt, - containerSize.height - ) - case _ => canvasSize - } - - if (newSize != canvasSize) { - canvas.width = Math.min(newSize.width, containerSize.width) - canvas.height = Math.min(newSize.height, containerSize.height) - } + val newWidth = containerSize.height.toDouble * aspectRatio + if newWidth > containerSize.width then + Size( + containerSize.width, + (containerSize.width / aspectRatio).toInt + ) + else + Size( + newWidth.toInt, + containerSize.height + ) + case _ => canvasSize + } + + if (newSize != canvasSize) { + canvas.width = Math.min(newSize.width, containerSize.width) + canvas.height = Math.min(newSize.height, containerSize.height) + } + } } } ) diff --git a/indigo/indigo/src/main/scala/indigo/shared/constants/Key.scala b/indigo/indigo/src/main/scala/indigo/shared/constants/Key.scala index ddec48917..81897e1fa 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/constants/Key.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/constants/Key.scala @@ -1,8 +1,8 @@ package indigo.shared.constants -final case class Key(code: Int, key: String) derives CanEqual { +final case class Key(code: KeyCode, key: String, location: KeyLocation) derives CanEqual { def isPrintable: Boolean = - (key != "") && Key.printable.map(_.code).contains(this.code) + (key != "") && KeyCode.printable.contains(this.code) // DO NOT REMOVE def ===(other: Key): Boolean = @@ -20,181 +20,114 @@ final case class Key(code: Int, key: String) derives CanEqual { case _ => false } - override def hashCode: Int = code + override def hashCode: Int = code.hashCode() } object Key { - implicit private def intToKey(i: Int): Key = - Key(i, "") + implicit private def keyCodeToKey(i: KeyCode): Key = + Key(i, "", KeyLocation.Standard) - implicit private def intToKey(t: (Int, String)): Key = - Key(t._1, t._2) + implicit private def keyCodeToKey(t: (KeyCode, String)): Key = + Key(t._1, t._2, KeyLocation.Standard) - val BACKSPACE: Key = 8 - val TAB: Key = 9 - val ENTER: Key = 13 -> "\n" - val SHIFT: Key = 16 - val CTRL: Key = 17 - val ALT: Key = 18 - val PAUSE_BREAK: Key = 19 - val CAPS_LOCK: Key = 20 - val ESCAPE: Key = 27 - val SPACE: Key = 32 -> " " - val PAGE_UP: Key = 33 - val PAGE_DOWN: Key = 34 - val END: Key = 35 - val HOME: Key = 36 - val LEFT_ARROW: Key = 37 - val UP_ARROW: Key = 38 - val RIGHT_ARROW: Key = 39 - val DOWN_ARROW: Key = 40 - val INSERT: Key = 45 - val DELETE: Key = 46 - val KEY_0: Key = 48 -> "0" - val KEY_1: Key = 49 -> "1" - val KEY_2: Key = 50 -> "2" - val KEY_3: Key = 51 -> "3" - val KEY_4: Key = 52 -> "4" - val KEY_5: Key = 53 -> "5" - val KEY_6: Key = 54 -> "6" - val KEY_7: Key = 55 -> "7" - val KEY_8: Key = 56 -> "8" - val KEY_9: Key = 57 -> "9" - val KEY_A: Key = 65 -> "A" - val KEY_B: Key = 66 -> "B" - val KEY_C: Key = 67 -> "C" - val KEY_D: Key = 68 -> "D" - val KEY_E: Key = 69 -> "E" - val KEY_F: Key = 70 -> "F" - val KEY_G: Key = 71 -> "G" - val KEY_H: Key = 72 -> "H" - val KEY_I: Key = 73 -> "I" - val KEY_J: Key = 74 -> "J" - val KEY_K: Key = 75 -> "K" - val KEY_L: Key = 76 -> "L" - val KEY_M: Key = 77 -> "M" - val KEY_N: Key = 78 -> "N" - val KEY_O: Key = 79 -> "O" - val KEY_P: Key = 80 -> "P" - val KEY_Q: Key = 81 -> "Q" - val KEY_R: Key = 82 -> "R" - val KEY_S: Key = 83 -> "S" - val KEY_T: Key = 84 -> "T" - val KEY_U: Key = 85 -> "U" - val KEY_V: Key = 86 -> "V" - val KEY_W: Key = 87 -> "W" - val KEY_X: Key = 88 -> "X" - val KEY_Y: Key = 89 -> "Y" - val KEY_Z: Key = 90 -> "Z" - val LEFT_WINDOW_KEY: Key = 91 - val RIGHT_WINDOW_KEY: Key = 92 - val SELECT_KEY: Key = 93 - val NUMPAD_0: Key = 96 -> "0" - val NUMPAD_1: Key = 97 -> "1" - val NUMPAD_2: Key = 98 -> "2" - val NUMPAD_3: Key = 99 -> "3" - val NUMPAD_4: Key = 100 -> "4" - val NUMPAD_5: Key = 101 -> "5" - val NUMPAD_6: Key = 102 -> "6" - val NUMPAD_7: Key = 103 -> "7" - val NUMPAD_8: Key = 104 -> "8" - val NUMPAD_9: Key = 105 -> "9" - val MULTIPLY: Key = 106 -> "*" - val ADD: Key = 107 -> "+" - val SUBTRACT: Key = 109 -> "-" - val DECIMAL_POINT: Key = 110 -> "." - val DIVIDE: Key = 111 -> "/" - val F1: Key = 112 - val F2: Key = 113 - val F3: Key = 114 - val F4: Key = 115 - val F5: Key = 116 - val F6: Key = 117 - val F7: Key = 118 - val F8: Key = 119 - val F9: Key = 120 - val F10: Key = 121 - val F11: Key = 122 - val F12: Key = 123 - val NUM_LOCK: Key = 144 - val SCROLL_LOCK: Key = 145 - val SEMI_COLON: Key = 186 -> ";" - val EQUAL_SIGN: Key = 187 -> "=" - val COMMA: Key = 188 -> "," - val DASH: Key = 189 -> "-" - val PERIOD: Key = 190 -> "." - val FORWARD_SLASH: Key = 191 -> "/" - val GRAVE_ACCENT: Key = 192 - val OPEN_BRACKET: Key = 219 -> "(" - val BACK_SLASH: Key = 220 -> "\\" - val CLOSE_BRACKET: Key = 221 -> ")" - val SINGLE_QUOTE: Key = 222 -> "\'" - - val printable: List[Key] = - List( - SPACE, - KEY_0, - KEY_1, - KEY_2, - KEY_3, - KEY_4, - KEY_5, - KEY_6, - KEY_7, - KEY_8, - KEY_9, - KEY_A, - KEY_B, - KEY_C, - KEY_D, - KEY_E, - KEY_F, - KEY_G, - KEY_H, - KEY_I, - KEY_J, - KEY_K, - KEY_L, - KEY_M, - KEY_N, - KEY_O, - KEY_P, - KEY_Q, - KEY_R, - KEY_S, - KEY_T, - KEY_U, - KEY_V, - KEY_W, - KEY_X, - KEY_Y, - KEY_Z, - NUMPAD_0, - NUMPAD_1, - NUMPAD_2, - NUMPAD_3, - NUMPAD_4, - NUMPAD_5, - NUMPAD_6, - NUMPAD_7, - NUMPAD_8, - NUMPAD_9, - MULTIPLY, - ADD, - SUBTRACT, - DECIMAL_POINT, - DIVIDE, - SEMI_COLON, - EQUAL_SIGN, - COMMA, - DASH, - PERIOD, - FORWARD_SLASH, - GRAVE_ACCENT, - OPEN_BRACKET, - BACK_SLASH, - CLOSE_BRACKET, - SINGLE_QUOTE - ) + val BACKSPACE: Key = KeyCode.Backspace + val TAB: Key = KeyCode.Tab -> "\t" + val ENTER: Key = KeyCode.Enter -> "\n" + val SHIFT: Key = KeyCode.ShiftLeft + val CTRL: Key = KeyCode.ControlLeft + val ALT: Key = KeyCode.AltLeft + val PAUSE_BREAK: Key = KeyCode.Pause + val CAPS_LOCK: Key = KeyCode.CapsLock + val ESCAPE: Key = KeyCode.Escape + val SPACE: Key = KeyCode.Space -> " " + val PAGE_UP: Key = KeyCode.PageUp + val PAGE_DOWN: Key = KeyCode.PageDown + val END: Key = KeyCode.End + val HOME: Key = KeyCode.Home + val LEFT_ARROW: Key = KeyCode.ArrowLeft + val UP_ARROW: Key = KeyCode.ArrowUp + val RIGHT_ARROW: Key = KeyCode.ArrowRight + val DOWN_ARROW: Key = KeyCode.ArrowDown + val INSERT: Key = KeyCode.Insert + val DELETE: Key = KeyCode.Delete + val KEY_0: Key = KeyCode.Digit0 -> "0" + val KEY_1: Key = KeyCode.Digit1 -> "1" + val KEY_2: Key = KeyCode.Digit2 -> "2" + val KEY_3: Key = KeyCode.Digit3 -> "3" + val KEY_4: Key = KeyCode.Digit4 -> "4" + val KEY_5: Key = KeyCode.Digit5 -> "5" + val KEY_6: Key = KeyCode.Digit6 -> "6" + val KEY_7: Key = KeyCode.Digit7 -> "7" + val KEY_8: Key = KeyCode.Digit8 -> "8" + val KEY_9: Key = KeyCode.Digit9 -> "9" + val KEY_A: Key = KeyCode.KeyA -> "A" + val KEY_B: Key = KeyCode.KeyB -> "B" + val KEY_C: Key = KeyCode.KeyC -> "C" + val KEY_D: Key = KeyCode.KeyD -> "D" + val KEY_E: Key = KeyCode.KeyE -> "E" + val KEY_F: Key = KeyCode.KeyF -> "F" + val KEY_G: Key = KeyCode.KeyG -> "G" + val KEY_H: Key = KeyCode.KeyH -> "H" + val KEY_I: Key = KeyCode.KeyI -> "I" + val KEY_J: Key = KeyCode.KeyJ -> "J" + val KEY_K: Key = KeyCode.KeyK -> "K" + val KEY_L: Key = KeyCode.KeyL -> "L" + val KEY_M: Key = KeyCode.KeyM -> "M" + val KEY_N: Key = KeyCode.KeyN -> "N" + val KEY_O: Key = KeyCode.KeyO -> "O" + val KEY_P: Key = KeyCode.KeyP -> "P" + val KEY_Q: Key = KeyCode.KeyQ -> "Q" + val KEY_R: Key = KeyCode.KeyR -> "R" + val KEY_S: Key = KeyCode.KeyS -> "S" + val KEY_T: Key = KeyCode.KeyT -> "T" + val KEY_U: Key = KeyCode.KeyU -> "U" + val KEY_V: Key = KeyCode.KeyV -> "V" + val KEY_W: Key = KeyCode.KeyW -> "W" + val KEY_X: Key = KeyCode.KeyX -> "X" + val KEY_Y: Key = KeyCode.KeyY -> "Y" + val KEY_Z: Key = KeyCode.KeyZ -> "Z" + val LEFT_META_KEY: Key = KeyCode.MetaLeft + val RIGHT_WINDOW_KEY: Key = KeyCode.MetaRight + val SELECT_KEY: Key = KeyCode.Select + val NUMPAD_0: Key = KeyCode.Numpad0 -> "0" + val NUMPAD_1: Key = KeyCode.Numpad1 -> "1" + val NUMPAD_2: Key = KeyCode.Numpad2 -> "2" + val NUMPAD_3: Key = KeyCode.Numpad3 -> "3" + val NUMPAD_4: Key = KeyCode.Numpad4 -> "4" + val NUMPAD_5: Key = KeyCode.Numpad5 -> "5" + val NUMPAD_6: Key = KeyCode.Numpad6 -> "6" + val NUMPAD_7: Key = KeyCode.Numpad7 -> "7" + val NUMPAD_8: Key = KeyCode.Numpad8 -> "8" + val NUMPAD_9: Key = KeyCode.Numpad9 -> "9" + val MULTIPLY: Key = KeyCode.NumpadMultiply -> "*" + val ADD: Key = KeyCode.NumpadAdd -> "+" + val SUBTRACT: Key = KeyCode.NumpadSubtract -> "-" + val DECIMAL_POINT: Key = KeyCode.NumpadDecimal -> "." + val DIVIDE: Key = KeyCode.NumpadDivide -> "/" + val F1: Key = KeyCode.F1 + val F2: Key = KeyCode.F2 + val F3: Key = KeyCode.F3 + val F4: Key = KeyCode.F4 + val F5: Key = KeyCode.F5 + val F6: Key = KeyCode.F6 + val F7: Key = KeyCode.F7 + val F8: Key = KeyCode.F8 + val F9: Key = KeyCode.F9 + val F10: Key = KeyCode.F10 + val F11: Key = KeyCode.F11 + val F12: Key = KeyCode.F12 + val NUM_LOCK: Key = KeyCode.NumLock + val SCROLL_LOCK: Key = KeyCode.ScrollLock + val SEMI_COLON: Key = KeyCode.Semicolon -> ";" + val EQUAL_SIGN: Key = KeyCode.Equal -> "=" + val COMMA: Key = KeyCode.Comma -> "," + val DASH: Key = KeyCode.Minus -> "-" + val PERIOD: Key = KeyCode.Period -> "." + val FORWARD_SLASH: Key = KeyCode.Slash -> "/" + val BACK_QUOTE: Key = KeyCode.Backquote -> "`" + val OPEN_BRACKET: Key = KeyCode.BracketLeft -> "(" + val BACK_SLASH: Key = KeyCode.Backslash -> "\\" + val CLOSE_BRACKET: Key = KeyCode.BracketRight -> ")" + val SINGLE_QUOTE: Key = KeyCode.Quote -> "\'" } diff --git a/indigo/indigo/src/main/scala/indigo/shared/constants/KeyCode.scala b/indigo/indigo/src/main/scala/indigo/shared/constants/KeyCode.scala new file mode 100644 index 000000000..5a99934ce --- /dev/null +++ b/indigo/indigo/src/main/scala/indigo/shared/constants/KeyCode.scala @@ -0,0 +1,254 @@ +package indigo.shared.constants + +import indigo.shared.collections.Batch + +enum KeyCode(val value: String) derives CanEqual: + case Backspace extends KeyCode("Backspace") + case Tab extends KeyCode("Tab") + case Enter extends KeyCode("Enter") + case PauseBreak extends KeyCode("PauseBreak") + case CapsLock extends KeyCode("CapsLock") + case Escape extends KeyCode("Escape") + case Space extends KeyCode("Space") + case PageUp extends KeyCode("PageUp") + case PageDown extends KeyCode("PageDown") + case End extends KeyCode("End") + case Home extends KeyCode("Home") + case ArrowLeft extends KeyCode("ArrowLeft") + case ArrowUp extends KeyCode("ArrowUp") + case ArrowRight extends KeyCode("ArrowRight") + case ArrowDown extends KeyCode("ArrowDown") + case Insert extends KeyCode("Insert") + case Delete extends KeyCode("Delete") + case Minus extends KeyCode("Minus") + case Equal extends KeyCode("Equal") + case BracketLeft extends KeyCode("BracketLeft") + case BracketRight extends KeyCode("BracketRight") + case ControlLeft extends KeyCode("ControlLeft") + case ControlRight extends KeyCode("ControlRight") + case Semicolon extends KeyCode("Semicolon") + case Quote extends KeyCode("Quote") + case Backquote extends KeyCode("Backquote") + case ShiftLeft extends KeyCode("ShiftLeft") + case Backslash extends KeyCode("Backslash") + case Comma extends KeyCode("Comma") + case Period extends KeyCode("Period") + case Slash extends KeyCode("Slash") + case ShiftRight extends KeyCode("ShiftRight") + case AltLeft extends KeyCode("AltLeft") + case AltRight extends KeyCode("AltRight") + case Pause extends KeyCode("Pause") + case ScrollLock extends KeyCode("ScrollLock") + case Props extends KeyCode("Props") + case Undo extends KeyCode("Undo") + case Select extends KeyCode("Select") + case Copy extends KeyCode("Copy") + case Open extends KeyCode("Open") + case Paste extends KeyCode("Paste") + case Find extends KeyCode("Find") + case Cut extends KeyCode("Cut") + case Help extends KeyCode("Help") + case Numpad0 extends KeyCode("Numpad0") + case Numpad1 extends KeyCode("Numpad1") + case Numpad2 extends KeyCode("NumPad2") + case Numpad3 extends KeyCode("Numpad3") + case Numpad4 extends KeyCode("NumPad4") + case Numpad5 extends KeyCode("Numpad5") + case Numpad6 extends KeyCode("NumPad6") + case Numpad7 extends KeyCode("Numpad7") + case Numpad8 extends KeyCode("NumPad8") + case Numpad9 extends KeyCode("NumPad9") + case NumpadDecimal extends KeyCode("NumpadDecimal") + case NumpadMultiply extends KeyCode("NumpadMultiply") + case NumpadEqual extends KeyCode("NumpadEqual") + case NumpadComma extends KeyCode("NumpadComma") + case NumpadEnter extends KeyCode("NumpadEnter") + case NumpadDivide extends KeyCode("NumpadDivide") + case NumpadAdd extends KeyCode("NumpadAdd") + case NumpadSubtract extends KeyCode("NumpadSubtract") + case NumpadParenLeft extends KeyCode("NumpadParenLeft") + case NumpadParenRight extends KeyCode("NumpadParenRight") + case IntlBackslash extends KeyCode("IntlBackslash") + case KanaMode extends KeyCode("KanaMode") + case Convert extends KeyCode("Convert") + case NonConvert extends KeyCode("NonConvert") + case Lang1 extends KeyCode("Lang1") + case Lang2 extends KeyCode("Lang2") + case Lang3 extends KeyCode("Lang3") + case Lang4 extends KeyCode("Lang4") + case Lang5 extends KeyCode("Lang5") + case IntlRo extends KeyCode("IntlRo") + case IntlYen extends KeyCode("IntlYen") + case MediaTrackPrevious extends KeyCode("MediaTrackPrevious") + case MediaTrackNext extends KeyCode("MediaTrackNext") + case MediaPlayPause extends KeyCode("MediaPlayPause") + case MediaStop extends KeyCode("MediaStop") + case MediaSelect extends KeyCode("MediaSelect") + case AudioVolumeMute extends KeyCode("AudioVolumeMute") + case AudioVolumeDown extends KeyCode("AudioVolumeDown") + case AudioVolumeUp extends KeyCode("AudioVolumeUp") + case LaunchApp1 extends KeyCode("LaunchApp1") + case LaunchApp2 extends KeyCode("LaunchApp2") + case LaunchMail extends KeyCode("LaunchMail") + case BrowserHome extends KeyCode("BrowserHome") + case BrowserSearch extends KeyCode("BrowserSearch") + case BrowserFavorites extends KeyCode("BrowserFavorites") + case BrowserRefresh extends KeyCode("BrowserRefresh") + case BrowserStop extends KeyCode("BrowserStop") + case BrowserForward extends KeyCode("BrowserForward") + case BrowserBack extends KeyCode("BrowserBack") + case PrintScreen extends KeyCode("PrintScreen") + case NumLock extends KeyCode("NumLock") + case MetaLeft extends KeyCode("MetaLeft") + case MetaRight extends KeyCode("MetaRight") + case ContextMenu extends KeyCode("ContextMenu") + case Again extends KeyCode("Again") + case Power extends KeyCode("Power") + case Sleep extends KeyCode("Sleep") + case WakeUp extends KeyCode("WakeUp") + case Eject extends KeyCode("Eject") + case F1 extends KeyCode("F1") + case F2 extends KeyCode("F2") + case F3 extends KeyCode("F3") + case F4 extends KeyCode("F4") + case F5 extends KeyCode("F5") + case F6 extends KeyCode("F6") + case F7 extends KeyCode("F7") + case F8 extends KeyCode("F8") + case F9 extends KeyCode("F9") + case F10 extends KeyCode("F10") + case F11 extends KeyCode("F11") + case F12 extends KeyCode("F12") + case F13 extends KeyCode("F13") + case F14 extends KeyCode("F14") + case F15 extends KeyCode("F15") + case F16 extends KeyCode("F16") + case F17 extends KeyCode("F17") + case F18 extends KeyCode("F18") + case F19 extends KeyCode("F19") + case F20 extends KeyCode("F20") + case F21 extends KeyCode("F21") + case F22 extends KeyCode("F22") + case F23 extends KeyCode("F23") + case F24 extends KeyCode("F24") + case Digit0 extends KeyCode("Digit0") + case Digit1 extends KeyCode("Digit1") + case Digit2 extends KeyCode("Digit2") + case Digit3 extends KeyCode("Digit3") + case Digit4 extends KeyCode("Digit4") + case Digit5 extends KeyCode("Digit5") + case Digit6 extends KeyCode("Digit6") + case Digit7 extends KeyCode("Digit7") + case Digit8 extends KeyCode("Digit8") + case Digit9 extends KeyCode("Digit9") + case KeyA extends KeyCode("KeyA") + case KeyB extends KeyCode("KeyB") + case KeyC extends KeyCode("KeyC") + case KeyD extends KeyCode("KeyD") + case KeyE extends KeyCode("KeyE") + case KeyF extends KeyCode("KeyF") + case KeyG extends KeyCode("KeyG") + case KeyH extends KeyCode("KeyH") + case KeyI extends KeyCode("KeyI") + case KeyJ extends KeyCode("KeyJ") + case KeyK extends KeyCode("KeyK") + case KeyL extends KeyCode("KeyL") + case KeyM extends KeyCode("KeyM") + case KeyN extends KeyCode("KeyN") + case KeyO extends KeyCode("KeyO") + case KeyP extends KeyCode("KeyP") + case KeyQ extends KeyCode("KeyQ") + case KeyR extends KeyCode("KeyR") + case KeyS extends KeyCode("KeyS") + case KeyT extends KeyCode("KeyT") + case KeyU extends KeyCode("KeyU") + case KeyV extends KeyCode("KeyV") + case KeyW extends KeyCode("KeyW") + case KeyX extends KeyCode("KeyX") + case KeyY extends KeyCode("KeyY") + case KeyZ extends KeyCode("KeyZ") + case Unidentified extends KeyCode("Unidentified") + +object KeyCode: + def fromString(value: String) = value match { + case "VolumeUp" => KeyCode.AudioVolumeUp + case "VolumeDown" => KeyCode.AudioVolumeDown + case "VolumeMute" => KeyCode.AudioVolumeMute + case _ => + KeyCode.values + .find(_.value == value) + .getOrElse(KeyCode.Unidentified) + } + + lazy val printable: Batch[KeyCode] = Batch( + KeyCode.Space, + KeyCode.Digit0, + KeyCode.Digit1, + KeyCode.Digit2, + KeyCode.Digit3, + KeyCode.Digit4, + KeyCode.Digit5, + KeyCode.Digit6, + KeyCode.Digit7, + KeyCode.Digit8, + KeyCode.Digit9, + KeyCode.KeyA, + KeyCode.KeyB, + KeyCode.KeyC, + KeyCode.KeyD, + KeyCode.KeyE, + KeyCode.KeyF, + KeyCode.KeyG, + KeyCode.KeyH, + KeyCode.KeyI, + KeyCode.KeyJ, + KeyCode.KeyK, + KeyCode.KeyL, + KeyCode.KeyM, + KeyCode.KeyN, + KeyCode.KeyO, + KeyCode.KeyP, + KeyCode.KeyQ, + KeyCode.KeyR, + KeyCode.KeyS, + KeyCode.KeyT, + KeyCode.KeyU, + KeyCode.KeyV, + KeyCode.KeyW, + KeyCode.KeyX, + KeyCode.KeyY, + KeyCode.KeyZ, + KeyCode.Numpad0, + KeyCode.Numpad1, + KeyCode.Numpad2, + KeyCode.Numpad3, + KeyCode.Numpad4, + KeyCode.Numpad5, + KeyCode.Numpad6, + KeyCode.Numpad7, + KeyCode.Numpad8, + KeyCode.Numpad9, + KeyCode.NumpadDecimal, + KeyCode.NumpadMultiply, + KeyCode.NumpadEqual, + KeyCode.NumpadComma, + KeyCode.NumpadDivide, + KeyCode.NumpadAdd, + KeyCode.NumpadEnter, + KeyCode.NumpadSubtract, + KeyCode.NumpadParenLeft, + KeyCode.NumpadParenRight, + KeyCode.Backslash, + KeyCode.Comma, + KeyCode.Period, + KeyCode.Slash, + KeyCode.Minus, + KeyCode.Equal, + KeyCode.BracketLeft, + KeyCode.BracketRight, + KeyCode.Semicolon, + KeyCode.Quote, + KeyCode.Backquote, + KeyCode.Tab, + KeyCode.Enter + ) diff --git a/indigo/indigo/src/main/scala/indigo/shared/constants/KeyLocation.scala b/indigo/indigo/src/main/scala/indigo/shared/constants/KeyLocation.scala new file mode 100644 index 000000000..bab89151f --- /dev/null +++ b/indigo/indigo/src/main/scala/indigo/shared/constants/KeyLocation.scala @@ -0,0 +1,17 @@ +package indigo.shared.constants + +enum KeyLocation derives CanEqual: + case Standard + case Left + case Right + case Numpad + case Unknown + +object KeyLocation: + def fromInt(i: Int): KeyLocation = + i match + case 0 => KeyLocation.Standard + case 1 => KeyLocation.Left + case 2 => KeyLocation.Right + case 3 => KeyLocation.Numpad + case _ => KeyLocation.Unknown diff --git a/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala b/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala index 5b50b2815..952abafc9 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala @@ -8,6 +8,7 @@ import indigo.shared.collections.Batch import indigo.shared.config.GameViewport import indigo.shared.config.RenderingTechnology import indigo.shared.constants.Key +import indigo.shared.constants.KeyLocation import indigo.shared.datatypes.BindingKey import indigo.shared.datatypes.Point import indigo.shared.datatypes.RGBA @@ -655,23 +656,86 @@ object PointerEvent: /** Represents all keyboard events */ sealed trait KeyboardEvent extends InputEvent { - val keyCode: Key + val key: Key + val isRepeat: Boolean + val isAltKeyDown: Boolean + val isCtrlKeyDown: Boolean + val isMetaKeyDown: Boolean + val isShiftKeyDown: Boolean } object KeyboardEvent { /** A key was released during the last frame * - * @param keyCode - * The code and the JavaScript `String` representation - */ - final case class KeyUp(keyCode: Key) extends KeyboardEvent + * @param key + * A `Key` instance representing the key that was released + * @param isRepeat + * Whether the key was pressed repeatedly since the last frame + * @param isAltKeyDown + * Whether the `alt` key was pressed when the event was fired + * @param isCtrlKeyDown + * Whether the `ctrl` key was pressed when the event was fired + * @param isMetaKeyDown + * Whether the meta button (Windows key, or Cmd Key) key was pressed when the event was fired + * @param isShiftKeyDown + * Whether the `shift` key was pressed when the event was fired + */ + final case class KeyUp( + key: Key, + isRepeat: Boolean, + isAltKeyDown: Boolean, + isCtrlKeyDown: Boolean, + isMetaKeyDown: Boolean, + isShiftKeyDown: Boolean + ) extends KeyboardEvent + object KeyUp: + def apply(key: Key): KeyUp = + KeyUp( + key, + isRepeat = false, + isAltKeyDown = false, + isCtrlKeyDown = false, + isMetaKeyDown = false, + isShiftKeyDown = false + ) + def unapply(e: KeyUp): Option[Key] = + Option(e.key) /** A key was pressed down during the last frame * - * @param keyCode - * The code and the JavaScript `String` representation - */ - final case class KeyDown(keyCode: Key) extends KeyboardEvent + * @param key + * A `Key` instance representing the key that was pressed + * @param isRepeat + * Whether the key was pressed repeatedly since the last frame + * @param isAltKeyDown + * Whether the `alt` key was pressed when the event was fired + * @param isCtrlKeyDown + * Whether the `ctrl` key was pressed when the event was fired + * @param isMetaKeyDown + * Whether the meta button (Windows key, or Cmd Key) key was pressed when the event was fired + * @param isShiftKeyDown + * Whether the `shift` key was pressed when the event was fired + */ + final case class KeyDown( + key: Key, + isRepeat: Boolean, + isAltKeyDown: Boolean, + isCtrlKeyDown: Boolean, + isMetaKeyDown: Boolean, + isShiftKeyDown: Boolean + ) extends KeyboardEvent + object KeyDown: + def apply(key: Key): KeyDown = + KeyDown( + key, + isRepeat = false, + isAltKeyDown = false, + isCtrlKeyDown = false, + isMetaKeyDown = false, + isShiftKeyDown = false + ) + def unapply(e: KeyDown): Option[Key] = + Option(e.key) } /** Can be emitted to trigger the one time play back of a sound asset. diff --git a/indigo/indigo/src/main/scala/indigo/shared/input/Keyboard.scala b/indigo/indigo/src/main/scala/indigo/shared/input/Keyboard.scala index 94db8d0d4..3416d458d 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/input/Keyboard.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/input/Keyboard.scala @@ -5,13 +5,18 @@ import indigo.shared.constants.Key import indigo.shared.events.KeyboardEvent import scala.annotation.tailrec +import indigo.shared.constants.KeyCode -final class Keyboard(keyboardEvents: Batch[KeyboardEvent], val keysDown: Batch[Key], val lastKeyHeldDown: Option[Key]) { +final class Keyboard( + keyboardEvents: Batch[KeyboardEvent], + val keysDown: Batch[KeyCode], + val lastKeyHeldDown: Option[KeyCode] +) { - lazy val keysReleased: Batch[Key] = keyboardEvents.collect { case k: KeyboardEvent.KeyUp => k.keyCode } + lazy val keysReleased: Batch[KeyCode] = keyboardEvents.collect { case k: KeyboardEvent.KeyUp => k.key.code } - def keysAreDown(keys: Key*): Boolean = keys.forall(keyCode => keysDown.contains(keyCode)) - def keysAreUp(keys: Key*): Boolean = keys.forall(keyCode => keysReleased.contains(keyCode)) + def keysAreDown(keys: KeyCode*): Boolean = keys.forall(keyCode => keysDown.contains(keyCode)) + def keysAreUp(keys: KeyCode*): Boolean = keys.forall(keyCode => keysReleased.contains(keyCode)) } object Keyboard { @@ -31,18 +36,20 @@ object Keyboard { private given CanEqual[Batch[KeyboardEvent], Batch[KeyboardEvent]] = CanEqual.derived - def calculateKeysDown(keyboardEvents: Batch[KeyboardEvent], previousKeysDown: Batch[Key]): Batch[Key] = { + def calculateKeysDown(keyboardEvents: Batch[KeyboardEvent], previousKeysDown: Batch[KeyCode]): Batch[KeyCode] = { @tailrec - def rec(remaining: List[KeyboardEvent], keysDownAcc: List[Key]): Batch[Key] = + def rec(remaining: List[KeyboardEvent], keysDownAcc: List[KeyCode]): Batch[KeyCode] = remaining match { case Nil => Batch.fromList(keysDownAcc.reverse) case KeyboardEvent.KeyDown(k) :: tl => - rec(tl, k :: keysDownAcc) + rec(tl, k.code :: keysDownAcc) case KeyboardEvent.KeyUp(k) :: tl => - rec(tl, keysDownAcc.filterNot(p => p === k)) + rec(tl, keysDownAcc.filterNot(p => p == k.code)) + + case _ => rec(remaining.tail, keysDownAcc) } rec(keyboardEvents.toList, previousKeysDown.reverse.toList) diff --git a/indigo/indigo/src/test/scala/indigo/shared/constants/Key.scala b/indigo/indigo/src/test/scala/indigo/shared/constants/Key.scala index a70e8a9f8..4515dd494 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/constants/Key.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/constants/Key.scala @@ -1,8 +1,8 @@ package indigo.shared.constants class KeyTests extends munit.FunSuite { - private val upperJ = Key(74, "J") - private val lowerJ = Key(74, "j") + private val upperJ = Key(KeyCode.KeyJ, "J", KeyLocation.Standard) + private val lowerJ = Key(KeyCode.KeyJ, "j", KeyLocation.Standard) test("Key.equals is case-insensitive") { assertEquals(upperJ, lowerJ)