813 lines
30 KiB
C
813 lines
30 KiB
C
WND_W32_SharedState WND_W32_shared_state = ZI;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookdef Startup
|
|
|
|
void WND_Startup(void)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
|
|
//- Initialize btn table
|
|
{
|
|
ZeroArray(g->vk_to_btn);
|
|
for (u32 i = 'A', j = Btn_A; i <= 'Z'; ++i, ++j)
|
|
{
|
|
g->vk_to_btn[i] = (Btn)j;
|
|
}
|
|
for (u32 i = '0', j = Btn_0; i <= '9'; ++i, ++j)
|
|
{
|
|
g->vk_to_btn[i] = (Btn)j;
|
|
}
|
|
for (u32 i = VK_F1, j = Btn_F1; i <= VK_F24; ++i, ++j)
|
|
{
|
|
g->vk_to_btn[i] = (Btn)j;
|
|
}
|
|
g->vk_to_btn[VK_ESCAPE] = Btn_Esc;
|
|
g->vk_to_btn[VK_OEM_3] = Btn_GraveAccent;
|
|
g->vk_to_btn[VK_OEM_MINUS] = Btn_Minus;
|
|
g->vk_to_btn[VK_OEM_PLUS] = Btn_Equal;
|
|
g->vk_to_btn[VK_BACK] = Btn_Backspace;
|
|
g->vk_to_btn[VK_TAB] = Btn_Tab;
|
|
g->vk_to_btn[VK_SPACE] = Btn_Space;
|
|
g->vk_to_btn[VK_RETURN] = Btn_Enter;
|
|
g->vk_to_btn[VK_CONTROL] = Btn_Ctrl;
|
|
g->vk_to_btn[VK_SHIFT] = Btn_Shift;
|
|
g->vk_to_btn[VK_MENU] = Btn_Alt;
|
|
g->vk_to_btn[VK_UP] = Btn_Up;
|
|
g->vk_to_btn[VK_LEFT] = Btn_Left;
|
|
g->vk_to_btn[VK_DOWN] = Btn_Down;
|
|
g->vk_to_btn[VK_RIGHT] = Btn_Right;
|
|
g->vk_to_btn[VK_DELETE] = Btn_Delete;
|
|
g->vk_to_btn[VK_PRIOR] = Btn_PageUp;
|
|
g->vk_to_btn[VK_NEXT] = Btn_PageDown;
|
|
g->vk_to_btn[VK_HOME] = Btn_Home;
|
|
g->vk_to_btn[VK_END] = Btn_End;
|
|
g->vk_to_btn[VK_OEM_2] = Btn_ForwardSlash;
|
|
g->vk_to_btn[VK_OEM_PERIOD] = Btn_Period;
|
|
g->vk_to_btn[VK_OEM_COMMA] = Btn_Comma;
|
|
g->vk_to_btn[VK_OEM_7] = Btn_Quote;
|
|
g->vk_to_btn[VK_OEM_4] = Btn_LeftBracket;
|
|
g->vk_to_btn[VK_OEM_6] = Btn_RightBracket;
|
|
g->vk_to_btn[VK_INSERT] = Btn_Insert;
|
|
g->vk_to_btn[VK_OEM_1] = Btn_Semicolon;
|
|
}
|
|
|
|
//- Create window class
|
|
{
|
|
HMODULE instance = GetModuleHandle(0);
|
|
|
|
/* Register the window class */
|
|
WNDCLASSEXW *wc = &g->window_class;
|
|
wc->cbSize = sizeof(WNDCLASSEX);
|
|
wc->lpszClassName = WND_W32_WindowClassName;
|
|
wc->hCursor = LoadCursor(0, IDC_ARROW);
|
|
wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
// wc->hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wc->lpfnWndProc = WND_W32_WindowProc;
|
|
wc->hInstance = instance;
|
|
|
|
/* Use first icon resource as window icon (same as explorer) */
|
|
wchar_t path[4096] = ZI;
|
|
GetModuleFileNameW(instance, path, countof(path));
|
|
ExtractIconExW(path, 0, &wc->hIcon, &wc->hIconSm, 1);
|
|
|
|
if (!RegisterClassExW(wc))
|
|
{
|
|
Panic(Lit("Failed to register window class"));
|
|
}
|
|
}
|
|
|
|
//- Register raw mouse input
|
|
{
|
|
RAWINPUTDEVICE rid = ZI;
|
|
rid.usUsagePage = 0x01; /* HID_USAGE_PAGE_GENERIC */
|
|
rid.usUsage = 0x02; /* HID_USAGE_GENERIC_MOUSE */
|
|
RegisterRawInputDevices(&rid, 1, sizeof(rid));
|
|
}
|
|
|
|
//- Start message processing job
|
|
JobPoolId message_job_pool = InitJobPool(1, Lit("Win32 message loop"), JobPoolPriority_Graphics);
|
|
RunJob(WND_W32_ProcessMessagesForever, .pool = message_job_pool);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Window helpers
|
|
|
|
WND_W32_Window *WND_W32_WindowFromHandle(WND_Handle handle)
|
|
{
|
|
return (WND_W32_Window *)handle.v;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Initialization
|
|
|
|
/* Win32 limitation: Window must be initialized on same thread that processes events */
|
|
JobDef(WND_W32_ProcessMessagesForever, sig, id)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = &g->window;
|
|
window->w2u_inputs_arena = AcquireArena(Gibi(64));
|
|
|
|
//- Init hwnd
|
|
HWND hwnd = 0;
|
|
{
|
|
/*
|
|
* From martins (https://gist.github.com/mmozeiko/5e727f845db182d468a34d524508ad5f#file-win32_d3d11-c-L66-L70):
|
|
* WS_EX_NOREDIRECTIONBITMAP flag here is needed to fix ugly bug with Windows 10
|
|
* when window is resized and DXGI swap chain uses FLIP presentation model
|
|
* DO NOT use it if you choose to use non-FLIP presentation model
|
|
* read about the bug here: https://stackoverflow.com/q/63096226 and here: https://stackoverflow.com/q/53000291
|
|
*/
|
|
DWORD exstyle = WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP;
|
|
|
|
/* TODO: Check for hwnd success */
|
|
hwnd = CreateWindowExW(
|
|
exstyle,
|
|
g->window_class.lpszClassName,
|
|
L"",
|
|
WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
0,
|
|
0,
|
|
g->window_class.hInstance,
|
|
0
|
|
);
|
|
|
|
/* Dark mode */
|
|
BOOL dark_mode = 1;
|
|
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, (LPCVOID)&dark_mode, sizeof(dark_mode));
|
|
|
|
/* Set window as userdata */
|
|
/* FIXME: Necessary? */
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
|
}
|
|
window->hwnd = hwnd;
|
|
|
|
//- Begin processing messages
|
|
Atomic32Set(&window->is_ready, 1);
|
|
for (;;)
|
|
{
|
|
// SetFocus(window->hwnd);
|
|
MSG msg = ZI;
|
|
GetMessageW(&msg, 0, 0, 0);
|
|
{
|
|
__profn("Process window message");
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
|
|
/* Destroy window */
|
|
DestroyWindow(window->hwnd);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Message processing
|
|
|
|
void WND_W32_PushInput(WND_W32_Window *window, Input input)
|
|
{
|
|
LockTicketMutex(&window->w2u_tm);
|
|
{
|
|
*PushStructNoZero(window->w2u_inputs_arena, Input) = input;
|
|
}
|
|
UnlockTicketMutex(&window->w2u_tm);
|
|
}
|
|
|
|
LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = &g->window;
|
|
LRESULT result = 0;
|
|
|
|
{
|
|
switch(msg)
|
|
{
|
|
//- Default
|
|
default:
|
|
{
|
|
result = DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
} break;
|
|
|
|
//- Quit
|
|
case WM_QUIT:
|
|
case WM_CLOSE:
|
|
case WM_DESTROY:
|
|
{
|
|
WND_W32_PushInput(window, (Input) { .kind = InputKind_Quit });
|
|
} break;
|
|
|
|
//- Keyboard button
|
|
case WM_SYSKEYUP:
|
|
case WM_SYSKEYDOWN:;
|
|
case WM_KEYUP:
|
|
case WM_KEYDOWN:
|
|
{
|
|
WORD vk_code = LOWORD(wparam);
|
|
Input input = ZI;
|
|
if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
|
|
{
|
|
input.kind = InputKind_ButtonDown;
|
|
input.is_repeat = (lparam & 0x40000000) != 0;
|
|
}
|
|
else if (msg == WM_KEYUP || msg == WM_SYSKEYUP)
|
|
{
|
|
input.kind = InputKind_ButtonUp;
|
|
}
|
|
if (vk_code < countof(g->vk_to_btn))
|
|
{
|
|
input.button = g->vk_to_btn[vk_code];
|
|
}
|
|
WND_W32_PushInput(window, input);
|
|
if (msg == WM_SYSKEYUP || msg == WM_SYSKEYDOWN)
|
|
{
|
|
result = DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
} break;
|
|
|
|
//- Text
|
|
case WM_SYSCHAR:
|
|
case WM_CHAR:
|
|
{
|
|
u32 codepoint = 0;
|
|
{
|
|
u16 utf16_char = (u32)wparam;
|
|
if (IsUtf16HighSurrogate(utf16_char))
|
|
{
|
|
window->previous_utf16_high_surrogate = utf16_char;
|
|
}
|
|
else if (IsUtf16LowSurrogate(utf16_char))
|
|
{
|
|
u16 high = window->previous_utf16_high_surrogate;
|
|
u16 low = utf16_char;
|
|
if (high)
|
|
{
|
|
u16 utf16_pair_bytes[2] = { high, low };
|
|
Utf16DecodeResult decoded = DecodeUtf16((String16) { .len = countof(utf16_pair_bytes), .text = utf16_pair_bytes });
|
|
if (decoded.advance16 == 2 && decoded.codepoint < U32Max)
|
|
{
|
|
codepoint = decoded.codepoint;
|
|
}
|
|
}
|
|
window->previous_utf16_high_surrogate = 0;
|
|
}
|
|
else
|
|
{
|
|
window->previous_utf16_high_surrogate = 0;
|
|
codepoint = utf16_char;
|
|
}
|
|
if (codepoint != 0)
|
|
{
|
|
if (codepoint == '\r')
|
|
{
|
|
codepoint = '\n'; /* Just treat all \r as newline */
|
|
}
|
|
}
|
|
}
|
|
if ((codepoint >= 32 && codepoint != 127) || codepoint == '\t' || codepoint == '\n')
|
|
{
|
|
Input input = ZI;
|
|
input.kind = InputKind_Text;
|
|
input.text_codepoint = codepoint;
|
|
WND_W32_PushInput(window, input);
|
|
}
|
|
} break;
|
|
|
|
//- Mouse buttons
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_XBUTTONUP:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_XBUTTONDOWN:
|
|
{
|
|
Input input = ZI;
|
|
b32 is_down = msg == WM_LBUTTONDOWN ||
|
|
msg == WM_MBUTTONDOWN ||
|
|
msg == WM_RBUTTONDOWN ||
|
|
msg == WM_XBUTTONDOWN;
|
|
if (is_down)
|
|
{
|
|
input.kind = InputKind_ButtonDown;
|
|
SetCapture(hwnd);
|
|
}
|
|
else
|
|
{
|
|
input.kind = InputKind_ButtonUp;
|
|
ReleaseCapture();
|
|
}
|
|
switch (msg)
|
|
{
|
|
case WM_LBUTTONUP: case WM_LBUTTONDOWN: input.button = Btn_M1; break;
|
|
case WM_RBUTTONUP: case WM_RBUTTONDOWN: input.button = Btn_M2; break;
|
|
case WM_MBUTTONUP: case WM_MBUTTONDOWN: input.button = Btn_M3; break;
|
|
case WM_XBUTTONUP: case WM_XBUTTONDOWN:
|
|
{
|
|
u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam);
|
|
if (wparam_xbutton == XBUTTON1)
|
|
{
|
|
input.button = Btn_M4;
|
|
}
|
|
else if (wparam_xbutton == XBUTTON2)
|
|
{
|
|
input.button = Btn_M5;
|
|
}
|
|
} break;
|
|
}
|
|
if (input.button)
|
|
{
|
|
WND_W32_PushInput(window, input);
|
|
}
|
|
} break;
|
|
|
|
//- Mouse wheel
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
|
|
i32 dir = delta >= 0 ? 1 : -1;
|
|
Btn btn = dir >= 0 ? Btn_MWheelUp : Btn_MWheelDown;
|
|
for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA)
|
|
{
|
|
/* Send a button down & button up event simultaneously */
|
|
WND_W32_PushInput(window, (Input) { .kind = InputKind_ButtonDown, .button = btn });
|
|
WND_W32_PushInput(window, (Input) { .kind = InputKind_ButtonUp, .button = btn });
|
|
}
|
|
} break;
|
|
|
|
//- Cursor move
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
i32 x = GET_X_LPARAM(lparam);
|
|
i32 y = GET_Y_LPARAM(lparam);
|
|
Input input = ZI;
|
|
input.kind = InputKind_CursorMove;
|
|
input.cursor_pos = VEC2I32(x, y);
|
|
WND_W32_PushInput(window, input);
|
|
} break;
|
|
|
|
//- Raw mouse move
|
|
case WM_INPUT:
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
{
|
|
/* Read raw input buffer */
|
|
UINT buff_size;
|
|
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER));
|
|
u8 *buff = PushStructs(scratch.arena, u8, buff_size);
|
|
if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size)
|
|
{
|
|
P_LogErrorF("GetRawInputData did not return correct size");
|
|
break;
|
|
}
|
|
RAWINPUT raw = ZI;
|
|
CopyBytes(&raw, buff, sizeof(RAWINPUT));
|
|
|
|
if (raw.header.dwType == RIM_TYPEMOUSE)
|
|
{
|
|
i32 x = raw.data.mouse.lLastX;
|
|
i32 y = raw.data.mouse.lLastY;
|
|
Input input = ZI;
|
|
input.kind = InputKind_MouseMove;
|
|
input.mouse_delta = VEC2I32(x, y);
|
|
WND_W32_PushInput(window, input);
|
|
}
|
|
}
|
|
EndScratch(scratch);
|
|
} break;
|
|
|
|
#if 0
|
|
//- Process command
|
|
case WND_CmdMsgId:
|
|
{
|
|
P_LogWarningF("RAAAAH");
|
|
|
|
WND_Event user_event = ZI;
|
|
WND_Cmd user_cmd = ZI;
|
|
{
|
|
LockTicketMutex(&window->u2w_tm);
|
|
{
|
|
user_event = window->u2w_update_end_event;
|
|
user_cmd = window->u2w_update_end_cmd;
|
|
}
|
|
UnlockTicketMutex(&window->u2w_tm);
|
|
}
|
|
|
|
/* Determine old settings */
|
|
Vec2I32 old_draw_p0 = ZI;
|
|
Vec2I32 old_draw_p1 = ZI;
|
|
Vec2I32 old_restore_p0 = ZI;
|
|
Vec2I32 old_restore_p1 = ZI;
|
|
b32 old_is_visible = 0;
|
|
b32 old_has_border = 0;
|
|
{
|
|
{
|
|
RECT screen_rect = ZI;
|
|
GetClientRect(hwnd, (LPRECT)&screen_rect);
|
|
ClientToScreen(hwnd, (LPPOINT)&screen_rect.left);
|
|
ClientToScreen(hwnd, (LPPOINT)&screen_rect.right);
|
|
old_draw_p0 = VEC2I32(screen_rect.left, screen_rect.top);
|
|
old_draw_p1 = VEC2I32(screen_rect.right, screen_rect.bottom);
|
|
}
|
|
{
|
|
WINDOWPLACEMENT placement = { .length = sizeof(placement) };
|
|
GetWindowPlacement(hwnd, &placement);
|
|
RECT placement_rect = placement.rcNormalPosition;
|
|
old_restore_p0 = VEC2I32(placement_rect.left, placement_rect.top);
|
|
old_restore_p1 = VEC2I32(placement_rect.right, placement_rect.bottom);
|
|
}
|
|
{
|
|
DWORD style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
old_is_visible = !!(style & WS_VISIBLE);
|
|
old_has_border = !(style & WS_POPUP);
|
|
}
|
|
}
|
|
|
|
/* Determine new settings */
|
|
Vec2I32 new_draw_p0 = old_draw_p0;
|
|
Vec2I32 new_draw_p1 = old_draw_p1;
|
|
Vec2I32 new_restore_p0 = old_restore_p0;
|
|
Vec2I32 new_restore_p1 = old_restore_p1;
|
|
b32 new_is_visible = old_is_visible;
|
|
b32 new_has_border = old_has_border;
|
|
{
|
|
WND_Settings desired_settings = user_cmd.desired_settings;
|
|
if (desired_settings.flags & WND_Flag_Fullscreen)
|
|
{
|
|
new_has_border = 0;
|
|
new_draw_p0 = user_event.monitor_p0;
|
|
new_draw_p1 = user_event.monitor_p1;
|
|
}
|
|
else if (desired_settings.flags & WND_Flag_Maximized)
|
|
{
|
|
// show_cmd = SW_MAXIMIZE;
|
|
}
|
|
else if (desired_settings.flags & WND_Flag_Minimized)
|
|
{
|
|
// show_cmd = SW_MINIMIZE;
|
|
}
|
|
else
|
|
{
|
|
new_has_border = 1;
|
|
new_restore_p0 = desired_settings.restore_p0;
|
|
new_restore_p1 = desired_settings.restore_p1;
|
|
}
|
|
new_is_visible = 1;
|
|
}
|
|
|
|
/* Determine new style & placement */
|
|
b32 has_user_seen_os_settings = user_event.os_gen == (u64)Atomic64Fetch(&window->os_gen);
|
|
if (has_user_seen_os_settings)
|
|
{
|
|
/* Calculate style */
|
|
b32 dirty_style = 0;
|
|
DWORD new_style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
{
|
|
if (new_is_visible != old_is_visible)
|
|
{
|
|
dirty_style = 1;
|
|
if (new_is_visible)
|
|
{
|
|
new_style |= WS_VISIBLE;
|
|
}
|
|
else
|
|
{
|
|
new_style &= ~WS_VISIBLE;
|
|
}
|
|
}
|
|
if (new_has_border != old_has_border)
|
|
{
|
|
dirty_style = 1;
|
|
if (new_has_border)
|
|
{
|
|
new_style &= ~WS_POPUP;
|
|
new_style |= WS_OVERLAPPEDWINDOW;
|
|
}
|
|
else
|
|
{
|
|
new_style &= ~WS_OVERLAPPEDWINDOW;
|
|
new_style |= WS_POPUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Calculate placement */
|
|
b32 dirty_placement = 0;
|
|
WINDOWPLACEMENT new_placement = { .length = sizeof(new_placement) };
|
|
GetWindowPlacement(hwnd, &new_placement);
|
|
{
|
|
if (!EqVec2I32(new_restore_p0, old_restore_p0) || !EqVec2I32(new_restore_p1, old_restore_p1))
|
|
{
|
|
dirty_placement = 1;
|
|
new_placement.rcNormalPosition.left = new_restore_p0.x;
|
|
new_placement.rcNormalPosition.top = new_restore_p0.y;
|
|
new_placement.rcNormalPosition.right = new_restore_p1.x;
|
|
new_placement.rcNormalPosition.bottom = new_restore_p1.y;
|
|
}
|
|
}
|
|
|
|
/* Calculate draw position */
|
|
b32 dirty_draw_rect = 0;
|
|
RECT new_draw_rect = ZI;
|
|
if (!EqVec2I32(new_draw_p0, old_draw_p0) || !EqVec2I32(new_draw_p1, old_draw_p1))
|
|
{
|
|
dirty_draw_rect = 1;
|
|
new_draw_rect.left = new_draw_p0.x;
|
|
new_draw_rect.top = new_draw_p0.y;
|
|
new_draw_rect.right = new_draw_p1.x;
|
|
new_draw_rect.bottom = new_draw_p1.y;
|
|
AdjustWindowRect(&new_draw_rect, new_style, 0);
|
|
}
|
|
|
|
/* Apply changes */
|
|
if (dirty_style || dirty_placement || dirty_draw_rect)
|
|
{
|
|
if (dirty_style)
|
|
{
|
|
++window->cmd_depth;
|
|
SetWindowLongPtrW(hwnd, GWL_STYLE, new_style);
|
|
}
|
|
if (dirty_placement)
|
|
{
|
|
++window->cmd_depth;
|
|
SetWindowPlacement(hwnd, &new_placement);
|
|
}
|
|
if (dirty_draw_rect)
|
|
{
|
|
u32 pflags = 0;
|
|
Vec2I32 size = VEC2I32(new_draw_rect.right - new_draw_rect.left, new_draw_rect.bottom - new_draw_rect.top);
|
|
++window->cmd_depth;
|
|
SetWindowPos(hwnd, 0, new_draw_rect.left, new_draw_rect.top, size.x, size.y, pflags);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Bring window to front on first show */
|
|
if (!window->first_shown)
|
|
{
|
|
SetForegroundWindow(hwnd);
|
|
BringWindowToTop(hwnd);
|
|
window->first_shown = 1;
|
|
}
|
|
} break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookdef Cmds
|
|
|
|
void WND_PushCmd_(WND_Frame frame, WND_Cmd desc)
|
|
{
|
|
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window_handle);
|
|
WND_W32_CmdNode *n = PushStruct(window->cmds_arena, WND_W32_CmdNode);
|
|
n->cmd = desc;
|
|
QueuePush(window->first_cmd, window->last_cmd, n);
|
|
if (desc.kind == WND_CmdKind_Restore)
|
|
{
|
|
n->cmd.restore = PushString(window->cmds_arena, desc.restore);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookdef Frame
|
|
|
|
WND_Frame WND_BeginFrame(Arena *arena)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = &g->window;
|
|
WND_Frame result = ZI;
|
|
|
|
while (!Atomic32Fetch(&window->is_ready))
|
|
{
|
|
_mm_pause();
|
|
}
|
|
HWND hwnd = window->hwnd;
|
|
result.window_handle.v = (u64)window;
|
|
|
|
/* Reset cmds */
|
|
if (!window->cmds_arena)
|
|
{
|
|
window->cmds_arena = AcquireArena(Gibi(64));
|
|
}
|
|
ResetArena(window->cmds_arena);
|
|
window->first_cmd = 0;
|
|
window->last_cmd = 0;
|
|
|
|
/* Pop inputs */
|
|
{
|
|
LockTicketMutex(&window->w2u_tm);
|
|
{
|
|
Input *src = (Input *)ArenaBase(window->w2u_inputs_arena);
|
|
result.inputs_count = ArenaCount(window->w2u_inputs_arena, Input);
|
|
result.inputs = PushStructsNoZero(arena, Input, result.inputs_count);
|
|
CopyStructs(result.inputs, src, result.inputs_count);
|
|
ResetArena(window->w2u_inputs_arena);
|
|
}
|
|
UnlockTicketMutex(&window->w2u_tm);
|
|
}
|
|
|
|
/* Grab monitor info */
|
|
RECT monitor_rect = ZI;
|
|
{
|
|
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
|
|
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
|
|
monitor_rect = monitor_info.rcMonitor;
|
|
}
|
|
|
|
/* Client rect */
|
|
RECT client_rect = ZI;
|
|
GetClientRect(hwnd, (LPRECT)&client_rect);
|
|
|
|
/* Screen rect */
|
|
RECT screen_rect = client_rect;
|
|
ClientToScreen(hwnd, (LPPOINT)&screen_rect.left);
|
|
ClientToScreen(hwnd, (LPPOINT)&screen_rect.right);
|
|
result.draw_size = VEC2I32(screen_rect.right - screen_rect.left, screen_rect.bottom - screen_rect.top);
|
|
|
|
/* Minimized / maximized / fullscreen */
|
|
DWORD style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
DWORD ex_style = (DWORD)GetWindowLongPtr(hwnd, GWL_EXSTYLE);
|
|
WINDOWPLACEMENT placement = { .length = sizeof(placement) };
|
|
GetWindowPlacement(hwnd, &placement);
|
|
{
|
|
if (placement.showCmd == SW_MAXIMIZE)
|
|
{
|
|
result.maximized = 1;
|
|
}
|
|
if (placement.showCmd == SW_MINIMIZE)
|
|
{
|
|
result.minimized = 1;
|
|
}
|
|
if (screen_rect.left == monitor_rect.left &&
|
|
screen_rect.top == monitor_rect.top &&
|
|
screen_rect.right == monitor_rect.right &&
|
|
screen_rect.bottom == monitor_rect.bottom)
|
|
{
|
|
result.fullscreen = 1;
|
|
}
|
|
}
|
|
|
|
result.forced_top = window->is_forced_top;
|
|
result.fullscreen = window->is_fullscreen;
|
|
|
|
/* Generate restore data */
|
|
{
|
|
WND_W32_RestorableData restore = ZI;
|
|
{
|
|
restore.magic = WND_W32_RestoreMagic;
|
|
restore.placement = placement;
|
|
restore.style = style;
|
|
restore.ex_style = ex_style;
|
|
restore.is_forced_top = window->is_forced_top;
|
|
restore.is_fullscreen = window->is_fullscreen;
|
|
restore.fullscreen_restore_rect = window->fullscreen_restore_rect;
|
|
if (IsWindowArranged(hwnd))
|
|
{
|
|
restore.is_snapped = 1;
|
|
restore.snapped_screen_rect = screen_rect;
|
|
}
|
|
}
|
|
result.restore = PushString(window->cmds_arena, StringFromStruct(&restore));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void WND_EndFrame(WND_Frame frame)
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window_handle);
|
|
HWND hwnd = window->hwnd;
|
|
|
|
/* Process cmds */
|
|
for (WND_W32_CmdNode *n = window->first_cmd; n; n = n->next)
|
|
{
|
|
WND_Cmd cmd = n->cmd;
|
|
switch(cmd.kind)
|
|
{
|
|
//- Minimize
|
|
case WND_CmdKind_SetMinimized:
|
|
{
|
|
ShowWindow(hwnd, SW_MINIMIZE);
|
|
} break;
|
|
|
|
//- Maximize
|
|
case WND_CmdKind_SetMaximized:
|
|
{
|
|
ShowWindow(hwnd, SW_MAXIMIZE);
|
|
} break;
|
|
|
|
//- Fullscreen
|
|
case WND_CmdKind_SetFullscreen:
|
|
{
|
|
if (cmd.v != window->is_fullscreen)
|
|
{
|
|
DWORD old_style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
RECT old_rect = ZI;
|
|
{
|
|
GetClientRect(hwnd, (LPRECT)&old_rect);
|
|
ClientToScreen(hwnd, (LPPOINT)&old_rect.left);
|
|
ClientToScreen(hwnd, (LPPOINT)&old_rect.right);
|
|
AdjustWindowRect(&old_rect, old_style, 0);
|
|
}
|
|
RECT new_rect = ZI;
|
|
HWND new_hwnd = 0;
|
|
DWORD new_style = old_style;
|
|
if (cmd.v)
|
|
{
|
|
/* Enter fullscreen */
|
|
{
|
|
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
|
|
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
|
|
new_rect = monitor_info.rcMonitor;
|
|
}
|
|
new_style &= ~WS_OVERLAPPEDWINDOW;
|
|
new_style |= WS_POPUP;
|
|
window->fullscreen_restore_rect = old_rect;
|
|
new_hwnd = HWND_TOP;
|
|
}
|
|
else
|
|
{
|
|
/* Exit fullscreen */
|
|
new_rect = window->fullscreen_restore_rect;
|
|
new_style &= ~WS_POPUP;
|
|
new_style |= WS_OVERLAPPEDWINDOW;
|
|
new_hwnd = window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST;
|
|
}
|
|
window->is_fullscreen = cmd.v;
|
|
SetWindowLongPtr(hwnd, GWL_STYLE, new_style);
|
|
SetWindowPos(hwnd, new_hwnd, new_rect.left, new_rect.top, new_rect.right - new_rect.left, new_rect.bottom - new_rect.top, 0);
|
|
}
|
|
} break;
|
|
|
|
//- Force top
|
|
case WND_CmdKind_SetForcedTop:
|
|
{
|
|
window->is_forced_top = cmd.v;
|
|
SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
} break;
|
|
|
|
//- Restore
|
|
case WND_CmdKind_Restore:
|
|
{
|
|
/* FIXME: Cap bounds */
|
|
String restore_str = cmd.restore;
|
|
if (restore_str.len == sizeof(WND_W32_RestorableData))
|
|
{
|
|
WND_W32_RestorableData *restore = PushStructNoZero(scratch.arena, WND_W32_RestorableData);
|
|
CopyBytes(restore, restore_str.text, restore_str.len);
|
|
if (restore->magic == WND_W32_RestoreMagic)
|
|
{
|
|
WINDOWPLACEMENT placement = restore->placement;
|
|
SetWindowPlacement(hwnd, &placement);
|
|
SetWindowLongPtr(hwnd, GWL_STYLE, restore->style);
|
|
SetWindowLongPtr(hwnd, GWL_EXSTYLE, restore->ex_style);
|
|
|
|
if (restore->is_fullscreen)
|
|
{
|
|
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
|
|
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
|
|
RECT rect = monitor_info.rcMonitor;
|
|
window->is_fullscreen = 1;
|
|
window->fullscreen_restore_rect = restore->fullscreen_restore_rect;
|
|
SetWindowPos(hwnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);
|
|
}
|
|
|
|
if (restore->is_forced_top)
|
|
{
|
|
window->is_forced_top = 1;
|
|
SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
}
|
|
|
|
if (restore->is_snapped)
|
|
{
|
|
RECT rect = restore->snapped_screen_rect;
|
|
HWND pos_hwnd = window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST;
|
|
SetWindowPos(hwnd, pos_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
/* Bring window to front on first show */
|
|
if (!window->first_shown)
|
|
{
|
|
ShowWindow(hwnd, SW_SHOW);
|
|
SetForegroundWindow(hwnd);
|
|
BringWindowToTop(hwnd);
|
|
window->first_shown = 1;
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
}
|