SBgl 0.1.0
A graphics framework in C99
Loading...
Searching...
No Matches
window.c
Go to the documentation of this file.
2#include "core/sbl_arena.h"
3#include "win32_internal.h"
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
14static void win32_report_error(const wchar_t* message) {
15 // Output to debugger (visible in Visual Studio Output window)
16 OutputDebugStringW(message);
17 OutputDebugStringW(L"\n");
18
19 // Output to stderr (visible in console)
20 int len = WideCharToMultiByte(CP_UTF8, 0, message, -1, NULL, 0, NULL, NULL);
21 if (len > 0) {
22 char* utf8 = (char*)malloc(len);
23 if (utf8) {
24 WideCharToMultiByte(CP_UTF8, 0, message, -1, utf8, len, NULL, NULL);
25 fprintf(stderr, "[Win32] %s\n", utf8);
26 free(utf8);
27 }
28 }
29}
30
31static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
32 sbgl_Window* window = (sbgl_Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
33
34 switch (msg) {
35 case WM_CLOSE:
36 if (window)
37 window->shouldClose = true;
38 return 0;
39 case WM_DESTROY:
40 PostQuitMessage(0);
41 return 0;
42 case WM_SIZE:
43 if (window) {
44 int w = LOWORD(lparam);
45 int h = HIWORD(lparam);
46 if (w > 0 && h > 0 && (w != window->width || h != window->height)) {
47 window->width = w;
48 window->height = h;
49 window->resized = true;
50 }
51 }
52 break;
53 case WM_SETFOCUS:
54 if (window)
55 window->focused = true;
56 break;
57 case WM_KILLFOCUS:
58 if (window)
59 window->focused = false;
60 break;
61 case WM_INPUT: {
62 // Handle raw input for high-precision mouse deltas when cursor is locked
63 if (window && window->cursorLocked) {
64 RAWINPUT raw;
65 UINT size = sizeof(raw);
66
67 if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &raw,
68 &size, sizeof(RAWINPUTHEADER)) != (UINT)-1) {
69 if (raw.header.dwType == RIM_TYPEMOUSE) {
70 // Accumulate raw deltas
71 window->accumulatedDeltaX += raw.mouse.lLastX;
72 window->accumulatedDeltaY += raw.mouse.lLastY;
73 }
74 }
75 }
76 break;
77 }
78 case WM_DPICHANGED: {
79 // Handle DPI changes for high-DPI display support
80 if (window) {
81 RECT* const prcNewWindow = (RECT*)lparam;
82 SetWindowPos(window->hwnd, NULL,
83 prcNewWindow->left,
84 prcNewWindow->top,
85 prcNewWindow->right - prcNewWindow->left,
86 prcNewWindow->bottom - prcNewWindow->top,
87 SWP_NOZORDER | SWP_NOACTIVATE);
88 window->resized = true;
89 }
90 break;
91 }
92 }
93
94 // Pass input messages to the input system
95 if (window)
96 win32_internal_process_message(window->input, msg, wparam, lparam);
97
98 return DefWindowProc(hwnd, msg, wparam, lparam);
99}
100
102 struct SblArena* arena,
103 sbgl_InputState* input,
104 int width,
105 int height,
106 const char* title
107) {
108 // Set DPI awareness for Windows 10+ (Per-Monitor V2)
109 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
110
111 HINSTANCE hinstance = GetModuleHandle(NULL);
112
113 WNDCLASSW wc = { 0 };
114 wc.lpfnWndProc = WindowProc;
115 wc.hInstance = hinstance;
116 wc.lpszClassName = L"SBglWindowClass";
117 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
118
119 RegisterClassW(&wc);
120
121 // Convert title to WideChar
122 int title_len = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
123 wchar_t* wtitle = malloc(title_len * sizeof(wchar_t));
124 MultiByteToWideChar(CP_UTF8, 0, title, -1, wtitle, title_len);
125
126 // Calculate proper window size for requested client area
127 RECT rect = { 0, 0, width, height };
128 AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, 0);
129
130 HWND hwnd = CreateWindowExW(
131 0,
132 wc.lpszClassName,
133 wtitle,
134 WS_OVERLAPPEDWINDOW,
135 CW_USEDEFAULT,
136 CW_USEDEFAULT,
137 rect.right - rect.left, // Adjusted width including borders
138 rect.bottom - rect.top, // Adjusted height including title bar
139 NULL,
140 NULL,
141 hinstance,
142 NULL
143 );
144
145 free(wtitle);
146
147 if (!hwnd) {
148 win32_report_error(L"Failed to create window: CreateWindowExW returned NULL");
149 return NULL;
150 }
151
153 if (!window) {
154 DestroyWindow(hwnd);
155 return NULL;
156 }
157 window->hinstance = hinstance;
158 window->hwnd = hwnd;
159 window->shouldClose = false;
160 window->resized = false;
161 window->width = width;
162 window->height = height;
163 window->focused = true;
164 window->cursorVisible = true;
165 window->cursorLocked = false;
166 window->input = input;
167 window->accumulatedDeltaX = 0;
168 window->accumulatedDeltaY = 0;
169
170 /* Store class name for unregistration on destroy */
171 wcsncpy(window->className, wc.lpszClassName, 255);
172 window->className[255] = L'\0';
173
174 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);
175
176 ShowWindow(hwnd, SW_SHOW);
177 return window;
178}
179
181 if (!window)
182 return;
183
184 /* Unregister raw input if cursor is still locked */
185 if (window->cursorLocked) {
186 RAWINPUTDEVICE rid = {
187 .usUsagePage = 0x01,
188 .usUsage = 0x02,
189 .dwFlags = RIDEV_REMOVE,
190 .hwndTarget = NULL
191 };
192 RegisterRawInputDevices(&rid, 1, sizeof(rid));
193 ClipCursor(NULL);
194 }
195
196 DestroyWindow(window->hwnd);
197
198 /* Unregister window class to prevent resource leaks */
199 if (window->className[0] != L'\0') {
200 UnregisterClassW(window->className, window->hinstance);
201 }
202}
203
204bool sbgl_os_WindowShouldClose(sbgl_Window* window) { return window ? window->shouldClose : true; }
205
206void sbgl_os_GetWindowSize(sbgl_Window* window, int* w, int* h) {
207 if (!window) {
208 if (w) *w = 0;
209 if (h) *h = 0;
210 return;
211 }
212 if (w) *w = window->width;
213 if (h) *h = window->height;
214}
215
217 if (!window)
218 return false;
219 bool r = window->resized;
220 window->resized = false;
221 return r;
222}
223
224void sbgl_os_SetCursorVisible(sbgl_Window* window, bool visible) {
225 if (!window || window->cursorVisible == visible)
226 return;
227
228 /* The cursor visibility state is updated and the system cursor counter
229 is incremented or decremented accordingly. */
230 window->cursorVisible = visible;
231 if (visible) {
232 ShowCursor(TRUE);
233 } else {
234 ShowCursor(FALSE);
235 }
236}
237
238void sbgl_os_SetCursorLocked(sbgl_Window* window, bool locked) {
239 if (!window || window->cursorLocked == locked)
240 return;
241
242 window->cursorLocked = locked;
243
244 if (locked) {
245 /* Register for raw mouse input to get high-precision deltas */
246 RAWINPUTDEVICE rid = {
247 .usUsagePage = 0x01, // Generic Desktop
248 .usUsage = 0x02, // Mouse
249 .dwFlags = RIDEV_NOLEGACY | RIDEV_CAPTUREMOUSE,
250 .hwndTarget = window->hwnd
251 };
252 if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
253 win32_report_error(L"Failed to register raw input device");
254 }
255
256 /* The cursor is confined to the window client area by clipping it
257 to the screen-space coordinates of the client rectangle. */
258 RECT rect;
259 GetClientRect(window->hwnd, &rect);
260 ClientToScreen(window->hwnd, (POINT*)&rect.left);
261 ClientToScreen(window->hwnd, (POINT*)&rect.right);
262 ClipCursor(&rect);
263
264 /* Hide the cursor */
265 ShowCursor(FALSE);
266 window->cursorVisible = false;
267
268 /* The cursor is initially centered within the window to ensure
269 consistent relative motion tracking. */
270 int centerX = window->width / 2;
271 int centerY = window->height / 2;
272 POINT p = { centerX, centerY };
273 ClientToScreen(window->hwnd, &p);
274 SetCursorPos(p.x, p.y);
275 } else {
276 /* Unregister raw input device */
277 RAWINPUTDEVICE rid = {
278 .usUsagePage = 0x01,
279 .usUsage = 0x02,
280 .dwFlags = RIDEV_REMOVE,
281 .hwndTarget = NULL
282 };
283 RegisterRawInputDevices(&rid, 1, sizeof(rid));
284
285 /* Any existing cursor clipping is removed, allowing the pointer
286 to move freely across the entire desktop. */
287 ClipCursor(NULL);
288
289 /* Show the cursor */
290 ShowCursor(TRUE);
291 window->cursorVisible = true;
292 }
293}
294
296 /* Returns the current focus state as tracked by window messages. */
297 return window ? window->focused : false;
298}
299
301 MSG msg;
302 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
303 TranslateMessage(&msg);
304 DispatchMessage(&msg);
305 }
306
307 // Update input states after processing all messages
308 if (window)
310}
311
313 (void)window; // Not used, but kept for API consistency
314 LARGE_INTEGER count;
315 QueryPerformanceCounter(&count);
316 return (uint64_t)count.QuadPart;
317}
318
320 (void)window;
321 LARGE_INTEGER freq;
322 QueryPerformanceFrequency(&freq);
323 return (uint64_t)freq.QuadPart;
324}
325
327 return window ? (void*)window->hwnd : NULL;
328}
329
331 return window ? (void*)window->hinstance : NULL;
332}
333
335 (void)window;
336 return NULL; // Not used on Win32
337}
void win32_internal_process_message(sbgl_InputState *input, UINT msg, WPARAM wparam, LPARAM lparam)
Definition input.c:98
void win32_internal_update_input_states(sbgl_InputState *input, sbgl_Window *window)
Definition input.c:142
Internal Platform Abstraction Layer (HAL).
Arena allocator implementation.
#define SBL_ARENA_PUSH_STRUCT_ZERO(arena, type)
Definition sbl_arena.h:20
Arena allocator.
Definition sbl_arena.h:47
Represents the real-time state of physical inputs.
Definition sbgl_input.h:165
Native X11 window state.
sbgl_InputState * input
wchar_t className[256]
HINSTANCE hinstance
bool sbgl_os_WasWindowResized(sbgl_Window *window)
Checks if the window has been resized since the last check.
Definition window.c:216
uint64_t sbgl_os_GetPerfCount(sbgl_Window *window)
Gets the high-resolution performance counter.
Definition window.c:312
void sbgl_os_PollEvents(sbgl_Window *window)
Dispatches OS events (messages/protocol requests).
Definition window.c:300
void sbgl_os_SetCursorLocked(sbgl_Window *window, bool locked)
Locks or unlocks the cursor within the window bounds.
Definition window.c:238
uint64_t sbgl_os_GetPerfFreq(sbgl_Window *window)
Gets the performance counter frequency.
Definition window.c:319
void * sbgl_os_GetNativeWindowHandle(sbgl_Window *window)
Retrieves the raw window handle for Vulkan surface creation.
Definition window.c:326
void sbgl_os_SetCursorVisible(sbgl_Window *window, bool visible)
Sets the visibility of the OS cursor for the given window.
Definition window.c:224
sbgl_Window * sbgl_os_CreateWindow(struct SblArena *arena, sbgl_InputState *input, int width, int height, const char *title)
Definition window.c:101
bool sbgl_os_WindowShouldClose(sbgl_Window *window)
Checks the window's close flag.
Definition window.c:204
void sbgl_os_GetWindowSize(sbgl_Window *window, int *w, int *h)
Retrieves the current client area size.
Definition window.c:206
static void win32_report_error(const wchar_t *message)
Reports errors to both debugger output and stderr.
Definition window.c:14
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
Definition window.c:31
bool sbgl_os_IsWindowFocused(sbgl_Window *window)
Checks if the window currently has input focus.
Definition window.c:295
void * sbgl_os_GetNativeDisplayHandle(sbgl_Window *window)
Retrieves the native display handle (Linux specific).
Definition window.c:334
void * sbgl_os_GetNativeInstanceHandle(sbgl_Window *window)
Retrieves the native instance handle (Win32 specific).
Definition window.c:330
void sbgl_os_DestroyWindow(sbgl_Window *window)
Destroys a native window.
Definition window.c:180