1 module framed.win32; 2 import framed; 3 4 // dfmt off 5 version (Windows): 6 // dfmt on 7 8 import core.stdc.stdlib; 9 import core.sys.windows.windows; 10 11 nothrow: 12 13 private: 14 15 struct LinkedNode { 16 Event ev; 17 LinkedNode* next; 18 } 19 20 struct Win32Data { 21 HWND hwnd; 22 uint[] buffer; 23 LinkedNode* evqHead; 24 LinkedNode* evqTail; 25 int rWidth; 26 int rHeight; 27 bool[cast(size_t) KeyCode.Menu + 1] keysDown; 28 bool[cast(size_t) MouseButton.Right + 1] buttonsDown; 29 bool mouseOutside; 30 bool cursorTracked; 31 } 32 33 void advanceQueue(Win32Data* self) { 34 MSG msg; 35 while (PeekMessageW(&msg, self.hwnd, 0, 0, PM_REMOVE)) { 36 TranslateMessage(&msg); 37 DispatchMessage(&msg); 38 } 39 } 40 41 void addToEvq(Win32Data* self, Event ev) { 42 LinkedNode* node = cast(LinkedNode*) malloc(LinkedNode.sizeof); 43 node.ev = ev; 44 node.next = null; 45 if (self.evqTail == null) { 46 self.evqHead = node; 47 } 48 else { 49 self.evqTail.next = node; 50 } 51 self.evqTail = node; 52 } 53 54 int getWidth(Win32Data* self) { 55 RECT r; 56 if (!GetClientRect(self.hwnd, &r)) { 57 return 0; 58 } 59 return cast(int)(r.right - r.left); 60 } 61 62 int getHeight(Win32Data* self) { 63 RECT r; 64 if (!GetClientRect(self.hwnd, &r)) { 65 return 0; 66 } 67 return cast(int)(r.bottom - r.top); 68 } 69 70 void close(Win32Data* self) { 71 DestroyWindow(self.hwnd); 72 free(self); 73 } 74 75 void update(Win32Data* self, uint[] buffer) { 76 self.buffer = buffer; 77 InvalidateRect(self.hwnd, NULL, TRUE); 78 } 79 80 void yield(Win32Data* self) { 81 MSG msg; 82 while (self.evqHead == null) { 83 GetMessage(&msg, self.hwnd, 0, 0); 84 TranslateMessage(&msg); 85 DispatchMessage(&msg); 86 } 87 } 88 89 bool evqEmpty(Win32Data* self) { 90 advanceQueue(self); 91 return self.evqHead == null; 92 } 93 94 Event evqFront(Win32Data* self) { 95 advanceQueue(self); 96 assert(self.evqHead != null); 97 return self.evqHead.ev; 98 } 99 100 void evqPopFront(Win32Data* self) { 101 advanceQueue(self); 102 auto next = self.evqHead.next; 103 free(self.evqHead); 104 self.evqHead = next; 105 if (self.evqHead == null) { 106 self.evqTail = null; 107 } 108 } 109 110 int translateKey(WPARAM wParam, LPARAM lParam) { 111 WPARAM vk = wParam; 112 UINT scancode = (lParam >> 16) & 0xFF; 113 int extended = (lParam >> 24) & 1; 114 115 switch (wParam) { 116 case VK_SHIFT: 117 vk = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); 118 break; 119 case VK_CONTROL: 120 vk = extended ? VK_RCONTROL : VK_LCONTROL; 121 break; 122 case VK_MENU: 123 vk = extended ? VK_RMENU : VK_LMENU; 124 break; 125 default: 126 break; 127 } 128 129 // dfmt off 130 switch (vk) { 131 case VK_SPACE: return KeyCode.Space; 132 case VK_OEM_7: return KeyCode.Quote; 133 case VK_OEM_COMMA: return KeyCode.Comma; 134 case VK_OEM_MINUS: return KeyCode.Minus; 135 case VK_OEM_PERIOD: return KeyCode.Period; 136 case VK_OEM_2: return KeyCode.Slash; 137 case '0': .. case '9': return KeyCode.D0 + cast(int)(wParam - '0'); 138 case VK_OEM_1: return KeyCode.Semicolon; 139 case VK_OEM_PLUS: return KeyCode.Equal; 140 case 'A': .. case 'Z': return KeyCode.A + cast(int)(wParam - 'A'); 141 case VK_OEM_4: return KeyCode.LeftBracket; 142 case VK_OEM_5: return KeyCode.Backslash; 143 case VK_OEM_6: return KeyCode.RightBracket; 144 case VK_OEM_3: return KeyCode.Backtick; 145 case VK_ESCAPE: return KeyCode.Escape; 146 case VK_RETURN: return KeyCode.Enter; 147 case VK_TAB: return KeyCode.Tab; 148 case VK_BACK: return KeyCode.Backspace; 149 case VK_INSERT: return KeyCode.Insert; 150 case VK_DELETE: return KeyCode.Delete; 151 case VK_RIGHT: return KeyCode.Right; 152 case VK_LEFT: return KeyCode.Left; 153 case VK_DOWN: return KeyCode.Down; 154 case VK_UP: return KeyCode.Up; 155 case VK_PRIOR: return KeyCode.PageUp; 156 case VK_NEXT: return KeyCode.PageDown; 157 case VK_HOME: return KeyCode.Home; 158 case VK_END: return KeyCode.End; 159 case VK_CAPITAL: return KeyCode.CapsLock; 160 case VK_SCROLL: return KeyCode.ScrollLock; 161 case VK_NUMLOCK: return KeyCode.NumLock; 162 case VK_SNAPSHOT: return KeyCode.PrintScreen; 163 case VK_PAUSE: return KeyCode.Pause; 164 case VK_F1: .. case VK_F24: return KeyCode.F1 + cast(int)(wParam - VK_F1); 165 case VK_NUMPAD0: .. case VK_NUMPAD9: return KeyCode.Numpad0 + cast(int)(wParam - VK_NUMPAD0); 166 case VK_DECIMAL: return KeyCode.NumpadPeriod; 167 case VK_DIVIDE: return KeyCode.NumpadSlash; 168 case VK_MULTIPLY: return KeyCode.NumpadMultiply; 169 case VK_SUBTRACT: return KeyCode.NumpadMinus; 170 case VK_ADD: return KeyCode.NumpadPlus; 171 // TODO: KeyCode.NumpadEqual 172 case VK_LSHIFT: return KeyCode.LeftShift; 173 case VK_LCONTROL: return KeyCode.LeftCtrl; 174 case VK_LMENU: return KeyCode.LeftAlt; 175 case VK_LWIN: return KeyCode.LeftSuper; 176 case VK_RSHIFT: return KeyCode.RightShift; 177 case VK_RCONTROL: return KeyCode.RightCtrl; 178 case VK_RMENU: return KeyCode.RightAlt; 179 case VK_RWIN: return KeyCode.RightSuper; 180 case VK_APPS: return KeyCode.Menu; 181 default: 182 import core.stdc.stdio : printf; 183 debug printf("Unrecognized virtual key code: %x\n", cast(int) wParam); 184 return -1; 185 } 186 // dfmt on 187 } 188 189 extern (Windows) LRESULT wndProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam) nothrow { 190 Win32Data* self = cast(Win32Data*) GetWindowLongPtr(hwnd, GWLP_USERDATA); 191 192 if (self == null) { 193 return DefWindowProc(hwnd, Msg, wParam, lParam); 194 } 195 196 switch (Msg) { 197 case WM_CLOSE: 198 addToEvq(self, Event(EventType.CloseRequest)); 199 return 0; 200 case WM_ENTERSIZEMOVE: 201 self.rWidth = getWidth(self); 202 self.rHeight = getHeight(self); 203 break; 204 case WM_EXITSIZEMOVE: 205 auto ev = Event(EventType.Resize); 206 if (self.rWidth != getWidth(self) || self.rHeight != getHeight(self)) { 207 ev.width = self.rWidth; 208 ev.height = self.rHeight; 209 self.rWidth = getWidth(self); 210 self.rHeight = getHeight(self); 211 addToEvq(self, ev); 212 } 213 break; 214 case WM_SIZE: 215 if (wParam == 0 && self.rWidth != 0 && self.rHeight != 0) 216 break; 217 auto ev = Event(EventType.Resize); 218 if (self.rWidth != getWidth(self) || self.rHeight != getHeight(self)) { 219 ev.width = self.rWidth; 220 ev.height = self.rHeight; 221 self.rWidth = getWidth(self); 222 self.rHeight = getHeight(self); 223 addToEvq(self, ev); 224 } 225 break; 226 case WM_MOUSEMOVE: 227 if (!self.cursorTracked) { 228 self.cursorTracked = true; 229 230 TRACKMOUSEEVENT track; 231 track.cbSize = TRACKMOUSEEVENT.sizeof; 232 track.dwFlags = TME_LEAVE; 233 track.hwndTrack = self.hwnd; 234 track.dwHoverTime = 0; 235 236 TrackMouseEvent(&track); 237 } 238 239 auto ev = Event(EventType.MouseMove); 240 ev.x = cast(int)(lParam & 0xFFFF); 241 ev.y = cast(int)(lParam >> 16); 242 if (self.mouseOutside) { 243 self.mouseOutside = false; 244 addToEvq(self, Event(EventType.MouseEnter)); 245 } 246 addToEvq(self, ev); 247 break; 248 case WM_MOUSELEAVE: 249 self.mouseOutside = true; 250 self.cursorTracked = false; 251 addToEvq(self, Event(EventType.MouseLeave)); 252 break; 253 case WM_LBUTTONDOWN: 254 case WM_MBUTTONDOWN: 255 case WM_RBUTTONDOWN: 256 case WM_LBUTTONUP: 257 case WM_MBUTTONUP: 258 case WM_RBUTTONUP: 259 MouseButton button; 260 261 if (Msg == WM_LBUTTONDOWN || Msg == WM_LBUTTONUP) { 262 button = MouseButton.Left; 263 } 264 else if (Msg == WM_MBUTTONDOWN || Msg == WM_MBUTTONUP) { 265 button = MouseButton.Middle; 266 } 267 else if (Msg == WM_RBUTTONDOWN || Msg == WM_RBUTTONUP) { 268 button = MouseButton.Right; 269 } 270 271 EventType type; 272 273 bool set = true; 274 foreach (v; self.buttonsDown) { 275 if (v) { 276 set = false; 277 break; 278 } 279 } 280 281 if (set) { 282 SetCapture(self.hwnd); 283 } 284 285 if (Msg == WM_LBUTTONDOWN || Msg == WM_MBUTTONDOWN 286 || Msg == WM_RBUTTONDOWN) { 287 type = EventType.MouseDown; 288 if (self.buttonsDown[button]) 289 return 0; 290 self.buttonsDown[button] = true; 291 } 292 else { 293 type = EventType.MouseUp; 294 if (!self.buttonsDown[button]) 295 return 0; 296 self.buttonsDown[button] = false; 297 } 298 299 set = true; 300 foreach (v; self.buttonsDown) { 301 if (v) { 302 set = false; 303 break; 304 } 305 } 306 307 if (set) { 308 ReleaseCapture(); 309 } 310 311 auto ev = Event(type); 312 ev.button = button; 313 addToEvq(self, ev); 314 315 return 0; 316 case WM_KEYDOWN: 317 case WM_SYSKEYDOWN: 318 auto ev = Event(EventType.KeyDown); 319 int key = translateKey(wParam, lParam); 320 if (key == -1) { 321 break; 322 } 323 ev.key = cast(KeyCode) key; 324 if (self.keysDown[ev.key]) { 325 ev.type = EventType.KeyRepeat; 326 } 327 else { 328 self.keysDown[ev.key] = true; 329 } 330 addToEvq(self, ev); 331 break; 332 case WM_KEYUP: 333 case WM_SYSKEYUP: 334 auto ev = Event(EventType.KeyUp); 335 int key = translateKey(wParam, lParam); 336 if (key == -1) { 337 break; 338 } 339 ev.key = cast(KeyCode) key; 340 self.keysDown[ev.key] = false; 341 addToEvq(self, ev); 342 break; 343 case WM_ERASEBKGND: 344 return TRUE; 345 case WM_PAINT: 346 PAINTSTRUCT ps; 347 HDC hdc = BeginPaint(hwnd, &ps); 348 349 RECT r; 350 GetClientRect(hwnd, &r); 351 352 int width = cast(int)(r.right - r.left); 353 int height = cast(int)(r.bottom - r.top); 354 355 if (width == 0 || height == 0) { 356 return 0; 357 } 358 359 BYTE[BITMAPINFO.bmiColors.offsetof + (3 * DWORD.sizeof)] bitmapinfo; 360 BITMAPINFOHEADER* bih = cast(BITMAPINFOHEADER*) bitmapinfo; 361 bih.biSize = BITMAPINFOHEADER.sizeof; 362 bih.biWidth = width; 363 bih.biHeight = height; 364 bih.biPlanes = 1; 365 bih.biBitCount = 32; 366 bih.biCompression = BI_BITFIELDS; 367 DWORD* pMasks = cast(DWORD*)&bitmapinfo[bih.biSize]; 368 pMasks[0] = 0xFF0000; 369 pMasks[1] = 0x00FF00; 370 pMasks[2] = 0x0000FF; 371 372 if (self.buffer.length == width * cast(size_t) height) { 373 StretchDIBits(hdc, 0, 0, width, height, 0, height + 1, width, 374 -height, cast(void*) self.buffer.ptr, 375 cast(BITMAPINFO*) bih, DIB_RGB_COLORS, SRCCOPY); 376 } 377 378 EndPaint(hwnd, &ps); 379 380 return 0; 381 default: 382 break; 383 } 384 385 return DefWindowProc(hwnd, Msg, wParam, lParam); 386 } 387 388 HMODULE hInstance; 389 390 bool win32Inited = false; 391 void win32Init() { 392 if (win32Inited) 393 return; 394 395 win32Inited = true; 396 397 hInstance = GetModuleHandle(NULL); 398 399 WNDCLASSEX wc; 400 wc.cbSize = WNDCLASSEX.sizeof; 401 wc.style = 0; 402 wc.lpfnWndProc = &wndProc; 403 wc.cbClsExtra = 0; 404 wc.cbWndExtra = 0; 405 wc.hInstance = hInstance; 406 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 407 wc.hCursor = LoadCursor(NULL, IDC_ARROW); 408 wc.hbrBackground = cast(HBRUSH) GetStockObject(BLACK_BRUSH); 409 wc.lpszMenuName = NULL; 410 wc.lpszClassName = "WindowClass"w.ptr; 411 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 412 413 if (!RegisterClassEx(&wc)) { 414 assert(0); 415 } 416 } 417 418 wchar* toWStr(string s) { 419 int length = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, s.ptr, cast(int) s.length, NULL, 0,); 420 wchar* result = cast(wchar*) calloc(length, wchar.sizeof); 421 MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, s.ptr, cast(int) s.length, result, length,); 422 return result; 423 } 424 425 package: 426 427 Framebuffer win32OpenWindow(WindowOptions options) { 428 WindowData* data = cast(WindowData*) malloc(WindowData.sizeof); 429 data.getWidth = cast(int function(void*) nothrow)&getWidth; 430 data.getHeight = cast(int function(void*) nothrow)&getHeight; 431 data.close = cast(void function(void*) nothrow)&close; 432 data.update = cast(void function(void*, uint[]) nothrow)&update; 433 data.yield = cast(void function(void*) nothrow)&yield; 434 data.evqEmpty = cast(bool function(void*) nothrow)&evqEmpty; 435 data.evqFront = cast(Event function(void*) nothrow)&evqFront; 436 data.evqPopFront = cast(void function(void*) nothrow)&evqPopFront; 437 438 win32Init(); 439 440 wchar* wtitle = options.title.toWStr; 441 442 int style = WS_OVERLAPPEDWINDOW; 443 if (!options.resizable) { 444 style ^= WS_THICKFRAME | WS_MAXIMIZEBOX; 445 } 446 447 RECT r; 448 r.left = 0; 449 r.top = 0; 450 r.right = options.initialWidth; 451 r.bottom = options.initialHeight; 452 AdjustWindowRect(&r, style, FALSE); 453 454 // dfmt off 455 HWND hwnd = CreateWindowW( 456 "WindowClass"w.ptr, 457 wtitle, 458 style, 459 0, 0, 460 r.right - r.left, r.bottom - r.top, 461 GetDesktopWindow(), 462 NULL, hInstance, NULL, 463 ); 464 // dfmt on 465 466 free(wtitle); 467 468 Win32Data* self = cast(Win32Data*) calloc(Win32Data.sizeof, 1); 469 self.hwnd = hwnd; 470 self.rWidth = options.initialWidth; 471 self.rHeight = options.initialHeight; 472 self.mouseOutside = true; 473 self.cursorTracked = false; 474 self.buffer = null; 475 self.evqHead = null; 476 self.evqTail = null; 477 478 SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR) self); 479 480 data.udata = self; 481 482 ShowWindow(hwnd, SW_SHOW); 483 484 return Framebuffer(FramebufferType.Window, data); 485 }