277 lines
8.8 KiB
C++
277 lines
8.8 KiB
C++
#include "platform/platform.h"
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <windows.h>
|
|
#include <malloc.h>
|
|
|
|
struct PlatformWindow {
|
|
HWND hwnd;
|
|
bool should_close;
|
|
int32_t width;
|
|
int32_t height;
|
|
int32_t pending_menu_cmd;
|
|
PlatformFrameCallback frame_callback;
|
|
void *frame_callback_user_data;
|
|
PlatformInput input;
|
|
B32 prev_mouse_down;
|
|
};
|
|
|
|
static PlatformWindow *g_current_window = nullptr;
|
|
|
|
static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
switch (msg) {
|
|
case WM_SIZE:
|
|
if (g_current_window && wparam != SIZE_MINIMIZED) {
|
|
g_current_window->width = (int32_t)LOWORD(lparam);
|
|
g_current_window->height = (int32_t)HIWORD(lparam);
|
|
// Render a frame during the modal resize loop so the UI
|
|
// stays responsive instead of showing a stretched image.
|
|
if (g_current_window->frame_callback) {
|
|
g_current_window->frame_callback(g_current_window->frame_callback_user_data);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_CHAR:
|
|
if (g_current_window && wparam >= 32 && wparam < 0xFFFF) {
|
|
PlatformInput *ev = &g_current_window->input;
|
|
if (ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME)
|
|
ev->chars[ev->char_count++] = (uint16_t)wparam;
|
|
}
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
if (g_current_window) {
|
|
PlatformInput *ev = &g_current_window->input;
|
|
if (ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
|
|
ev->keys[ev->key_count++] = (uint8_t)wparam;
|
|
ev->ctrl_held = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
|
|
ev->shift_held = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
|
}
|
|
break; // fall through to DefWindowProc for system keys
|
|
case WM_MOUSEWHEEL:
|
|
if (g_current_window) {
|
|
int16_t wheel_delta = (int16_t)HIWORD(wparam);
|
|
g_current_window->input.scroll_delta.y += (F32)wheel_delta / (F32)WHEEL_DELTA * 6.0f;
|
|
}
|
|
return 0;
|
|
case WM_COMMAND:
|
|
if (g_current_window && HIWORD(wparam) == 0)
|
|
g_current_window->pending_menu_cmd = (int32_t)LOWORD(wparam);
|
|
return 0;
|
|
case WM_SETCURSOR:
|
|
// When the cursor is in our client area, force it to an arrow.
|
|
// Without this, moving from a resize border back into the client
|
|
// area would leave the resize cursor shape stuck.
|
|
if (LOWORD(lparam) == HTCLIENT) {
|
|
SetCursor(LoadCursor(nullptr, IDC_ARROW));
|
|
return TRUE;
|
|
}
|
|
break;
|
|
case WM_CLOSE:
|
|
if (g_current_window)
|
|
g_current_window->should_close = true;
|
|
return 0;
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
case WM_SYSCOMMAND:
|
|
if ((wparam & 0xfff0) == SC_KEYMENU)
|
|
return 0;
|
|
break;
|
|
}
|
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
|
WNDCLASSEXW wc = {};
|
|
wc.cbSize = sizeof(wc);
|
|
wc.style = CS_CLASSDC;
|
|
wc.lpfnWndProc = win32_wndproc;
|
|
wc.hInstance = GetModuleHandleW(nullptr);
|
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wc.lpszClassName = L"autosample_wc";
|
|
RegisterClassExW(&wc);
|
|
|
|
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
|
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
|
int x = (screen_w - desc->width) / 2;
|
|
int y = (screen_h - desc->height) / 2;
|
|
|
|
RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height };
|
|
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
|
|
|
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, nullptr, 0);
|
|
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
|
MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);
|
|
|
|
HWND hwnd = CreateWindowExW(
|
|
0, wc.lpszClassName, wtitle,
|
|
WS_OVERLAPPEDWINDOW,
|
|
x, y,
|
|
rect.right - rect.left,
|
|
rect.bottom - rect.top,
|
|
nullptr, nullptr, wc.hInstance, nullptr
|
|
);
|
|
|
|
_freea(wtitle);
|
|
|
|
if (!hwnd)
|
|
return nullptr;
|
|
|
|
ShowWindow(hwnd, SW_SHOWDEFAULT);
|
|
UpdateWindow(hwnd);
|
|
|
|
PlatformWindow *window = new PlatformWindow();
|
|
window->hwnd = hwnd;
|
|
window->should_close = false;
|
|
window->width = desc->width;
|
|
window->height = desc->height;
|
|
|
|
g_current_window = window;
|
|
return window;
|
|
}
|
|
|
|
void platform_destroy_window(PlatformWindow *window) {
|
|
if (!window) return;
|
|
|
|
if (window->hwnd) {
|
|
DestroyWindow(window->hwnd);
|
|
UnregisterClassW(L"autosample_wc", GetModuleHandleW(nullptr));
|
|
}
|
|
|
|
if (g_current_window == window)
|
|
g_current_window = nullptr;
|
|
|
|
delete window;
|
|
}
|
|
|
|
bool platform_poll_events(PlatformWindow *window) {
|
|
MSG msg;
|
|
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
if (msg.message == WM_QUIT) {
|
|
window->should_close = true;
|
|
}
|
|
}
|
|
return !window->should_close;
|
|
}
|
|
|
|
void platform_get_size(PlatformWindow *window, int32_t *w, int32_t *h) {
|
|
if (w) *w = window->width;
|
|
if (h) *h = window->height;
|
|
}
|
|
|
|
void *platform_get_native_handle(PlatformWindow *window) {
|
|
return (void *)window->hwnd;
|
|
}
|
|
|
|
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
|
window->frame_callback = cb;
|
|
window->frame_callback_user_data = user_data;
|
|
}
|
|
|
|
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu_count) {
|
|
HMENU menu_bar = CreateMenu();
|
|
|
|
for (int32_t i = 0; i < menu_count; i++) {
|
|
HMENU submenu = CreatePopupMenu();
|
|
|
|
for (int32_t j = 0; j < menus[i].item_count; j++) {
|
|
PlatformMenuItem *item = &menus[i].items[j];
|
|
if (!item->label) {
|
|
AppendMenuW(submenu, MF_SEPARATOR, 0, nullptr);
|
|
} else {
|
|
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 0);
|
|
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
|
MultiByteToWideChar(CP_UTF8, 0, item->label, -1, wlabel, wchar_count);
|
|
AppendMenuW(submenu, MF_STRING, (UINT_PTR)item->id, wlabel);
|
|
_freea(wlabel);
|
|
}
|
|
}
|
|
|
|
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 0);
|
|
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
|
MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, wlabel, wchar_count);
|
|
AppendMenuW(menu_bar, MF_POPUP, (UINT_PTR)submenu, wlabel);
|
|
_freea(wlabel);
|
|
}
|
|
|
|
SetMenu(window->hwnd, menu_bar);
|
|
}
|
|
|
|
int32_t platform_poll_menu_command(PlatformWindow *window) {
|
|
int32_t cmd = window->pending_menu_cmd;
|
|
window->pending_menu_cmd = 0;
|
|
return cmd;
|
|
}
|
|
|
|
PlatformInput platform_get_input(PlatformWindow *window) {
|
|
PlatformInput result = window->input;
|
|
|
|
// Poll mouse position
|
|
POINT cursor;
|
|
GetCursorPos(&cursor);
|
|
ScreenToClient(window->hwnd, &cursor);
|
|
result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y);
|
|
|
|
// Poll mouse button
|
|
result.was_mouse_down = window->prev_mouse_down;
|
|
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
|
window->prev_mouse_down = result.mouse_down;
|
|
|
|
// Clear accumulated events for next frame
|
|
window->input = {};
|
|
return result;
|
|
}
|
|
|
|
void platform_clipboard_set(const char *text) {
|
|
if (!text) return;
|
|
int len = (int)strlen(text);
|
|
if (len == 0) return;
|
|
|
|
// Convert UTF-8 to wide string for Windows clipboard
|
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, nullptr, 0);
|
|
if (wlen == 0) return;
|
|
|
|
HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
|
|
if (!hmem) return;
|
|
|
|
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
|
|
MultiByteToWideChar(CP_UTF8, 0, text, len, wbuf, wlen);
|
|
wbuf[wlen] = L'\0';
|
|
GlobalUnlock(hmem);
|
|
|
|
HWND hwnd = g_current_window ? g_current_window->hwnd : nullptr;
|
|
if (OpenClipboard(hwnd)) {
|
|
EmptyClipboard();
|
|
SetClipboardData(CF_UNICODETEXT, hmem);
|
|
CloseClipboard();
|
|
} else {
|
|
GlobalFree(hmem);
|
|
}
|
|
}
|
|
|
|
const char *platform_clipboard_get() {
|
|
static char buf[4096];
|
|
buf[0] = '\0';
|
|
|
|
HWND hwnd = g_current_window ? g_current_window->hwnd : nullptr;
|
|
if (!OpenClipboard(hwnd)) return nullptr;
|
|
|
|
HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT);
|
|
if (hmem) {
|
|
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
|
|
if (wbuf) {
|
|
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, nullptr, nullptr);
|
|
buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
|
|
GlobalUnlock(hmem);
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
return buf[0] ? buf : nullptr;
|
|
}
|