power_play/src/window/window_win32/window_win32.c
2025-10-26 21:57:01 -05:00

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);
}