From c9ebd448f90a48522d8b2575e012e7268609b352 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Thu, 16 Apr 2026 23:03:24 +0200 Subject: [PATCH] update --- implementations/Crafter.Graphics-Device.cpp | 72 ++++++--- implementations/Crafter.Graphics-Font.cpp | 11 ++ implementations/Crafter.Graphics-Window.cpp | 86 +++++----- interfaces/Crafter.Graphics-Font.cppm | 3 +- ...ter.Graphics-RenderingElement2DVulkan.cppm | 29 ++-- interfaces/Crafter.Graphics-Types.cppm | 147 +++++++++++++++++- interfaces/Crafter.Graphics-Window.cppm | 1 + 7 files changed, 277 insertions(+), 72 deletions(-) diff --git a/implementations/Crafter.Graphics-Device.cpp b/implementations/Crafter.Graphics-Device.cpp index b027a26..d65d583 100644 --- a/implementations/Crafter.Graphics-Device.cpp +++ b/implementations/Crafter.Graphics-Device.cpp @@ -211,6 +211,33 @@ constexpr CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym) case XKB_KEY_y: return CrafterKeys::Y; case XKB_KEY_z: return CrafterKeys::Z; + case XKB_KEY_A: return CrafterKeys::A; + case XKB_KEY_B: return CrafterKeys::B; + case XKB_KEY_C: return CrafterKeys::C; + case XKB_KEY_D: return CrafterKeys::D; + case XKB_KEY_E: return CrafterKeys::E; + case XKB_KEY_F: return CrafterKeys::F; + case XKB_KEY_G: return CrafterKeys::G; + case XKB_KEY_H: return CrafterKeys::H; + case XKB_KEY_I: return CrafterKeys::I; + case XKB_KEY_J: return CrafterKeys::J; + case XKB_KEY_K: return CrafterKeys::K; + case XKB_KEY_L: return CrafterKeys::L; + case XKB_KEY_M: return CrafterKeys::M; + case XKB_KEY_N: return CrafterKeys::N; + case XKB_KEY_O: return CrafterKeys::O; + case XKB_KEY_P: return CrafterKeys::P; + case XKB_KEY_Q: return CrafterKeys::Q; + case XKB_KEY_R: return CrafterKeys::R; + case XKB_KEY_S: return CrafterKeys::S; + case XKB_KEY_T: return CrafterKeys::T; + case XKB_KEY_U: return CrafterKeys::U; + case XKB_KEY_V: return CrafterKeys::V; + case XKB_KEY_W: return CrafterKeys::W; + case XKB_KEY_X: return CrafterKeys::X; + case XKB_KEY_Y: return CrafterKeys::Y; + case XKB_KEY_Z: return CrafterKeys::Z; + // Numbers case XKB_KEY_0: return CrafterKeys::_0; case XKB_KEY_1: return CrafterKeys::_1; @@ -300,8 +327,7 @@ constexpr CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym) case XKB_KEY_period: return CrafterKeys::period; case XKB_KEY_slash: return CrafterKeys::slash; - default: - throw std::runtime_error(std::format("Unkown XKB_KEY: {}", sym)); + default: return CrafterKeys::CrafterKeysMax; } } @@ -461,31 +487,39 @@ void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, } - - void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - xkb_keycode_t keycode = key + 8; - xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); + xkb_keycode_t keycode = key + 8; + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); CrafterKeys crafterKey = keysym_to_crafter_key(keysym); - if(state == WL_KEYBOARD_KEY_STATE_PRESSED) { - if(Device::focusedWindow->heldkeys[static_cast(crafterKey)]) { - Device::focusedWindow->onKeyHold[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyHold.Invoke(crafterKey); - } else{ - Device::focusedWindow->heldkeys[static_cast(crafterKey)] = true; - Device::focusedWindow->onKeyDown[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyDown.Invoke(crafterKey); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (focusedWindow->heldkeys[(std::uint8_t)crafterKey]) { + focusedWindow->onKeyHold[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyHold.Invoke(crafterKey); + } else { + focusedWindow->heldkeys[(std::uint8_t)crafterKey] = true; + focusedWindow->onKeyDown[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyDown.Invoke(crafterKey); } - } else{ - Device::focusedWindow->heldkeys[static_cast(crafterKey)] = false; - Device::focusedWindow->onKeyUp[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyUp.Invoke(crafterKey); + + std::string buf; + buf.resize(16); + int n = xkb_state_key_get_utf8(xkb_state, keycode, buf.data(), 16); + if (n > 0) { + if ((unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) { + buf.resize(n); + focusedWindow->onTextInput.Invoke(buf); + } + } + } else { + focusedWindow->heldkeys[(std::uint8_t)crafterKey] = false; + focusedWindow->onKeyUp[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyUp.Invoke(crafterKey); } } void Device::keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - + xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) { diff --git a/implementations/Crafter.Graphics-Font.cpp b/implementations/Crafter.Graphics-Font.cpp index e101528..7d7c3ca 100644 --- a/implementations/Crafter.Graphics-Font.cpp +++ b/implementations/Crafter.Graphics-Font.cpp @@ -56,4 +56,15 @@ Font::Font(const std::filesystem::path& fontFilePath) { this->ascent = ascent; this->descent = descent; this->lineGap = lineGap; +} + +std::uint32_t Font::GetLineWidth(const std::string_view text, float size) { + float scale = stbtt_ScaleForPixelHeight(&font, size); + std::uint32_t lineWidth = 0; + for (const char c : text) { + int advance, lsb; + stbtt_GetCodepointHMetrics(&font, c, &advance, &lsb); + lineWidth += (int)(advance * scale); + } + return lineWidth; } \ No newline at end of file diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index 7b795ad..57a63ee 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -272,49 +272,65 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { PostQuitMessage(0); break; } - case WM_KEYDOWN:{ - if ((lParam & (1 << 30)) == 0) { // only first press - CrafterKeys crafterKey = vk_to_crafter_key(wParam); - if(window->heldkeys[static_cast(crafterKey)]) { - window->onKeyHold[static_cast(crafterKey)].Invoke(); - window->onAnyKeyHold.Invoke(crafterKey); - } else{ - window->heldkeys[static_cast(crafterKey)] = true; - window->onKeyDown[static_cast(crafterKey)].Invoke(); - window->onAnyKeyDown.Invoke(crafterKey); - } + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { // SYSKEYDOWN catches Alt combos, F10, etc. + CrafterKeys crafterKey = vk_to_crafter_key(wParam); + bool isRepeat = (lParam & (1 << 30)) != 0; + + if (isRepeat) { + window->onKeyHold[(uint8_t)crafterKey].Invoke(); + window->onAnyKeyHold.Invoke(crafterKey); + } else { + window->heldkeys[(uint8_t)crafterKey] = true; + window->onKeyDown[(uint8_t)crafterKey].Invoke(); + window->onAnyKeyDown.Invoke(crafterKey); } break; } - case WM_KEYUP: { + + case WM_KEYUP: + case WM_SYSKEYUP: { CrafterKeys crafterKey = vk_to_crafter_key(wParam); - window->heldkeys[static_cast(crafterKey)] = false; - window->onKeyUp[static_cast(crafterKey)].Invoke(); + window->heldkeys[(uint8_t)crafterKey] = false; + window->onKeyUp[(uint8_t)crafterKey].Invoke(); window->onAnyKeyUp.Invoke(crafterKey); break; } - case WM_MOUSEMOVE: { - int x = LOWORD(lParam); - int y = HIWORD(lParam); - Vector pos(x, y); - window->currentMousePos = pos; - window->onMouseMove.Invoke(); - for(MouseElement* element : window->mouseElements) { - if(element) { - if(window->currentMousePos.x >= element->scaled.position.x && window->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->currentMousePos.y > element->scaled.position.y && window->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) { - element->onMouseMove.Invoke(); - if(!element->mouseHover) { - element->mouseHover = true; - element->onMouseEnter.Invoke(); - } - } else if(element->mouseHover) { - element->mouseHover = false; - element->onMouseLeave.Invoke(); - } - } - } - window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast(nullptr)), window->mouseElements.end()); + case WM_CHAR: { + // wParam is a UTF-16 code unit. May be a surrogate — buffer until we have a pair. + wchar_t wc = (wchar_t)wParam; + + // Filter control characters (backspace=0x08, tab=0x09, enter=0x0D, escape=0x1B, etc.) + if (wc < 0x20 || wc == 0x7f) break; + + // Handle UTF-16 surrogate pairs (characters outside the BMP, e.g. emoji). + static wchar_t highSurrogate = 0; + wchar_t utf16[2]; + int utf16Len; + + if (wc >= 0xD800 && wc <= 0xDBFF) { + // High surrogate — stash it and wait for the low surrogate. + highSurrogate = wc; + break; + } else if (wc >= 0xDC00 && wc <= 0xDFFF) { + // Low surrogate — pair with the stashed high surrogate. + if (highSurrogate == 0) break; // orphaned low surrogate, ignore + utf16[0] = highSurrogate; + utf16[1] = wc; + utf16Len = 2; + highSurrogate = 0; + } else { + utf16[0] = wc; + utf16Len = 1; + } + + // Convert UTF-16 to UTF-8. + char utf8[8]; + int n = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Len, utf8, sizeof(utf8), nullptr, nullptr); + if (n > 0) { + window->onTextInput.Invoke(std::string(utf8, n)); + } break; } case WM_LBUTTONDOWN: { diff --git a/interfaces/Crafter.Graphics-Font.cppm b/interfaces/Crafter.Graphics-Font.cppm index 77d14dd..3b98b35 100644 --- a/interfaces/Crafter.Graphics-Font.cppm +++ b/interfaces/Crafter.Graphics-Font.cppm @@ -33,6 +33,7 @@ namespace Crafter { std::int_fast32_t descent; std::int_fast32_t lineGap; stbtt_fontinfo font; - Font(const std::filesystem::path& font); + Font(const std::filesystem::path& font); + std::uint32_t GetLineWidth(const std::string_view text, float size); }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index aad409a..81c5543 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -363,8 +363,14 @@ export namespace Crafter { int baseline = (int)(font.ascent * scale); std::uint32_t lineHeight = (font.ascent - font.descent) * scale; std::uint32_t currentY = baseline; - for(std::string_view line : lines) { + std::uint32_t ogOffsetX = offsetX; + std::uint32_t ogOffsetY = offsetY; + + for(std::string_view line : lines) { + offsetX = ogOffsetX; + offsetY = ogOffsetY; + std::int32_t lineWidth = 0; for (const char c : line) { int advance, lsb; @@ -379,7 +385,7 @@ export namespace Crafter { offsetX -= lineWidth / 2; break; case TextAlignment::Right: - offsetX += lineWidth; + offsetX -= lineWidth; break; } @@ -456,21 +462,12 @@ export namespace Crafter { } } - if(alignment != TextAlignment::Right) { - offsetX += (int)(ax * scale); + offsetX += (int)(ax * scale); - if (p + 1 < end) { - int next; - offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); - } - } else { - offsetX -= (int)(ax * scale); - - if (p + 1 < end) { - int next; - offsetX -= (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); - } - } + if (p + 1 < end) { + int next; + offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); + } } currentY += lineHeight; } diff --git a/interfaces/Crafter.Graphics-Types.cppm b/interfaces/Crafter.Graphics-Types.cppm index 8a55f2e..5ae0b8d 100644 --- a/interfaces/Crafter.Graphics-Types.cppm +++ b/interfaces/Crafter.Graphics-Types.cppm @@ -49,7 +49,7 @@ export namespace Crafter { std::chrono::duration delta; }; - enum class CrafterKeys { + enum class CrafterKeys : std::uint8_t { // Alphabetic keys A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, @@ -92,6 +92,151 @@ export namespace Crafter { CrafterKeysMax }; + constexpr std::string CrafterKeyToString(CrafterKeys key) { + switch (key) { + // Alphabetic keys + case CrafterKeys::A: return "A"; + case CrafterKeys::B: return "B"; + case CrafterKeys::C: return "C"; + case CrafterKeys::D: return "D"; + case CrafterKeys::E: return "E"; + case CrafterKeys::F: return "F"; + case CrafterKeys::G: return "G"; + case CrafterKeys::H: return "H"; + case CrafterKeys::I: return "I"; + case CrafterKeys::J: return "J"; + case CrafterKeys::K: return "K"; + case CrafterKeys::L: return "L"; + case CrafterKeys::M: return "M"; + case CrafterKeys::N: return "N"; + case CrafterKeys::O: return "O"; + case CrafterKeys::P: return "P"; + case CrafterKeys::Q: return "Q"; + case CrafterKeys::R: return "R"; + case CrafterKeys::S: return "S"; + case CrafterKeys::T: return "T"; + case CrafterKeys::U: return "U"; + case CrafterKeys::V: return "V"; + case CrafterKeys::W: return "W"; + case CrafterKeys::X: return "X"; + case CrafterKeys::Y: return "Y"; + case CrafterKeys::Z: return "Z"; + + // Numeric keys + case CrafterKeys::_0: return "0"; + case CrafterKeys::_1: return "1"; + case CrafterKeys::_2: return "2"; + case CrafterKeys::_3: return "3"; + case CrafterKeys::_4: return "4"; + case CrafterKeys::_5: return "5"; + case CrafterKeys::_6: return "6"; + case CrafterKeys::_7: return "7"; + case CrafterKeys::_8: return "8"; + case CrafterKeys::_9: return "9"; + + // Function keys + case CrafterKeys::F1: return "F1"; + case CrafterKeys::F2: return "F2"; + case CrafterKeys::F3: return "F3"; + case CrafterKeys::F4: return "F4"; + case CrafterKeys::F5: return "F5"; + case CrafterKeys::F6: return "F6"; + case CrafterKeys::F7: return "F7"; + case CrafterKeys::F8: return "F8"; + case CrafterKeys::F9: return "F9"; + case CrafterKeys::F10: return "F10"; + case CrafterKeys::F11: return "F11"; + case CrafterKeys::F12: return "F12"; + + // Control keys + case CrafterKeys::Escape: return "Escape"; + case CrafterKeys::Tab: return "Tab"; + case CrafterKeys::Enter: return "Enter"; + case CrafterKeys::Space: return "Space"; + case CrafterKeys::Backspace: return "Backspace"; + case CrafterKeys::Delete: return "Delete"; + case CrafterKeys::Insert: return "Insert"; + case CrafterKeys::Home: return "Home"; + case CrafterKeys::End: return "End"; + case CrafterKeys::PageUp: return "PageUp"; + case CrafterKeys::PageDown: return "PageDown"; + case CrafterKeys::CapsLock: return "CapsLock"; + case CrafterKeys::NumLock: return "NumLock"; + case CrafterKeys::ScrollLock: return "ScrollLock"; + + // Modifier keys + case CrafterKeys::LeftShift: return "LeftShift"; + case CrafterKeys::RightShift: return "RightShift"; + case CrafterKeys::LeftCtrl: return "LeftCtrl"; + case CrafterKeys::RightCtrl: return "RightCtrl"; + case CrafterKeys::LeftAlt: return "LeftAlt"; + case CrafterKeys::RightAlt: return "RightAlt"; + case CrafterKeys::LeftSuper: return "LeftSuper"; + case CrafterKeys::RightSuper: return "RightSuper"; + + // Arrow keys + case CrafterKeys::Up: return "Up"; + case CrafterKeys::Down: return "Down"; + case CrafterKeys::Left: return "Left"; + case CrafterKeys::Right: return "Right"; + + // Keypad keys + case CrafterKeys::keypad_0: return "Keypad0"; + case CrafterKeys::keypad_1: return "Keypad1"; + case CrafterKeys::keypad_2: return "Keypad2"; + case CrafterKeys::keypad_3: return "Keypad3"; + case CrafterKeys::keypad_4: return "Keypad4"; + case CrafterKeys::keypad_5: return "Keypad5"; + case CrafterKeys::keypad_6: return "Keypad6"; + case CrafterKeys::keypad_7: return "Keypad7"; + case CrafterKeys::keypad_8: return "Keypad8"; + case CrafterKeys::keypad_9: return "Keypad9"; + case CrafterKeys::keypad_enter: return "KeypadEnter"; + case CrafterKeys::keypad_plus: return "KeypadPlus"; + case CrafterKeys::keypad_minus: return "KeypadMinus"; + case CrafterKeys::keypad_multiply: return "KeypadMultiply"; + case CrafterKeys::keypad_divide: return "KeypadDivide"; + case CrafterKeys::keypad_decimal: return "KeypadDecimal"; + + // Punctuation and special keys + case CrafterKeys::grave: return "Grave"; + case CrafterKeys::minus: return "Minus"; + case CrafterKeys::equal: return "Equal"; + case CrafterKeys::bracket_left: return "BracketLeft"; + case CrafterKeys::bracket_right: return "BracketRight"; + case CrafterKeys::backslash: return "Backslash"; + case CrafterKeys::semicolon: return "Semicolon"; + case CrafterKeys::quote: return "Quote"; + case CrafterKeys::comma: return "Comma"; + case CrafterKeys::period: return "Period"; + case CrafterKeys::slash: return "Slash"; + case CrafterKeys::print_screen: return "PrintScreen"; + case CrafterKeys::pause: return "Pause"; + case CrafterKeys::menu: return "Menu"; + + // Additional keys + case CrafterKeys::volume_up: return "VolumeUp"; + case CrafterKeys::volume_down: return "VolumeDown"; + case CrafterKeys::volume_mute: return "VolumeMute"; + case CrafterKeys::media_play: return "MediaPlay"; + case CrafterKeys::media_stop: return "MediaStop"; + case CrafterKeys::media_prev: return "MediaPrev"; + case CrafterKeys::media_next: return "MediaNext"; + case CrafterKeys::browser_back: return "BrowserBack"; + case CrafterKeys::browser_forward: return "BrowserForward"; + case CrafterKeys::browser_refresh: return "BrowserRefresh"; + case CrafterKeys::browser_stop: return "BrowserStop"; + case CrafterKeys::browser_search: return "BrowserSearch"; + case CrafterKeys::browser_home: return "BrowserHome"; + case CrafterKeys::launch_mail: return "LaunchMail"; + case CrafterKeys::launch_calculator: return "LaunchCalculator"; + case CrafterKeys::launch_media_player:return "LaunchMediaPlayer"; + + case CrafterKeys::CrafterKeysMax: return "Unknown"; + } + return "Unknown"; + } + template constexpr T AlignUp(T value, T2 alignment) { return (value + alignment - 1) & ~(alignment - 1); diff --git a/interfaces/Crafter.Graphics-Window.cppm b/interfaces/Crafter.Graphics-Window.cppm index 0bcb47a..2a8f7ab 100644 --- a/interfaces/Crafter.Graphics-Window.cppm +++ b/interfaces/Crafter.Graphics-Window.cppm @@ -82,6 +82,7 @@ export namespace Crafter { Event onAnyKeyDown; Event onAnyKeyHold; Event onAnyKeyUp; + Event onTextInput; Event onMouseRightClick; Event onMouseLeftClick; Event onMouseRightHold;