using System.Runtime.InteropServices; using System.Text; using SDL2; namespace Nerfed.Runtime; public static class Keyboard { private static readonly List textInput = new List(); private const int maxPressedKeys = 8; private static readonly List keys = new List(maxPressedKeys); private static readonly List lastKeys = new List(maxPressedKeys); /// /// True if the key was pressed or continued to be held down this frame. /// /// /// public static bool IsKeyDown(Key key) { return HasKey(keys, key); } /// /// True if the key was pressed this frame. /// public static bool IsKeyPressed(Key key) { return HasKey(keys, key) && !HasKey(lastKeys, key); } /// /// True if the key was released or continued to be released this frame. /// public static bool IsKeyUp(Key key) { return !HasKey(keys, key); } /// /// True if the key was released this frame. /// public static bool IsKeyReleased(Key key) { return !HasKey(keys, key) && HasKey(lastKeys, key); } public static ReadOnlySpan GetPressedKeys() { return CollectionsMarshal.AsSpan(keys); } public static ReadOnlySpan GetTextInput() { return CollectionsMarshal.AsSpan(textInput); } internal static void Update() { textInput.Clear(); lastKeys.Clear(); if (keys.Count > 0) { lastKeys.AddRange(keys); } } private static bool HasKey(List list, Key key) { for (int i = 0; i < list.Count; i++) { if (keys[i] == key) { return true; } } return false; } internal static void ProcessEvent(ref SDL.SDL_Event ev) { switch (ev.type) { case SDL.SDL_EventType.SDL_TEXTINPUT: ProcessTextInputEvent(ref ev.text); break; case SDL.SDL_EventType.SDL_KEYDOWN: ProcessKeyDownEvent(ref ev.key); break; case SDL.SDL_EventType.SDL_KEYUP: ProcessKeyUpEvent(ref ev.key); break; } } private static unsafe void ProcessTextInputEvent(ref SDL.SDL_TextInputEvent evt) { // Based on the SDL2# LPUtf8StrMarshaler int MeasureStringLength(byte* ptr) { int bytes; for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1) ; return bytes; } fixed (byte* textPtr = evt.text) { int bytes = MeasureStringLength(textPtr); if (bytes > 0) { /* UTF8 will never encode more characters * than bytes in a string, so bytes is a * suitable upper estimate of size needed */ char* charsBuffer = stackalloc char[bytes]; int chars = Encoding.UTF8.GetChars( textPtr, bytes, charsBuffer, bytes ); if (chars > 0) { textInput.AddRange(new ReadOnlySpan(charsBuffer, chars)); } } } } private static void ProcessKeyDownEvent(ref SDL.SDL_KeyboardEvent ev) { Key key = ConvertScancode(ev.keysym.scancode); SetKeyState(key, KeyState.Down); } private static void ProcessKeyUpEvent(ref SDL.SDL_KeyboardEvent ev) { Key key = ConvertScancode(ev.keysym.scancode); SetKeyState(key, KeyState.Up); } public static void SetKeyState(Key key, KeyState state) { if (keys.Count >= maxPressedKeys) { return; } if (state == KeyState.Down) { if (!keys.Contains(key)) { keys.Add(key); } } else if (state == KeyState.Up) { keys.Remove(key); } } private static Key ConvertScancode(SDL.SDL_Scancode scancode) { switch (scancode) { case SDL.SDL_Scancode.SDL_SCANCODE_A: return Key.A; case SDL.SDL_Scancode.SDL_SCANCODE_B: return Key.B; case SDL.SDL_Scancode.SDL_SCANCODE_C: return Key.C; case SDL.SDL_Scancode.SDL_SCANCODE_D: return Key.D; case SDL.SDL_Scancode.SDL_SCANCODE_E: return Key.E; case SDL.SDL_Scancode.SDL_SCANCODE_F: return Key.F; case SDL.SDL_Scancode.SDL_SCANCODE_G: return Key.G; case SDL.SDL_Scancode.SDL_SCANCODE_H: return Key.H; case SDL.SDL_Scancode.SDL_SCANCODE_I: return Key.I; case SDL.SDL_Scancode.SDL_SCANCODE_J: return Key.J; case SDL.SDL_Scancode.SDL_SCANCODE_K: return Key.K; case SDL.SDL_Scancode.SDL_SCANCODE_L: return Key.L; case SDL.SDL_Scancode.SDL_SCANCODE_M: return Key.M; case SDL.SDL_Scancode.SDL_SCANCODE_N: return Key.N; case SDL.SDL_Scancode.SDL_SCANCODE_O: return Key.O; case SDL.SDL_Scancode.SDL_SCANCODE_P: return Key.P; case SDL.SDL_Scancode.SDL_SCANCODE_Q: return Key.Q; case SDL.SDL_Scancode.SDL_SCANCODE_R: return Key.R; case SDL.SDL_Scancode.SDL_SCANCODE_S: return Key.S; case SDL.SDL_Scancode.SDL_SCANCODE_T: return Key.T; case SDL.SDL_Scancode.SDL_SCANCODE_U: return Key.U; case SDL.SDL_Scancode.SDL_SCANCODE_V: return Key.V; case SDL.SDL_Scancode.SDL_SCANCODE_W: return Key.W; case SDL.SDL_Scancode.SDL_SCANCODE_X: return Key.X; case SDL.SDL_Scancode.SDL_SCANCODE_Y: return Key.Y; case SDL.SDL_Scancode.SDL_SCANCODE_Z: return Key.Z; case SDL.SDL_Scancode.SDL_SCANCODE_1: return Key.D1; case SDL.SDL_Scancode.SDL_SCANCODE_2: return Key.D2; case SDL.SDL_Scancode.SDL_SCANCODE_3: return Key.D3; case SDL.SDL_Scancode.SDL_SCANCODE_4: return Key.D4; case SDL.SDL_Scancode.SDL_SCANCODE_5: return Key.D5; case SDL.SDL_Scancode.SDL_SCANCODE_6: return Key.D6; case SDL.SDL_Scancode.SDL_SCANCODE_7: return Key.D7; case SDL.SDL_Scancode.SDL_SCANCODE_8: return Key.D8; case SDL.SDL_Scancode.SDL_SCANCODE_9: return Key.D9; case SDL.SDL_Scancode.SDL_SCANCODE_0: return Key.D0; case SDL.SDL_Scancode.SDL_SCANCODE_RETURN: return Key.Enter; case SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE: return Key.Escape; case SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE: return Key.Backspace; case SDL.SDL_Scancode.SDL_SCANCODE_TAB: return Key.Tab; case SDL.SDL_Scancode.SDL_SCANCODE_SPACE: return Key.Space; case SDL.SDL_Scancode.SDL_SCANCODE_MINUS: return Key.Minus; case SDL.SDL_Scancode.SDL_SCANCODE_EQUALS: return Key.Equals; case SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET: return Key.LeftBracket; case SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET: return Key.RightBracket; case SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH: return Key.Backslash; case SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON: return Key.Semicolon; case SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE: return Key.Apostrophe; case SDL.SDL_Scancode.SDL_SCANCODE_GRAVE: return Key.Tilde; case SDL.SDL_Scancode.SDL_SCANCODE_COMMA: return Key.Comma; case SDL.SDL_Scancode.SDL_SCANCODE_PERIOD: return Key.Period; case SDL.SDL_Scancode.SDL_SCANCODE_SLASH: return Key.Slash; case SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK: return Key.CapsLock; case SDL.SDL_Scancode.SDL_SCANCODE_F1: return Key.F1; case SDL.SDL_Scancode.SDL_SCANCODE_F2: return Key.F2; case SDL.SDL_Scancode.SDL_SCANCODE_F3: return Key.F3; case SDL.SDL_Scancode.SDL_SCANCODE_F4: return Key.F4; case SDL.SDL_Scancode.SDL_SCANCODE_F5: return Key.F5; case SDL.SDL_Scancode.SDL_SCANCODE_F6: return Key.F6; case SDL.SDL_Scancode.SDL_SCANCODE_F7: return Key.F7; case SDL.SDL_Scancode.SDL_SCANCODE_F8: return Key.F8; case SDL.SDL_Scancode.SDL_SCANCODE_F9: return Key.F9; case SDL.SDL_Scancode.SDL_SCANCODE_F10: return Key.F10; case SDL.SDL_Scancode.SDL_SCANCODE_F11: return Key.F11; case SDL.SDL_Scancode.SDL_SCANCODE_F12: return Key.F12; case SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN: return Key.PrintScreen; case SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK: return Key.Scroll; case SDL.SDL_Scancode.SDL_SCANCODE_PAUSE: return Key.Pause; case SDL.SDL_Scancode.SDL_SCANCODE_INSERT: return Key.Insert; case SDL.SDL_Scancode.SDL_SCANCODE_HOME: return Key.Home; case SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP: return Key.PageUp; case SDL.SDL_Scancode.SDL_SCANCODE_DELETE: return Key.Delete; case SDL.SDL_Scancode.SDL_SCANCODE_END: return Key.End; case SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN: return Key.PageDown; case SDL.SDL_Scancode.SDL_SCANCODE_RIGHT: return Key.Right; case SDL.SDL_Scancode.SDL_SCANCODE_LEFT: return Key.Left; case SDL.SDL_Scancode.SDL_SCANCODE_DOWN: return Key.Down; case SDL.SDL_Scancode.SDL_SCANCODE_UP: return Key.Up; case SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR: return Key.NumLock; case SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE: return Key.Divide; case SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY: return Key.Multiply; case SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS: return Key.Subtract; case SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS: return Key.Add; case SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER: return Key.Enter; case SDL.SDL_Scancode.SDL_SCANCODE_KP_1: return Key.NumPad1; case SDL.SDL_Scancode.SDL_SCANCODE_KP_2: return Key.NumPad2; case SDL.SDL_Scancode.SDL_SCANCODE_KP_3: return Key.NumPad3; case SDL.SDL_Scancode.SDL_SCANCODE_KP_4: return Key.NumPad4; case SDL.SDL_Scancode.SDL_SCANCODE_KP_5: return Key.NumPad5; case SDL.SDL_Scancode.SDL_SCANCODE_KP_6: return Key.NumPad6; case SDL.SDL_Scancode.SDL_SCANCODE_KP_7: return Key.NumPad7; case SDL.SDL_Scancode.SDL_SCANCODE_KP_8: return Key.NumPad8; case SDL.SDL_Scancode.SDL_SCANCODE_KP_9: return Key.NumPad9; case SDL.SDL_Scancode.SDL_SCANCODE_KP_0: return Key.NumPad0; case SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD: return Key.Period; case SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION: return Key.Apps; case SDL.SDL_Scancode.SDL_SCANCODE_KP_EQUALS: return Key.Equals; case SDL.SDL_Scancode.SDL_SCANCODE_F13: return Key.F13; case SDL.SDL_Scancode.SDL_SCANCODE_F14: return Key.F14; case SDL.SDL_Scancode.SDL_SCANCODE_F15: return Key.F15; case SDL.SDL_Scancode.SDL_SCANCODE_F16: return Key.F16; case SDL.SDL_Scancode.SDL_SCANCODE_F17: return Key.F17; case SDL.SDL_Scancode.SDL_SCANCODE_F18: return Key.F18; case SDL.SDL_Scancode.SDL_SCANCODE_F19: return Key.F19; case SDL.SDL_Scancode.SDL_SCANCODE_F20: return Key.F20; case SDL.SDL_Scancode.SDL_SCANCODE_F21: return Key.F21; case SDL.SDL_Scancode.SDL_SCANCODE_F22: return Key.F22; case SDL.SDL_Scancode.SDL_SCANCODE_F23: return Key.F23; case SDL.SDL_Scancode.SDL_SCANCODE_F24: return Key.F24; case SDL.SDL_Scancode.SDL_SCANCODE_EXECUTE: return Key.Execute; case SDL.SDL_Scancode.SDL_SCANCODE_HELP: return Key.Help; case SDL.SDL_Scancode.SDL_SCANCODE_MENU: return Key.Apps; case SDL.SDL_Scancode.SDL_SCANCODE_SELECT: return Key.Select; case SDL.SDL_Scancode.SDL_SCANCODE_KP_COMMA: return Key.Comma; case SDL.SDL_Scancode.SDL_SCANCODE_LCTRL: return Key.LeftControl; case SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT: return Key.LeftShift; case SDL.SDL_Scancode.SDL_SCANCODE_LALT: return Key.LeftAlt; case SDL.SDL_Scancode.SDL_SCANCODE_LGUI: return Key.LeftSuper; case SDL.SDL_Scancode.SDL_SCANCODE_RCTRL: return Key.RightControl; case SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT: return Key.RightShift; case SDL.SDL_Scancode.SDL_SCANCODE_RALT: return Key.RightAlt; case SDL.SDL_Scancode.SDL_SCANCODE_RGUI: return Key.RightSuper; default: return Key.None; } } }