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