This commit is contained in:
Jorijn van der Graaf 2026-04-16 23:03:24 +02:00
commit c9ebd448f9
7 changed files with 278 additions and 73 deletions

View file

@ -211,6 +211,33 @@ constexpr CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym)
case XKB_KEY_y: return CrafterKeys::Y; case XKB_KEY_y: return CrafterKeys::Y;
case XKB_KEY_z: return CrafterKeys::Z; 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 // Numbers
case XKB_KEY_0: return CrafterKeys::_0; case XKB_KEY_0: return CrafterKeys::_0;
case XKB_KEY_1: return CrafterKeys::_1; 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_period: return CrafterKeys::period;
case XKB_KEY_slash: return CrafterKeys::slash; case XKB_KEY_slash: return CrafterKeys::slash;
default: default: return CrafterKeys::CrafterKeysMax;
throw std::runtime_error(std::format("Unkown XKB_KEY: {}", sym));
} }
} }
@ -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) { 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_keycode_t keycode = key + 8;
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode);
CrafterKeys crafterKey = keysym_to_crafter_key(keysym); CrafterKeys crafterKey = keysym_to_crafter_key(keysym);
if(state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if(Device::focusedWindow->heldkeys[static_cast<std::uint8_t>(crafterKey)]) { if (focusedWindow->heldkeys[(std::uint8_t)crafterKey]) {
Device::focusedWindow->onKeyHold[static_cast<std::uint8_t>(crafterKey)].Invoke(); focusedWindow->onKeyHold[(std::uint8_t)crafterKey].Invoke();
Device::focusedWindow->onAnyKeyHold.Invoke(crafterKey); focusedWindow->onAnyKeyHold.Invoke(crafterKey);
} else{ } else {
Device::focusedWindow->heldkeys[static_cast<std::uint8_t>(crafterKey)] = true; focusedWindow->heldkeys[(std::uint8_t)crafterKey] = true;
Device::focusedWindow->onKeyDown[static_cast<std::uint8_t>(crafterKey)].Invoke(); focusedWindow->onKeyDown[(std::uint8_t)crafterKey].Invoke();
Device::focusedWindow->onAnyKeyDown.Invoke(crafterKey); focusedWindow->onAnyKeyDown.Invoke(crafterKey);
} }
} else{
Device::focusedWindow->heldkeys[static_cast<std::uint8_t>(crafterKey)] = false; std::string buf;
Device::focusedWindow->onKeyUp[static_cast<std::uint8_t>(crafterKey)].Invoke(); buf.resize(16);
Device::focusedWindow->onAnyKeyUp.Invoke(crafterKey); 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) { 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) { void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {

View file

@ -57,3 +57,14 @@ Font::Font(const std::filesystem::path& fontFilePath) {
this->descent = descent; this->descent = descent;
this->lineGap = lineGap; 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;
}

View file

@ -272,49 +272,65 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
PostQuitMessage(0); PostQuitMessage(0);
break; break;
} }
case WM_KEYDOWN:{ case WM_KEYDOWN:
if ((lParam & (1 << 30)) == 0) { // only first press case WM_SYSKEYDOWN: { // SYSKEYDOWN catches Alt combos, F10, etc.
CrafterKeys crafterKey = vk_to_crafter_key(wParam); CrafterKeys crafterKey = vk_to_crafter_key(wParam);
if(window->heldkeys[static_cast<std::uint8_t>(crafterKey)]) { bool isRepeat = (lParam & (1 << 30)) != 0;
window->onKeyHold[static_cast<std::uint8_t>(crafterKey)].Invoke();
window->onAnyKeyHold.Invoke(crafterKey); if (isRepeat) {
} else{ window->onKeyHold[(uint8_t)crafterKey].Invoke();
window->heldkeys[static_cast<std::uint8_t>(crafterKey)] = true; window->onAnyKeyHold.Invoke(crafterKey);
window->onKeyDown[static_cast<std::uint8_t>(crafterKey)].Invoke(); } else {
window->onAnyKeyDown.Invoke(crafterKey); window->heldkeys[(uint8_t)crafterKey] = true;
} window->onKeyDown[(uint8_t)crafterKey].Invoke();
window->onAnyKeyDown.Invoke(crafterKey);
} }
break; break;
} }
case WM_KEYUP: {
case WM_KEYUP:
case WM_SYSKEYUP: {
CrafterKeys crafterKey = vk_to_crafter_key(wParam); CrafterKeys crafterKey = vk_to_crafter_key(wParam);
window->heldkeys[static_cast<std::uint8_t>(crafterKey)] = false; window->heldkeys[(uint8_t)crafterKey] = false;
window->onKeyUp[static_cast<std::uint8_t>(crafterKey)].Invoke(); window->onKeyUp[(uint8_t)crafterKey].Invoke();
window->onAnyKeyUp.Invoke(crafterKey); window->onAnyKeyUp.Invoke(crafterKey);
break; break;
} }
case WM_MOUSEMOVE: {
int x = LOWORD(lParam);
int y = HIWORD(lParam);
Vector<float, 2> pos(x, y); case WM_CHAR: {
window->currentMousePos = pos; // wParam is a UTF-16 code unit. May be a surrogate — buffer until we have a pair.
window->onMouseMove.Invoke(); wchar_t wc = (wchar_t)wParam;
for(MouseElement* element : window->mouseElements) {
if(element) { // Filter control characters (backspace=0x08, tab=0x09, enter=0x0D, escape=0x1B, etc.)
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) { if (wc < 0x20 || wc == 0x7f) break;
element->onMouseMove.Invoke();
if(!element->mouseHover) { // Handle UTF-16 surrogate pairs (characters outside the BMP, e.g. emoji).
element->mouseHover = true; static wchar_t highSurrogate = 0;
element->onMouseEnter.Invoke(); wchar_t utf16[2];
} int utf16Len;
} else if(element->mouseHover) {
element->mouseHover = false; if (wc >= 0xD800 && wc <= 0xDBFF) {
element->onMouseLeave.Invoke(); // 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));
} }
window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast<MouseElement*>(nullptr)), window->mouseElements.end());
break; break;
} }
case WM_LBUTTONDOWN: { case WM_LBUTTONDOWN: {

View file

@ -33,6 +33,7 @@ namespace Crafter {
std::int_fast32_t descent; std::int_fast32_t descent;
std::int_fast32_t lineGap; std::int_fast32_t lineGap;
stbtt_fontinfo font; 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);
}; };
} }

View file

@ -363,7 +363,13 @@ export namespace Crafter {
int baseline = (int)(font.ascent * scale); int baseline = (int)(font.ascent * scale);
std::uint32_t lineHeight = (font.ascent - font.descent) * scale; std::uint32_t lineHeight = (font.ascent - font.descent) * scale;
std::uint32_t currentY = baseline; std::uint32_t currentY = baseline;
std::uint32_t ogOffsetX = offsetX;
std::uint32_t ogOffsetY = offsetY;
for(std::string_view line : lines) { for(std::string_view line : lines) {
offsetX = ogOffsetX;
offsetY = ogOffsetY;
std::int32_t lineWidth = 0; std::int32_t lineWidth = 0;
for (const char c : line) { for (const char c : line) {
@ -379,7 +385,7 @@ export namespace Crafter {
offsetX -= lineWidth / 2; offsetX -= lineWidth / 2;
break; break;
case TextAlignment::Right: case TextAlignment::Right:
offsetX += lineWidth; offsetX -= lineWidth;
break; break;
} }
@ -456,20 +462,11 @@ export namespace Crafter {
} }
} }
if(alignment != TextAlignment::Right) { offsetX += (int)(ax * scale);
offsetX += (int)(ax * scale);
if (p + 1 < end) { if (p + 1 < end) {
int next; int next;
offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &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));
}
} }
} }
currentY += lineHeight; currentY += lineHeight;

View file

@ -49,7 +49,7 @@ export namespace Crafter {
std::chrono::duration<double> delta; std::chrono::duration<double> delta;
}; };
enum class CrafterKeys { enum class CrafterKeys : std::uint8_t {
// Alphabetic keys // Alphabetic keys
A, B, C, D, E, F, G, H, I, J, K, L, M, 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, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
@ -92,6 +92,151 @@ export namespace Crafter {
CrafterKeysMax 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 <typename T, typename T2> template <typename T, typename T2>
constexpr T AlignUp(T value, T2 alignment) { constexpr T AlignUp(T value, T2 alignment) {
return (value + alignment - 1) & ~(alignment - 1); return (value + alignment - 1) & ~(alignment - 1);

View file

@ -82,6 +82,7 @@ export namespace Crafter {
Event<CrafterKeys> onAnyKeyDown; Event<CrafterKeys> onAnyKeyDown;
Event<CrafterKeys> onAnyKeyHold; Event<CrafterKeys> onAnyKeyHold;
Event<CrafterKeys> onAnyKeyUp; Event<CrafterKeys> onAnyKeyUp;
Event<const std::string_view> onTextInput;
Event<void> onMouseRightClick; Event<void> onMouseRightClick;
Event<void> onMouseLeftClick; Event<void> onMouseLeftClick;
Event<void> onMouseRightHold; Event<void> onMouseRightHold;