Files
raddebugger/src/os/gfx/win32/os_gfx_win32.c
T

1550 lines
49 KiB
C

// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Modern Windows SDK Functions
//
// (We must dynamically link to them, since they can be missing in older SDKs)
typedef BOOL w32_SetProcessDpiAwarenessContext_Type(void* value);
typedef UINT w32_GetDpiForWindow_Type(HWND hwnd);
typedef int w32_GetSystemMetricsForDpi_Type(int nIndex, UINT dpi);
#define w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void*)-4)
global w32_GetDpiForWindow_Type *w32_GetDpiForWindow_func = 0;
global w32_GetSystemMetricsForDpi_Type *w32_GetSystemMetricsForDpi_func = 0;
////////////////////////////////
//~ rjf: Basic Helpers
internal Rng2F32
os_w32_rng2f32_from_rect(RECT rect)
{
Rng2F32 r = {0};
r.x0 = (F32)rect.left;
r.x1 = (F32)rect.right;
r.y0 = (F32)rect.top;
r.y1 = (F32)rect.bottom;
return r;
}
////////////////////////////////
//~ rjf: Windows
internal OS_Handle
os_w32_handle_from_window(OS_W32_Window *window)
{
OS_Handle handle = {(U64)window};
return handle;
}
internal OS_W32_Window *
os_w32_window_from_handle(OS_Handle handle)
{
OS_W32_Window *window = (OS_W32_Window *)handle.u64[0];
return window;
}
internal OS_W32_Window *
os_w32_window_from_hwnd(HWND hwnd)
{
OS_W32_Window *result = 0;
for(OS_W32_Window *w = os_w32_gfx_state->first_window; w; w = w->next)
{
if(w->hwnd == hwnd)
{
result = w;
break;
}
}
return result;
}
internal HWND
os_w32_hwnd_from_window(OS_W32_Window *window)
{
return window->hwnd;
}
internal OS_W32_Window *
os_w32_window_alloc(void)
{
OS_W32_Window *result = os_w32_gfx_state->free_window;
if(result)
{
SLLStackPop(os_w32_gfx_state->free_window);
}
else
{
result = push_array_no_zero(os_w32_gfx_state->arena, OS_W32_Window, 1);
}
MemoryZeroStruct(result);
if(result)
{
DLLPushBack(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, result);
}
result->last_window_placement.length = sizeof(WINDOWPLACEMENT);
return result;
}
internal void
os_w32_window_release(OS_W32_Window *window)
{
if(window->paint_arena != 0)
{
arena_release(window->paint_arena);
}
DestroyWindow(window->hwnd);
DLLRemove(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, window);
SLLStackPush(os_w32_gfx_state->free_window, window);
}
internal OS_Event *
os_w32_push_event(OS_EventKind kind, OS_W32_Window *window)
{
OS_Event *result = os_event_list_push_new(os_w32_event_arena, &os_w32_event_list, kind);
result->window = os_w32_handle_from_window(window);
result->modifiers = os_get_modifiers();
return result;
}
internal OS_Key
os_w32_os_key_from_vkey(WPARAM vkey)
{
local_persist B32 first = 1;
local_persist OS_Key key_table[256];
if (first){
first = 0;
MemoryZeroArray(key_table);
key_table[(unsigned int)'A'] = OS_Key_A;
key_table[(unsigned int)'B'] = OS_Key_B;
key_table[(unsigned int)'C'] = OS_Key_C;
key_table[(unsigned int)'D'] = OS_Key_D;
key_table[(unsigned int)'E'] = OS_Key_E;
key_table[(unsigned int)'F'] = OS_Key_F;
key_table[(unsigned int)'G'] = OS_Key_G;
key_table[(unsigned int)'H'] = OS_Key_H;
key_table[(unsigned int)'I'] = OS_Key_I;
key_table[(unsigned int)'J'] = OS_Key_J;
key_table[(unsigned int)'K'] = OS_Key_K;
key_table[(unsigned int)'L'] = OS_Key_L;
key_table[(unsigned int)'M'] = OS_Key_M;
key_table[(unsigned int)'N'] = OS_Key_N;
key_table[(unsigned int)'O'] = OS_Key_O;
key_table[(unsigned int)'P'] = OS_Key_P;
key_table[(unsigned int)'Q'] = OS_Key_Q;
key_table[(unsigned int)'R'] = OS_Key_R;
key_table[(unsigned int)'S'] = OS_Key_S;
key_table[(unsigned int)'T'] = OS_Key_T;
key_table[(unsigned int)'U'] = OS_Key_U;
key_table[(unsigned int)'V'] = OS_Key_V;
key_table[(unsigned int)'W'] = OS_Key_W;
key_table[(unsigned int)'X'] = OS_Key_X;
key_table[(unsigned int)'Y'] = OS_Key_Y;
key_table[(unsigned int)'Z'] = OS_Key_Z;
for (U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1){
key_table[i] = (OS_Key)j;
}
for (U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1){
key_table[i] = (OS_Key)j;
}
for (U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1){
key_table[i] = (OS_Key)j;
}
key_table[VK_SPACE] = OS_Key_Space;
key_table[VK_OEM_3] = OS_Key_Tick;
key_table[VK_OEM_MINUS] = OS_Key_Minus;
key_table[VK_OEM_PLUS] = OS_Key_Equal;
key_table[VK_OEM_4] = OS_Key_LeftBracket;
key_table[VK_OEM_6] = OS_Key_RightBracket;
key_table[VK_OEM_1] = OS_Key_Semicolon;
key_table[VK_OEM_7] = OS_Key_Quote;
key_table[VK_OEM_COMMA] = OS_Key_Comma;
key_table[VK_OEM_PERIOD]= OS_Key_Period;
key_table[VK_OEM_2] = OS_Key_Slash;
key_table[VK_OEM_5] = OS_Key_BackSlash;
key_table[VK_TAB] = OS_Key_Tab;
key_table[VK_PAUSE] = OS_Key_Pause;
key_table[VK_ESCAPE] = OS_Key_Esc;
key_table[VK_UP] = OS_Key_Up;
key_table[VK_LEFT] = OS_Key_Left;
key_table[VK_DOWN] = OS_Key_Down;
key_table[VK_RIGHT] = OS_Key_Right;
key_table[VK_BACK] = OS_Key_Backspace;
key_table[VK_RETURN] = OS_Key_Return;
key_table[VK_DELETE] = OS_Key_Delete;
key_table[VK_INSERT] = OS_Key_Insert;
key_table[VK_PRIOR] = OS_Key_PageUp;
key_table[VK_NEXT] = OS_Key_PageDown;
key_table[VK_HOME] = OS_Key_Home;
key_table[VK_END] = OS_Key_End;
key_table[VK_CAPITAL] = OS_Key_CapsLock;
key_table[VK_NUMLOCK] = OS_Key_NumLock;
key_table[VK_SCROLL] = OS_Key_ScrollLock;
key_table[VK_APPS] = OS_Key_Menu;
key_table[VK_CONTROL] = OS_Key_Ctrl;
key_table[VK_LCONTROL] = OS_Key_Ctrl;
key_table[VK_RCONTROL] = OS_Key_Ctrl;
key_table[VK_SHIFT] = OS_Key_Shift;
key_table[VK_LSHIFT] = OS_Key_Shift;
key_table[VK_RSHIFT] = OS_Key_Shift;
key_table[VK_MENU] = OS_Key_Alt;
key_table[VK_LMENU] = OS_Key_Alt;
key_table[VK_RMENU] = OS_Key_Alt;
key_table[VK_DIVIDE] = OS_Key_NumSlash;
key_table[VK_MULTIPLY] = OS_Key_NumStar;
key_table[VK_SUBTRACT] = OS_Key_NumMinus;
key_table[VK_ADD] = OS_Key_NumPlus;
key_table[VK_DECIMAL] = OS_Key_NumPeriod;
for (U32 i = 0; i < 10; i += 1){
key_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i);
}
for (U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1){
key_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j);
}
}
OS_Key key = key_table[vkey&bitmask8];
return key;
}
internal WPARAM
os_w32_vkey_from_os_key(OS_Key key)
{
WPARAM result = 0;
{
local_persist B32 initialized = 0;
local_persist WPARAM vkey_table[OS_Key_COUNT] = {0};
if(initialized == 0)
{
initialized = 1;
vkey_table[OS_Key_Esc] = VK_ESCAPE;
for(OS_Key key = OS_Key_F1; key <= OS_Key_F24; key = (OS_Key)(key+1))
{
vkey_table[key] = VK_F1+(key-OS_Key_F1);
}
vkey_table[OS_Key_Tick] = VK_OEM_3;
for(OS_Key key = OS_Key_0; key <= OS_Key_9; key = (OS_Key)(key+1))
{
vkey_table[key] = '0'+(key-OS_Key_0);
}
vkey_table[OS_Key_Minus] = VK_OEM_MINUS;
vkey_table[OS_Key_Equal] = VK_OEM_PLUS;
vkey_table[OS_Key_Backspace] = VK_BACK;
vkey_table[OS_Key_Tab] = VK_TAB;
vkey_table[OS_Key_Q] = 'Q';
vkey_table[OS_Key_W] = 'W';
vkey_table[OS_Key_E] = 'E';
vkey_table[OS_Key_R] = 'R';
vkey_table[OS_Key_T] = 'T';
vkey_table[OS_Key_Y] = 'Y';
vkey_table[OS_Key_U] = 'U';
vkey_table[OS_Key_I] = 'I';
vkey_table[OS_Key_O] = 'O';
vkey_table[OS_Key_P] = 'P';
vkey_table[OS_Key_LeftBracket] = VK_OEM_4;
vkey_table[OS_Key_RightBracket] = VK_OEM_6;
vkey_table[OS_Key_BackSlash] = VK_OEM_5;
vkey_table[OS_Key_CapsLock] = VK_CAPITAL;
vkey_table[OS_Key_A] = 'A';
vkey_table[OS_Key_S] = 'S';
vkey_table[OS_Key_D] = 'D';
vkey_table[OS_Key_F] = 'F';
vkey_table[OS_Key_G] = 'G';
vkey_table[OS_Key_H] = 'H';
vkey_table[OS_Key_J] = 'J';
vkey_table[OS_Key_K] = 'K';
vkey_table[OS_Key_L] = 'L';
vkey_table[OS_Key_Semicolon] = VK_OEM_1;
vkey_table[OS_Key_Quote] = VK_OEM_7;
vkey_table[OS_Key_Return] = VK_RETURN;
vkey_table[OS_Key_Shift] = VK_SHIFT;
vkey_table[OS_Key_Z] = 'Z';
vkey_table[OS_Key_X] = 'X';
vkey_table[OS_Key_C] = 'C';
vkey_table[OS_Key_V] = 'V';
vkey_table[OS_Key_B] = 'B';
vkey_table[OS_Key_N] = 'N';
vkey_table[OS_Key_M] = 'M';
vkey_table[OS_Key_Comma] = VK_OEM_COMMA;
vkey_table[OS_Key_Period] = VK_OEM_PERIOD;
vkey_table[OS_Key_Slash] = VK_OEM_2;
vkey_table[OS_Key_Ctrl] = VK_CONTROL;
vkey_table[OS_Key_Alt] = VK_MENU;
vkey_table[OS_Key_Space] = VK_SPACE;
vkey_table[OS_Key_Menu] = VK_APPS;
vkey_table[OS_Key_ScrollLock] = VK_SCROLL;
vkey_table[OS_Key_Pause] = VK_PAUSE;
vkey_table[OS_Key_Insert] = VK_INSERT;
vkey_table[OS_Key_Home] = VK_HOME;
vkey_table[OS_Key_PageUp] = VK_PRIOR;
vkey_table[OS_Key_Delete] = VK_DELETE;
vkey_table[OS_Key_End] = VK_END;
vkey_table[OS_Key_PageDown] = VK_NEXT;
vkey_table[OS_Key_Up] = VK_UP;
vkey_table[OS_Key_Left] = VK_LEFT;
vkey_table[OS_Key_Down] = VK_DOWN;
vkey_table[OS_Key_Right] = VK_RIGHT;
for(OS_Key key = OS_Key_Ex0; key <= OS_Key_Ex29; key = (OS_Key)(key+1))
{
vkey_table[key] = 0xDF + (key-OS_Key_Ex0);
}
vkey_table[OS_Key_NumLock] = VK_NUMLOCK;
vkey_table[OS_Key_NumSlash] = VK_DIVIDE;
vkey_table[OS_Key_NumStar] = VK_MULTIPLY;
vkey_table[OS_Key_NumMinus] = VK_SUBTRACT;
vkey_table[OS_Key_NumPlus] = VK_ADD;
vkey_table[OS_Key_NumPeriod] = VK_DECIMAL;
vkey_table[OS_Key_Num0] = VK_NUMPAD0;
vkey_table[OS_Key_Num1] = VK_NUMPAD1;
vkey_table[OS_Key_Num2] = VK_NUMPAD2;
vkey_table[OS_Key_Num3] = VK_NUMPAD3;
vkey_table[OS_Key_Num4] = VK_NUMPAD4;
vkey_table[OS_Key_Num5] = VK_NUMPAD5;
vkey_table[OS_Key_Num6] = VK_NUMPAD6;
vkey_table[OS_Key_Num7] = VK_NUMPAD7;
vkey_table[OS_Key_Num8] = VK_NUMPAD8;
vkey_table[OS_Key_Num9] = VK_NUMPAD9;
vkey_table[OS_Key_LeftMouseButton] = VK_LBUTTON;
vkey_table[OS_Key_MiddleMouseButton] = VK_MBUTTON;
vkey_table[OS_Key_RightMouseButton] = VK_RBUTTON;
}
result = vkey_table[key];
}
return result;
}
internal LRESULT
os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
ProfBeginFunction();
LRESULT result = 0;
B32 good = 1;
if(os_w32_event_arena == 0)
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
good = 0;
}
if(good)
{
OS_W32_Window *window = os_w32_window_from_hwnd(hwnd);
OS_Handle window_handle = os_w32_handle_from_window(window);
B32 release = 0;
switch(uMsg)
{
default:
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}break;
case WM_ENTERSIZEMOVE:
{
os_w32_resizing = 1;
}break;
case WM_EXITSIZEMOVE:
{
os_w32_resizing = 0;
}break;
case WM_SIZE:
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
BeginPaint(hwnd, &ps);
update();
EndPaint(hwnd, &ps);
}break;
case WM_CLOSE:
{
os_w32_push_event(OS_EventKind_WindowClose, window);
}break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
{
release = 1;
} // fallthrough;
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
{
OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window);
switch (uMsg)
{
case WM_LBUTTONUP: case WM_LBUTTONDOWN:
{
event->key = OS_Key_LeftMouseButton;
}break;
case WM_MBUTTONUP: case WM_MBUTTONDOWN:
{
event->key = OS_Key_MiddleMouseButton;
}break;
case WM_RBUTTONUP: case WM_RBUTTONDOWN:
{
event->key = OS_Key_RightMouseButton;
}break;
}
event->pos.x = (F32)(S16)LOWORD(lParam);
event->pos.y = (F32)(S16)HIWORD(lParam);
if(release)
{
ReleaseCapture();
}
else
{
SetCapture(hwnd);
}
}break;
case WM_MOUSEMOVE:
{
OS_Event *event = os_w32_push_event(OS_EventKind_MouseMove, window);
event->pos.x = (F32)(S16)LOWORD(lParam);
event->pos.y = (F32)(S16)HIWORD(lParam);
}break;
case WM_MOUSEWHEEL:
{
S16 wheel_delta = HIWORD(wParam);
OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window);
POINT p;
p.x = (S32)(S16)LOWORD(lParam);
p.y = (S32)(S16)HIWORD(lParam);
ScreenToClient(window->hwnd, &p);
event->pos.x = (F32)p.x;
event->pos.y = (F32)p.y;
event->delta = v2f32(0.f, -(F32)wheel_delta);
}break;
case WM_MOUSEHWHEEL:
{
S16 wheel_delta = HIWORD(wParam);
OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window);
POINT p;
p.x = (S32)(S16)LOWORD(lParam);
p.y = (S32)(S16)HIWORD(lParam);
ScreenToClient(window->hwnd, &p);
event->pos.x = (F32)p.x;
event->pos.y = (F32)p.y;
event->delta = v2f32((F32)wheel_delta, 0.f);
}break;
case WM_SYSKEYDOWN: case WM_SYSKEYUP:
{
if(wParam != VK_MENU && (wParam < VK_F1 || VK_F24 < wParam || wParam == VK_F4))
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
} // fallthrough;
case WM_KEYDOWN: case WM_KEYUP:
{
B32 was_down = (lParam & bit31);
B32 is_down = !(lParam & bit32);
B32 is_repeat = 0;
if(!is_down)
{
release = 1;
}
else if(was_down)
{
is_repeat = 1;
}
B32 right_sided = 0;
if ((lParam & bit25) &&
(wParam == VK_CONTROL || wParam == VK_RCONTROL ||
wParam == VK_MENU || wParam == VK_RMENU ||
wParam == VK_SHIFT || wParam == VK_RSHIFT))
{
right_sided = 1;
}
OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window);
event->key = os_w32_os_key_from_vkey(wParam);
event->repeat_count = lParam & bitmask16;
event->is_repeat = is_repeat;
event->right_sided = right_sided;
if(event->key == OS_Key_Alt && event->modifiers & OS_Modifier_Alt) { event->modifiers &= ~OS_Modifier_Alt; }
if(event->key == OS_Key_Ctrl && event->modifiers & OS_Modifier_Ctrl) { event->modifiers &= ~OS_Modifier_Ctrl; }
if(event->key == OS_Key_Shift && event->modifiers & OS_Modifier_Shift) { event->modifiers &= ~OS_Modifier_Shift; }
}break;
case WM_SYSCHAR:
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}break;
case WM_CHAR:
{
U32 character = wParam;
if(character >= 32 && character != 127)
{
OS_Event *event = os_w32_push_event(OS_EventKind_Text, window);
if(lParam & bit29)
{
event->modifiers |= OS_Modifier_Alt;
}
event->character = character;
}
}break;
case WM_KILLFOCUS:
{
os_w32_push_event(OS_EventKind_WindowLoseFocus, window);
ReleaseCapture();
}break;
case WM_SETCURSOR:
{
Rng2F32 window_rect = os_client_rect_from_window(window_handle);
Vec2F32 mouse = os_mouse_from_window(window_handle);
B32 on_border = 0;
DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0;
B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW);
if(window != 0 && window->custom_border && !is_fullscreen)
{
B32 on_border_x = (mouse.x <= window->custom_border_edge_thickness || window_rect.x1-window->custom_border_edge_thickness <= mouse.x);
B32 on_border_y = (mouse.y <= window->custom_border_edge_thickness || window_rect.y1-window->custom_border_edge_thickness <= mouse.y);
on_border = on_border_x || on_border_y;
}
if(!os_w32_resizing && !on_border && contains_2f32(window_rect, mouse))
{
SetCursor(os_w32_gfx_state->hCursor);
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}break;
case WM_DPICHANGED:
{
F32 new_dpi = (F32)(wParam & 0xffff);
window->dpi = new_dpi;
}break;
//- rjf: [file drop]
case WM_DROPFILES:
{
HDROP drop = (HDROP)wParam;
POINT drop_pt = {0};
DragQueryPoint(drop, &drop_pt);
U64 num_files_dropped = DragQueryFile(drop, 0xffffffff, 0, 0);
OS_Event *event = os_w32_push_event(OS_EventKind_FileDrop, window);
event->pos = v2f32((F32)drop_pt.x, (F32)drop_pt.y);
for(U64 idx = 0; idx < num_files_dropped; idx += 1)
{
U64 name_size = DragQueryFile(drop, idx, 0, 0) + 1;
U8 *name_ptr = push_array(os_w32_event_arena, U8, name_size);
DragQueryFile(drop, idx, (char *)name_ptr, name_size);
str8_list_push(os_w32_event_arena, &event->strings, str8(name_ptr, name_size - 1));
}
DragFinish(drop);
}break;
//- rjf: [custom border]
case WM_NCPAINT:
{
if(os_w32_new_window_custom_border || (window != 0 && window->custom_border && !window->custom_border_composition_enabled))
{
result = 0;
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}break;
case WM_DWMCOMPOSITIONCHANGED:
{
if(window != 0 && window->custom_border)
{
BOOL enabled = 0;
DwmIsCompositionEnabled(&enabled);
window->custom_border_composition_enabled = enabled;
if(enabled)
{
MARGINS m = { 0, 0, 1, 0 };
DwmExtendFrameIntoClientArea(hwnd, &m);
DWORD dwmncrp_enabled = DWMNCRP_ENABLED;
DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(dwmncrp_enabled));
}
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}break;
case WM_WINDOWPOSCHANGED:
{
result = 0;
}break;
case WM_NCUAHDRAWCAPTION:
case WM_NCUAHDRAWFRAME:
{
// NOTE(rjf): undocumented messages for drawing themed window borders.
if(os_w32_new_window_custom_border || (window != 0 && window->custom_border))
{
result = 0;
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}break;
case WM_SETICON:
case WM_SETTEXT:
{
if(os_w32_new_window_custom_border || (window && window->custom_border && !window->custom_border_composition_enabled))
{
// NOTE(rjf):
// https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/
LONG_PTR old_style = GetWindowLongPtrW(hwnd, GWL_STYLE);
SetWindowLongPtrW(hwnd, GWL_STYLE, old_style & ~WS_VISIBLE);
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
SetWindowLongPtrW(hwnd, GWL_STYLE, old_style);
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}break;
//- rjf: [custom border] activation - without this `result`, stuff flickers.
case WM_NCACTIVATE:
{
if(!os_w32_new_window_custom_border && (window == 0 || window->custom_border == 0))
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
else
{
result = DefWindowProcW(hwnd, uMsg, wParam, -1);
}
}break;
//- rjf: [custom border] client/window size calculation
case WM_NCCALCSIZE:
{
if(os_w32_new_window_custom_border || (window && window->custom_border))
{
if(wParam == 1)
{
NCCALCSIZE_PARAMS *pncsp = (NCCALCSIZE_PARAMS *)lParam;
pncsp->rgrc[0].right += 1;
}
}
else
{
result = DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}break;
//- rjf: [custom border] client/window hit testing (mapping mouse -> action)
case WM_NCHITTEST:
{
DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0;
B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW);
if(window == 0 || window->custom_border == 0 || is_fullscreen)
{
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
else
{
POINT pos_monitor;
pos_monitor.x = GET_X_LPARAM(lParam);
pos_monitor.y = GET_Y_LPARAM(lParam);
POINT pos_client = pos_monitor;
ScreenToClient(hwnd, &pos_client);
//- rjf: check against window boundaries
RECT frame_rect;
GetWindowRect(hwnd, &frame_rect);
B32 is_over_window = (frame_rect.left <= pos_monitor.x && pos_monitor.x < frame_rect.right &&
frame_rect.top <= pos_monitor.y && pos_monitor.y < frame_rect.bottom);
//- rjf: check against borders
B32 is_over_left = 0;
B32 is_over_right = 0;
B32 is_over_top = 0;
B32 is_over_bottom = 0;
{
RECT rect;
GetClientRect(hwnd, &rect);
if(!IsZoomed(hwnd))
{
if(rect.left <= pos_client.x && pos_client.x < rect.left + window->custom_border_edge_thickness)
{
is_over_left = 1;
}
if(rect.right - window->custom_border_edge_thickness <= pos_client.x && pos_client.x < rect.right)
{
is_over_right = 1;
}
if(rect.bottom - window->custom_border_edge_thickness <= pos_client.y && pos_client.y < rect.bottom)
{
is_over_bottom = 1;
}
if(rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_edge_thickness)
{
is_over_top = 1;
}
}
}
//- rjf: check against title bar
B32 is_over_title_bar = 0;
{
RECT rect;
GetClientRect(hwnd, &rect);
is_over_title_bar = (rect.left <= pos_client.x && pos_client.x < rect.right &&
rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_title_thickness);
}
//- rjf: check against title bar client areas
B32 is_over_title_bar_client_area = 0;
for(OS_W32_TitleBarClientArea *area = window->first_title_bar_client_area;
area != 0;
area = area->next)
{
Rng2F32 rect = area->rect;
if(rect.x0 <= pos_client.x && pos_client.x < rect.x1 &&
rect.y0 <= pos_client.y && pos_client.y < rect.y1)
{
is_over_title_bar_client_area = 1;
break;
}
}
//- rjf: resolve hovering to result
result = HTNOWHERE;
if(is_over_window)
{
// rjf: default to client area
result = HTCLIENT;
// rjf: title bar
if(is_over_title_bar)
{
result = HTCAPTION;
}
// rjf: normal edges
if(is_over_left) { result = HTLEFT; }
if(is_over_right) { result = HTRIGHT; }
if(is_over_top) { result = HTTOP; }
if(is_over_bottom) { result = HTBOTTOM; }
// rjf: corners
if(is_over_left && is_over_top) { result = HTTOPLEFT; }
if(is_over_left && is_over_bottom) { result = HTBOTTOMLEFT; }
if(is_over_right && is_over_top) { result = HTTOPRIGHT; }
if(is_over_right && is_over_bottom) { result = HTBOTTOMRIGHT; }
// rjf: title bar client area
if(is_over_title_bar_client_area)
{
result = HTCLIENT;
}
}
}
}break;
}
}
ProfEnd();
return result;
}
////////////////////////////////
//~ rjf: Monitors
internal BOOL
os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr)
{
OS_W32_MonitorGatherBundle *bundle = (OS_W32_MonitorGatherBundle *)bundle_ptr;
OS_Handle handle = {(U64)monitor};
os_handle_list_push(bundle->arena, bundle->list, handle);
return 1;
}
////////////////////////////////
//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS)
internal void
os_gfx_init(void)
{
//- rjf: set up base shared state
Arena *arena = arena_alloc();
os_w32_gfx_state = push_array(arena, OS_W32_GfxState, 1);
os_w32_gfx_state->arena = arena;
os_w32_gfx_state->gfx_thread_tid = (U32)GetCurrentThreadId();
os_w32_gfx_state->hInstance = GetModuleHandle(0);
//- rjf: set dpi awareness
w32_SetProcessDpiAwarenessContext_Type *SetProcessDpiAwarenessContext_func = 0;
HMODULE module = LoadLibraryA("user32.dll");
if(module != 0)
{
SetProcessDpiAwarenessContext_func =
(w32_SetProcessDpiAwarenessContext_Type*)GetProcAddress(module, "SetProcessDpiAwarenessContext");
w32_GetDpiForWindow_func =
(w32_GetDpiForWindow_Type*)GetProcAddress(module, "GetDpiForWindow");
w32_GetSystemMetricsForDpi_func = (w32_GetSystemMetricsForDpi_Type *)GetProcAddress(module, "GetSystemMetricsForDpi");
FreeLibrary(module);
}
if(SetProcessDpiAwarenessContext_func != 0)
{
SetProcessDpiAwarenessContext_func(w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
//- rjf: register graphical-window class
{
WNDCLASSEXW wndclass = {sizeof(wndclass)};
wndclass.lpfnWndProc = os_w32_wnd_proc;
wndclass.hInstance = os_w32_gfx_state->hInstance;
wndclass.lpszClassName = L"graphical-window";
wndclass.hCursor = LoadCursorA(0, IDC_ARROW);
wndclass.hIcon = LoadIcon(os_w32_gfx_state->hInstance, MAKEINTRESOURCE(1));
wndclass.style = CS_VREDRAW|CS_HREDRAW;
ATOM wndatom = RegisterClassExW(&wndclass);
(void)wndatom;
}
//- rjf: grab graphics system info
{
os_w32_gfx_state->gfx_info.double_click_time = GetDoubleClickTime()/1000.f;
os_w32_gfx_state->gfx_info.caret_blink_time = GetCaretBlinkTime()/1000.f;
DEVMODEW devmodew = {0};
if(EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew))
{
os_w32_gfx_state->gfx_info.default_refresh_rate = (F32)devmodew.dmDisplayFrequency;
}
}
//- rjf: set initial cursor
os_set_cursor(OS_Cursor_Pointer);
//- rjf: fill vkey -> OS_Key table
{
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'A'] = OS_Key_A;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'B'] = OS_Key_B;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'C'] = OS_Key_C;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'D'] = OS_Key_D;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'E'] = OS_Key_E;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'F'] = OS_Key_F;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'G'] = OS_Key_G;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'H'] = OS_Key_H;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'I'] = OS_Key_I;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'J'] = OS_Key_J;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'K'] = OS_Key_K;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'L'] = OS_Key_L;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'M'] = OS_Key_M;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'N'] = OS_Key_N;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'O'] = OS_Key_O;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'P'] = OS_Key_P;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Q'] = OS_Key_Q;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'R'] = OS_Key_R;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'S'] = OS_Key_S;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'T'] = OS_Key_T;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'U'] = OS_Key_U;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'V'] = OS_Key_V;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'W'] = OS_Key_W;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'X'] = OS_Key_X;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Y'] = OS_Key_Y;
os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Z'] = OS_Key_Z;
for(U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1)
{
os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j;
}
for(U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1)
{
os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j;
}
for(U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1)
{
os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j;
}
os_w32_gfx_state->key_from_vkey_table[VK_SPACE] = OS_Key_Space;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_3] = OS_Key_Tick;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_MINUS] = OS_Key_Minus;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_PLUS] = OS_Key_Equal;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_4] = OS_Key_LeftBracket;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_6] = OS_Key_RightBracket;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_1] = OS_Key_Semicolon;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_7] = OS_Key_Quote;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_COMMA] = OS_Key_Comma;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_PERIOD]= OS_Key_Period;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_2] = OS_Key_Slash;
os_w32_gfx_state->key_from_vkey_table[VK_OEM_5] = OS_Key_BackSlash;
os_w32_gfx_state->key_from_vkey_table[VK_TAB] = OS_Key_Tab;
os_w32_gfx_state->key_from_vkey_table[VK_PAUSE] = OS_Key_Pause;
os_w32_gfx_state->key_from_vkey_table[VK_ESCAPE] = OS_Key_Esc;
os_w32_gfx_state->key_from_vkey_table[VK_UP] = OS_Key_Up;
os_w32_gfx_state->key_from_vkey_table[VK_LEFT] = OS_Key_Left;
os_w32_gfx_state->key_from_vkey_table[VK_DOWN] = OS_Key_Down;
os_w32_gfx_state->key_from_vkey_table[VK_RIGHT] = OS_Key_Right;
os_w32_gfx_state->key_from_vkey_table[VK_BACK] = OS_Key_Backspace;
os_w32_gfx_state->key_from_vkey_table[VK_RETURN] = OS_Key_Return;
os_w32_gfx_state->key_from_vkey_table[VK_DELETE] = OS_Key_Delete;
os_w32_gfx_state->key_from_vkey_table[VK_INSERT] = OS_Key_Insert;
os_w32_gfx_state->key_from_vkey_table[VK_PRIOR] = OS_Key_PageUp;
os_w32_gfx_state->key_from_vkey_table[VK_NEXT] = OS_Key_PageDown;
os_w32_gfx_state->key_from_vkey_table[VK_HOME] = OS_Key_Home;
os_w32_gfx_state->key_from_vkey_table[VK_END] = OS_Key_End;
os_w32_gfx_state->key_from_vkey_table[VK_CAPITAL] = OS_Key_CapsLock;
os_w32_gfx_state->key_from_vkey_table[VK_NUMLOCK] = OS_Key_NumLock;
os_w32_gfx_state->key_from_vkey_table[VK_SCROLL] = OS_Key_ScrollLock;
os_w32_gfx_state->key_from_vkey_table[VK_APPS] = OS_Key_Menu;
os_w32_gfx_state->key_from_vkey_table[VK_CONTROL] = OS_Key_Ctrl;
os_w32_gfx_state->key_from_vkey_table[VK_LCONTROL] = OS_Key_Ctrl;
os_w32_gfx_state->key_from_vkey_table[VK_RCONTROL] = OS_Key_Ctrl;
os_w32_gfx_state->key_from_vkey_table[VK_SHIFT] = OS_Key_Shift;
os_w32_gfx_state->key_from_vkey_table[VK_LSHIFT] = OS_Key_Shift;
os_w32_gfx_state->key_from_vkey_table[VK_RSHIFT] = OS_Key_Shift;
os_w32_gfx_state->key_from_vkey_table[VK_MENU] = OS_Key_Alt;
os_w32_gfx_state->key_from_vkey_table[VK_LMENU] = OS_Key_Alt;
os_w32_gfx_state->key_from_vkey_table[VK_RMENU] = OS_Key_Alt;
os_w32_gfx_state->key_from_vkey_table[VK_DIVIDE] = OS_Key_NumSlash;
os_w32_gfx_state->key_from_vkey_table[VK_MULTIPLY] = OS_Key_NumStar;
os_w32_gfx_state->key_from_vkey_table[VK_SUBTRACT] = OS_Key_NumMinus;
os_w32_gfx_state->key_from_vkey_table[VK_ADD] = OS_Key_NumPlus;
os_w32_gfx_state->key_from_vkey_table[VK_DECIMAL] = OS_Key_NumPeriod;
for(U32 i = 0; i < 10; i += 1)
{
os_w32_gfx_state->key_from_vkey_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i);
}
for(U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1)
{
os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j);
}
}
}
////////////////////////////////
//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS)
internal OS_GfxInfo *
os_get_gfx_info(void)
{
return &os_w32_gfx_state->gfx_info;
}
////////////////////////////////
//~ rjf: @os_hooks Clipboards (Implemented Per-OS)
internal void
os_set_clipboard_text(String8 string)
{
if(OpenClipboard(0))
{
EmptyClipboard();
HANDLE string_copy_handle = GlobalAlloc(GMEM_MOVEABLE, string.size+1);
if(string_copy_handle)
{
U8 *copy_buffer = (U8 *)GlobalLock(string_copy_handle);
MemoryCopy(copy_buffer, string.str, string.size);
copy_buffer[string.size] = 0;
GlobalUnlock(string_copy_handle);
SetClipboardData(CF_TEXT, string_copy_handle);
}
CloseClipboard();
}
}
internal String8
os_get_clipboard_text(Arena *arena)
{
String8 result = {0};
if(IsClipboardFormatAvailable(CF_TEXT) &&
OpenClipboard(0))
{
HANDLE data_handle = GetClipboardData(CF_TEXT);
if(data_handle)
{
U8 *buffer = (U8 *)GlobalLock(data_handle);
if(buffer)
{
U64 size = cstring8_length(buffer);
result = push_str8_copy(arena, str8(buffer, size));
GlobalUnlock(data_handle);
}
}
CloseClipboard();
}
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Windows (Implemented Per-OS)
internal OS_Handle
os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title)
{
B32 custom_border = !!(flags & OS_WindowFlag_CustomBorder);
//- rjf: make hwnd
HWND hwnd = 0;
{
Temp scratch = scratch_begin(0, 0);
String16 title16 = str16_from_8(scratch.arena, title);
os_w32_new_window_custom_border = custom_border;
hwnd = CreateWindowExW(WS_EX_APPWINDOW,
L"graphical-window",
(WCHAR*)title16.str,
WS_OVERLAPPEDWINDOW | WS_SIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
(int)resolution.x,
(int)resolution.y,
0, 0,
os_w32_gfx_state->hInstance,
0);
DragAcceptFiles(hwnd, 1);
os_w32_new_window_custom_border = 0;
scratch_end(scratch);
}
//- rjf- make/fill window
OS_W32_Window *window = os_w32_window_alloc();
{
window->hwnd = hwnd;
if(w32_GetDpiForWindow_func != 0)
{
window->dpi = (F32)w32_GetDpiForWindow_func(hwnd);
}
else
{
window->dpi = 96.f;
}
}
//- rjf: early detection of composition
{
BOOL enabled = 0;
DwmIsCompositionEnabled(&enabled);
window->custom_border_composition_enabled = enabled;
}
//- rjf: custom border
if(flags & OS_WindowFlag_CustomBorder)
{
window->custom_border = 1;
window->paint_arena = arena_alloc();
}
//- rjf: convert to handle + return
OS_Handle result = os_w32_handle_from_window(window);
return result;
}
internal void
os_window_close(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
os_w32_window_release(window);
}
internal void
os_window_first_paint(OS_Handle window_handle)
{
OS_W32_Window *window = os_w32_window_from_handle(window_handle);
window->first_paint_done = 1;
ShowWindow(window->hwnd, SW_SHOW);
if(window->maximized)
{
ShowWindow(window->hwnd, SW_MAXIMIZE);
}
}
internal void
os_window_focus(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
SetForegroundWindow(window->hwnd);
SetFocus(window->hwnd);
}
internal B32
os_window_is_focused(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
HWND active_hwnd = GetActiveWindow();
return active_hwnd == window->hwnd;
}
internal B32
os_window_is_fullscreen(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE);
return !(window_style & WS_OVERLAPPEDWINDOW);
}
internal void
os_window_set_fullscreen(OS_Handle handle, B32 fullscreen)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE);
B32 is_fullscreen_already = os_window_is_fullscreen(handle);
if(fullscreen)
{
if(!is_fullscreen_already)
{
GetWindowPlacement(window->hwnd, &window->last_window_placement);
}
MONITORINFO monitor_info = {sizeof(monitor_info)};
if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info))
{
SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(window->hwnd, HWND_TOP,
monitor_info.rcMonitor.left,
monitor_info.rcMonitor.top,
monitor_info.rcMonitor.right - monitor_info.rcMonitor.left,
monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
else
{
SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW);
SetWindowPlacement(window->hwnd, &window->last_window_placement);
SetWindowPos(window->hwnd, 0, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
internal B32
os_window_is_maximized(OS_Handle handle)
{
B32 result = 0;
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window)
{
result = !!(IsZoomed(window->hwnd));
}
return result;
}
internal void
os_window_set_maximized(OS_Handle handle, B32 maximized)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window != 0)
{
if(window->first_paint_done)
{
switch(maximized)
{
default:
case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break;
case 1:{ShowWindow(window->hwnd, SW_MAXIMIZE);}break;
}
}
else
{
window->maximized = maximized;
}
}
}
internal B32
os_window_is_minimized(OS_Handle handle)
{
B32 result = 0;
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window)
{
result = !!(IsIconic(window->hwnd));
}
return result;
}
internal void
os_window_set_minimized(OS_Handle handle, B32 minimized)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window != 0 && minimized != os_window_is_minimized(handle))
{
switch(minimized)
{
default:
case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break;
case 1:{ShowWindow(window->hwnd, SW_MINIMIZE);}break;
}
}
}
internal void
os_window_bring_to_front(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window != 0)
{
BringWindowToTop(window->hwnd);
}
}
internal void
os_window_set_monitor(OS_Handle window_handle, OS_Handle monitor)
{
OS_W32_Window *window = os_w32_window_from_handle(window_handle);
HMONITOR hmonitor = (HMONITOR)monitor.u64[0];
{
MONITORINFOEXW info;
info.cbSize = sizeof(MONITORINFOEXW);
if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info))
{
Rng2F32 existing_rect = os_rect_from_window(window_handle);
Vec2F32 window_size = dim_2f32(existing_rect);
SetWindowPos(window->hwnd, HWND_TOP,
(info.rcWork.left + info.rcWork.right)/2 - window_size.x/2,
(info.rcWork.top + info.rcWork.bottom)/2 - window_size.y/2,
window_size.x,
window_size.y, 0);
}
}
}
internal void
os_window_clear_custom_border_data(OS_Handle handle)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window->custom_border)
{
arena_clear(window->paint_arena);
window->first_title_bar_client_area = window->last_title_bar_client_area = 0;
window->custom_border_title_thickness = 0;
window->custom_border_edge_thickness = 0;
}
}
internal void
os_window_push_custom_title_bar(OS_Handle handle, F32 thickness)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
window->custom_border_title_thickness = thickness;
}
internal void
os_window_push_custom_edges(OS_Handle handle, F32 thickness)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
window->custom_border_edge_thickness = thickness;
}
internal void
os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect)
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window->custom_border)
{
OS_W32_TitleBarClientArea *area = push_array(window->paint_arena, OS_W32_TitleBarClientArea, 1);
if(area != 0)
{
area->rect = rect;
SLLQueuePush(window->first_title_bar_client_area, window->last_title_bar_client_area, area);
}
}
}
internal Rng2F32
os_rect_from_window(OS_Handle handle)
{
Rng2F32 r = {0};
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window)
{
RECT rect = {0};
GetWindowRect(os_w32_hwnd_from_window(window), &rect);
r = os_w32_rng2f32_from_rect(rect);
}
return r;
}
internal Rng2F32
os_client_rect_from_window(OS_Handle handle)
{
Rng2F32 r = {0};
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window)
{
RECT rect = {0};
GetClientRect(os_w32_hwnd_from_window(window), &rect);
r = os_w32_rng2f32_from_rect(rect);
}
return r;
}
internal F32
os_dpi_from_window(OS_Handle handle)
{
F32 result = 96.f;
OS_W32_Window *window = os_w32_window_from_handle(handle);
if(window != 0)
{
result = window->dpi;
}
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Monitors (Implemented Per-OS)
internal OS_HandleArray
os_push_monitors_array(Arena *arena)
{
Temp scratch = scratch_begin(&arena, 1);
OS_HandleList list = {0};
{
OS_W32_MonitorGatherBundle bundle = {arena, &list};
EnumDisplayMonitors(0, 0, os_w32_monitor_gather_enum_proc, (LPARAM)&bundle);
}
OS_HandleArray array = os_handle_array_from_list(arena, &list);
scratch_end(scratch);
return array;
}
internal OS_Handle
os_primary_monitor(void)
{
POINT zero_pt = {0, 0};
HMONITOR monitor = MonitorFromPoint(zero_pt, MONITOR_DEFAULTTOPRIMARY);
OS_Handle result = {(U64)monitor};
return result;
}
internal OS_Handle
os_monitor_from_window(OS_Handle window)
{
OS_W32_Window *w = os_w32_window_from_handle(window);
HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY);
OS_Handle result = {(U64)handle};
return result;
}
internal String8
os_name_from_monitor(Arena *arena, OS_Handle monitor)
{
String8 result = {0};
HMONITOR monitor_handle = (HMONITOR)monitor.u64[0];
MONITORINFOEXW info;
info.cbSize = sizeof(MONITORINFOEXW);
if(GetMonitorInfoW(monitor_handle, (MONITORINFO *)&info))
{
String16 result16 = str16_cstring((U16 *)info.szDevice);
result = str8_from_16(arena, result16);
}
return result;
}
internal Vec2F32
os_dim_from_monitor(OS_Handle monitor)
{
Vec2F32 result = {0};
HMONITOR monitor_handle = (HMONITOR)monitor.u64[0];
MONITORINFO info = {0};
info.cbSize = sizeof(MONITORINFO);
if(GetMonitorInfoW(monitor_handle, &info))
{
result.x = info.rcWork.right - info.rcWork.left;
result.y = info.rcWork.bottom - info.rcWork.top;
}
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Events (Implemented Per-OS)
internal void
os_send_wakeup_event(void)
{
PostThreadMessageA(os_w32_gfx_state->gfx_thread_tid, 0x401, 0, 0);
}
internal OS_EventList
os_get_events(Arena *arena, B32 wait)
{
os_w32_event_arena = arena;
MemoryZeroStruct(&os_w32_event_list);
MSG msg = {0};
if(!wait || GetMessage(&msg, 0, 0, 0))
{
B32 first_wait = wait;
for(;first_wait || PeekMessage(&msg, 0, 0, 0, PM_REMOVE); first_wait = 0)
{
DispatchMessage(&msg);
TranslateMessage(&msg);
if(msg.message == WM_QUIT)
{
os_w32_push_event(OS_EventKind_WindowClose, 0);
}
}
}
return os_w32_event_list;
}
internal OS_Modifiers
os_get_modifiers(void)
{
OS_Modifiers modifiers = 0;
if(GetKeyState(VK_CONTROL) & 0x8000)
{
modifiers |= OS_Modifier_Ctrl;
}
if(GetKeyState(VK_SHIFT) & 0x8000)
{
modifiers |= OS_Modifier_Shift;
}
if(GetKeyState(VK_MENU) & 0x8000)
{
modifiers |= OS_Modifier_Alt;
}
return modifiers;
}
internal B32
os_key_is_down(OS_Key key)
{
B32 down = 0;
WPARAM vkey = os_w32_vkey_from_os_key(key);
if(GetKeyState(vkey) & 0x8000)
{
down = 1;
}
return down;
}
internal Vec2F32
os_mouse_from_window(OS_Handle handle)
{
ProfBeginFunction();
Vec2F32 v = {0};
POINT p;
if(GetCursorPos(&p))
{
OS_W32_Window *window = os_w32_window_from_handle(handle);
ScreenToClient(window->hwnd, &p);
v.x = (F32)p.x;
v.y = (F32)p.y;
}
ProfEnd();
return v;
}
////////////////////////////////
//~ rjf: @os_hooks Cursors (Implemented Per-OS)
internal void
os_set_cursor(OS_Cursor cursor)
{
B32 valid_cursor = 1;
HCURSOR hcursor = 0;
switch(cursor)
{
default: {valid_cursor = 0;}break;
#define Win32CursorXList(X) \
X(Pointer, IDC_ARROW) \
X(IBar, IDC_IBEAM) \
X(LeftRight, IDC_SIZEWE) \
X(UpDown, IDC_SIZENS) \
X(DownRight, IDC_SIZENWSE) \
X(UpRight, IDC_SIZENESW) \
X(UpDownLeftRight, IDC_SIZEALL) \
X(HandPoint, IDC_HAND)\
X(Disabled, IDC_NO)
#define CursorCase(E,R) case OS_Cursor_##E:{ \
local_persist HCURSOR curs = 0; \
if (curs == 0){ curs = LoadCursor(NULL, R); } \
hcursor = curs; }break;
Win32CursorXList(CursorCase)
#undef CursorCase
#undef Win32CursorXList
}
if(valid_cursor && !os_w32_resizing)
{
if(hcursor != os_w32_gfx_state->hCursor)
{
PostMessage(0, WM_SETCURSOR, 0, 0);
POINT p = {0};
GetCursorPos(&p);
SetCursorPos(p.x, p.y);
}
os_w32_gfx_state->hCursor = hcursor;
}
}
////////////////////////////////
//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS)
internal void
os_graphical_message(B32 error, String8 title, String8 message)
{
Temp scratch = scratch_begin(0, 0);
String16 title16 = str16_from_8(scratch.arena, title);
String16 message16 = str16_from_8(scratch.arena, message);
MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR));
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: @os_hooks Shell Operations
internal void
os_show_in_filesystem_ui(String8 path)
{
Temp scratch = scratch_begin(0, 0);
String8 path_copy = push_str8_copy(scratch.arena, path);
for(U64 idx = 0; idx < path_copy.size; idx += 1)
{
if(path_copy.str[idx] == '/')
{
path_copy.str[idx] = '\\';
}
}
String16 path16 = str16_from_8(scratch.arena, path_copy);
SFGAOF flags = 0;
PIDLIST_ABSOLUTE list = 0;
if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags)))
{
HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0);
CoTaskMemFree(list);
(void)hr;
}
scratch_end(scratch);
}
internal void
os_open_in_browser(String8 url)
{
Temp scratch = scratch_begin(0, 0);
String16 url16 = str16_from_8(scratch.arena, url);
ShellExecuteW(0, L"open", (WCHAR *)url16.str, 0, 0, SW_SHOWNORMAL);
scratch_end(scratch);
}