714 lines
22 KiB
C
714 lines
22 KiB
C
WND_W32_SharedState WND_W32_shared_state = Zi;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookimpl Bootstrap
|
|
|
|
void WND_Bootstrap(void)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
|
|
//- Initialize btn table
|
|
{
|
|
ZeroFixedArray(g->vk_to_button);
|
|
for (u32 i = 'A', j = Button_A; i <= 'Z'; ++i, ++j)
|
|
{
|
|
g->vk_to_button[i] = (Button)j;
|
|
}
|
|
for (u32 i = '0', j = Button_0; i <= '9'; ++i, ++j)
|
|
{
|
|
g->vk_to_button[i] = (Button)j;
|
|
}
|
|
for (u32 i = VK_F1, j = Button_F1; i <= VK_F24; ++i, ++j)
|
|
{
|
|
g->vk_to_button[i] = (Button)j;
|
|
}
|
|
g->vk_to_button[VK_ESCAPE] = Button_Escape;
|
|
g->vk_to_button[VK_OEM_3] = Button_GraveAccent;
|
|
g->vk_to_button[VK_OEM_MINUS] = Button_Minus;
|
|
g->vk_to_button[VK_OEM_PLUS] = Button_Equal;
|
|
g->vk_to_button[VK_BACK] = Button_Backspace;
|
|
g->vk_to_button[VK_TAB] = Button_Tab;
|
|
g->vk_to_button[VK_SPACE] = Button_Space;
|
|
g->vk_to_button[VK_RETURN] = Button_Enter;
|
|
g->vk_to_button[VK_CONTROL] = Button_Ctrl;
|
|
g->vk_to_button[VK_SHIFT] = Button_Shift;
|
|
g->vk_to_button[VK_MENU] = Button_Alt;
|
|
g->vk_to_button[VK_UP] = Button_Up;
|
|
g->vk_to_button[VK_LEFT] = Button_Left;
|
|
g->vk_to_button[VK_DOWN] = Button_Down;
|
|
g->vk_to_button[VK_RIGHT] = Button_Right;
|
|
g->vk_to_button[VK_DELETE] = Button_Delete;
|
|
g->vk_to_button[VK_PRIOR] = Button_PageUp;
|
|
g->vk_to_button[VK_NEXT] = Button_PageDown;
|
|
g->vk_to_button[VK_HOME] = Button_Home;
|
|
g->vk_to_button[VK_END] = Button_End;
|
|
g->vk_to_button[VK_OEM_2] = Button_ForwardSlash;
|
|
g->vk_to_button[VK_OEM_PERIOD] = Button_Period;
|
|
g->vk_to_button[VK_OEM_COMMA] = Button_Comma;
|
|
g->vk_to_button[VK_OEM_7] = Button_Quote;
|
|
g->vk_to_button[VK_OEM_4] = Button_LeftBracket;
|
|
g->vk_to_button[VK_OEM_6] = Button_RightBracket;
|
|
g->vk_to_button[VK_INSERT] = Button_Insert;
|
|
g->vk_to_button[VK_OEM_1] = Button_Semicolon;
|
|
}
|
|
|
|
//- Initialize cursors
|
|
{
|
|
HCURSOR arrow = LoadCursor(0, IDC_ARROW);
|
|
for (u64 cursor_idx = 0; cursor_idx < countof(g->cursors); ++cursor_idx)
|
|
{
|
|
g->cursors[cursor_idx] = arrow;
|
|
}
|
|
g->cursors[WND_CursorKind_Text] = LoadCursor(0, IDC_IBEAM);
|
|
g->cursors[WND_CursorKind_No] = LoadCursor(0, IDC_NO);
|
|
g->cursors[WND_CursorKind_Hand] = LoadCursor(0, IDC_HAND);
|
|
g->cursors[WND_CursorKind_Move] = LoadCursor(0, IDC_SIZEALL);
|
|
g->cursors[WND_CursorKind_HorizontalResize] = LoadCursor(0, IDC_SIZEWE);
|
|
g->cursors[WND_CursorKind_VerticalResize] = LoadCursor(0, IDC_SIZENS);
|
|
g->cursors[WND_CursorKind_TlBrResize] = LoadCursor(0, IDC_SIZENWSE);
|
|
g->cursors[WND_CursorKind_TrBlResize] = LoadCursor(0, IDC_SIZENESW);
|
|
}
|
|
|
|
//- 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));
|
|
}
|
|
|
|
//- Dispatch message processor
|
|
DispatchWave(Lit("Win32 msg loop"), 1, WND_W32_ProcessMessagesForever, 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ 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 */
|
|
void WND_W32_ProcessMessagesForever(WaveLaneCtx *lane)
|
|
{
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = &g->window;
|
|
window->w2u_events_arena = AcquireArena(Gibi(64));
|
|
|
|
//- Initialize hwnd
|
|
{
|
|
/*
|
|
* 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 */
|
|
window->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(window->hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, (LPCVOID)&dark_mode, sizeof(dark_mode));
|
|
|
|
/* Set window as userdata */
|
|
/* FIXME: Necessary? */
|
|
SetWindowLongPtrW(window->hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
|
}
|
|
|
|
//- Acquire swapchain
|
|
{
|
|
window->swapchain = G_AcquireSwapchain((u64)window->hwnd);
|
|
}
|
|
|
|
//- Begin processing messages
|
|
Atomic32Set(&window->is_ready, 1);
|
|
for (;;)
|
|
{
|
|
// SetFocus(window->hwnd);
|
|
MSG msg = Zi;
|
|
GetMessageW(&msg, 0, 0, 0);
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
|
|
/* Destroy window */
|
|
DestroyWindow(window->hwnd);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Message processing
|
|
|
|
void WND_W32_PushEvent(WND_W32_Window *window, ControllerEvent event)
|
|
{
|
|
LockTicketMutex(&window->w2u_tm);
|
|
{
|
|
*PushStructNoZero(window->w2u_events_arena, ControllerEvent) = event;
|
|
}
|
|
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_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_Quit });
|
|
} break;
|
|
|
|
//- Cursor kind
|
|
|
|
case WM_SETCURSOR:
|
|
{
|
|
/* FIXME */
|
|
HCURSOR new_cursor = (HCURSOR)Atomic64Fetch(&window->new_cursor);
|
|
if (new_cursor != window->active_cursor)
|
|
{
|
|
SetCursor(new_cursor);
|
|
window->active_cursor = new_cursor;
|
|
result = 1;
|
|
}
|
|
} break;
|
|
|
|
//- Keyboard button
|
|
case WM_SYSKEYUP:
|
|
case WM_SYSKEYDOWN:
|
|
case WM_KEYUP:
|
|
case WM_KEYDOWN:
|
|
{
|
|
WORD vk_code = LOWORD(wparam);
|
|
ControllerEvent event = Zi;
|
|
if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
|
|
{
|
|
event.kind = ControllerEventKind_ButtonDown;
|
|
event.is_repeat = (lparam & 0x40000000) != 0;
|
|
}
|
|
else if (msg == WM_KEYUP || msg == WM_SYSKEYUP)
|
|
{
|
|
event.kind = ControllerEventKind_ButtonUp;
|
|
}
|
|
if (vk_code < countof(g->vk_to_button))
|
|
{
|
|
event.button = g->vk_to_button[vk_code];
|
|
}
|
|
WND_W32_PushEvent(window, event);
|
|
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')
|
|
{
|
|
ControllerEvent event = Zi;
|
|
event.kind = ControllerEventKind_Text;
|
|
event.text_codepoint = codepoint;
|
|
WND_W32_PushEvent(window, event);
|
|
}
|
|
} 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:
|
|
{
|
|
ControllerEvent event = Zi;
|
|
b32 is_down = msg == WM_LBUTTONDOWN ||
|
|
msg == WM_MBUTTONDOWN ||
|
|
msg == WM_RBUTTONDOWN ||
|
|
msg == WM_XBUTTONDOWN;
|
|
if (is_down)
|
|
{
|
|
event.kind = ControllerEventKind_ButtonDown;
|
|
SetCapture(hwnd);
|
|
}
|
|
else
|
|
{
|
|
event.kind = ControllerEventKind_ButtonUp;
|
|
ReleaseCapture();
|
|
}
|
|
switch (msg)
|
|
{
|
|
case WM_LBUTTONUP: case WM_LBUTTONDOWN: event.button = Button_M1; break;
|
|
case WM_RBUTTONUP: case WM_RBUTTONDOWN: event.button = Button_M2; break;
|
|
case WM_MBUTTONUP: case WM_MBUTTONDOWN: event.button = Button_M3; break;
|
|
case WM_XBUTTONUP: case WM_XBUTTONDOWN:
|
|
{
|
|
u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam);
|
|
if (wparam_xbutton == XBUTTON1)
|
|
{
|
|
event.button = Button_M4;
|
|
}
|
|
else if (wparam_xbutton == XBUTTON2)
|
|
{
|
|
event.button = Button_M5;
|
|
}
|
|
} break;
|
|
}
|
|
if (event.button)
|
|
{
|
|
WND_W32_PushEvent(window, event);
|
|
}
|
|
} break;
|
|
|
|
//- Mouse wheel
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
|
|
i32 dir = delta >= 0 ? 1 : -1;
|
|
Button btn = dir >= 0 ? Button_MWheelUp : Button_MWheelDown;
|
|
for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA)
|
|
{
|
|
/* Send a button down & button up event simultaneously */
|
|
WND_W32_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_ButtonDown, .button = btn });
|
|
WND_W32_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_ButtonUp, .button = btn });
|
|
}
|
|
} break;
|
|
|
|
//- Cursor move
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
i32 x = GET_X_LPARAM(lparam);
|
|
i32 y = GET_Y_LPARAM(lparam);
|
|
ControllerEvent event = Zi;
|
|
event.kind = ControllerEventKind_CursorMove;
|
|
event.cursor_pos = VEC2I32(x, y);
|
|
WND_W32_PushEvent(window, event);
|
|
} 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)
|
|
{
|
|
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;
|
|
ControllerEvent event = Zi;
|
|
event.kind = ControllerEventKind_MouseMove;
|
|
event.mouse_delta = VEC2I32(x, y);
|
|
WND_W32_PushEvent(window, event);
|
|
}
|
|
}
|
|
EndScratch(scratch);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookimpl Command
|
|
|
|
void WND_PushCmd_(WND_Frame frame, WND_Cmd desc)
|
|
{
|
|
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window);
|
|
WND_W32_CmdNode *n = PushStruct(window->frame_arena, WND_W32_CmdNode);
|
|
n->cmd = desc;
|
|
SllQueuePush(window->first_cmd, window->last_cmd, n);
|
|
if (desc.kind == WND_CmdKind_Restore)
|
|
{
|
|
n->cmd.restore = PushString(window->frame_arena, desc.restore);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ @hookimpl Frame
|
|
|
|
WND_Frame WND_BeginFrame(G_Format backbuffer_format, WND_BackbufferSizeMode backbuffer_size_mode)
|
|
{
|
|
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.v = (u64)window;
|
|
|
|
/* 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;
|
|
}
|
|
result.monitor_size = VEC2I32(monitor_rect.right - monitor_rect.left, monitor_rect.bottom - monitor_rect.top);
|
|
result.monitor_size.x = MaxI32(result.monitor_size.x, 1);
|
|
result.monitor_size.y = MaxI32(result.monitor_size.y, 1);
|
|
|
|
/* 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);
|
|
result.draw_size.x = MaxI32(result.draw_size.x, 1);
|
|
result.draw_size.y = MaxI32(result.draw_size.y, 1);
|
|
|
|
/* Prepare backbuffer */
|
|
Vec2I32 backbuffer_size = Zi;
|
|
if (backbuffer_size_mode == WND_BackbufferSizeMode_MatchWindow)
|
|
{
|
|
backbuffer_size = result.draw_size;
|
|
}
|
|
else if (backbuffer_size_mode == WND_BackbufferSizeMode_MatchMonitor)
|
|
{
|
|
backbuffer_size = result.monitor_size;
|
|
}
|
|
result.backbuffer = G_PrepareBackbuffer(window->swapchain, backbuffer_format, backbuffer_size);
|
|
|
|
/* Reset per-frame data */
|
|
if (!window->frame_arena)
|
|
{
|
|
window->frame_arena = AcquireArena(Gibi(64));
|
|
}
|
|
ResetArena(window->frame_arena);
|
|
window->first_cmd = 0;
|
|
window->last_cmd = 0;
|
|
|
|
/* Pop user input */
|
|
{
|
|
LockTicketMutex(&window->w2u_tm);
|
|
{
|
|
ControllerEvent *src = ArenaFirst(window->w2u_events_arena, ControllerEvent);
|
|
result.controller_events.count = ArenaCount(window->w2u_events_arena, ControllerEvent);
|
|
result.controller_events.events = PushStructsNoZero(window->frame_arena, ControllerEvent, result.controller_events.count);
|
|
CopyStructs(result.controller_events.events, src, result.controller_events.count);
|
|
ResetArena(window->w2u_events_arena);
|
|
}
|
|
UnlockTicketMutex(&window->w2u_tm);
|
|
}
|
|
|
|
/* 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;
|
|
result.has_focus = GetForegroundWindow() == hwnd;
|
|
|
|
/* 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;
|
|
restore.has_focus = result.has_focus;
|
|
if (IsWindowArranged(hwnd))
|
|
{
|
|
restore.is_snapped = 1;
|
|
restore.snapped_screen_rect = screen_rect;
|
|
}
|
|
}
|
|
result.restore = PushString(window->frame_arena, StringFromStruct(&restore));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void WND_EndFrame(WND_Frame frame, i32 vsync)
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
WND_W32_SharedState *g = &WND_W32_shared_state;
|
|
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window);
|
|
HWND hwnd = window->hwnd;
|
|
|
|
/* Process cmds */
|
|
b32 was_restored = 0;
|
|
WND_CursorKind new_cursor = (WND_CursorKind)Atomic64Fetch(&window->new_cursor);
|
|
for (WND_W32_CmdNode *n = window->first_cmd; n; n = n->next)
|
|
{
|
|
WND_Cmd cmd = n->cmd;
|
|
switch(cmd.kind)
|
|
{
|
|
default: break;
|
|
|
|
//- 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;
|
|
|
|
//- Cursor
|
|
case WND_CmdKind_SetCursor:
|
|
{
|
|
new_cursor = cmd.cursor;
|
|
} 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;
|
|
was_restored = 1;
|
|
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);
|
|
}
|
|
|
|
if (restore->has_focus)
|
|
{
|
|
ShowWindow(hwnd, SW_SHOW);
|
|
SetForegroundWindow(hwnd);
|
|
BringWindowToTop(hwnd);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
/* Bring window to front on first frame */
|
|
if (!was_restored && window->frame_gen == 0)
|
|
{
|
|
ShowWindow(hwnd, SW_SHOW);
|
|
SetForegroundWindow(hwnd);
|
|
BringWindowToTop(hwnd);
|
|
}
|
|
|
|
/* Set cursor */
|
|
Atomic64Set(&window->new_cursor, (u64)g->cursors[new_cursor]);
|
|
|
|
/* Commit backbuffer */
|
|
G_CommitBackbuffer(frame.backbuffer, vsync);
|
|
|
|
++window->frame_gen;
|
|
EndScratch(scratch);
|
|
}
|