1 /// Win32 Window interface for Gfx-d 2 module gfx.window.win32; 3 4 version(Windows): 5 6 import core.sys.windows.windows; 7 import gfx.graal.presentation; 8 import gfx.window; 9 10 import std.exception; 11 import std.experimental.logger; 12 13 class Win32Display : Display 14 { 15 import gfx.core.rc : atomicRcCode; 16 import gfx.graal : Backend, Instance; 17 18 mixin(atomicRcCode); 19 20 private Win32Window[HWND] _windows; 21 private Window[] _iwindows; 22 private Instance _instance; 23 24 this(in Backend[] loadOrder) { 25 assert(!g_dpy); 26 g_dpy = this; 27 28 registerWindowClass(); 29 import std.experimental.logger : info, trace, warningf; 30 assert(!_instance); 31 32 foreach (b; loadOrder) { 33 final switch (b) { 34 case Backend.vulkan: 35 try { 36 trace("Attempting to instantiate Vulkan"); 37 import gfx.vulkan : createVulkanInstance, vulkanInit; 38 vulkanInit(); 39 _instance = createVulkanInstance(); 40 info("Creating a Vulkan instance"); 41 } 42 catch (Exception ex) { 43 warningf("Vulkan is not available. %s", ex.msg); 44 } 45 break; 46 case Backend.gl3: 47 try { 48 trace("Attempting to instantiate OpenGL"); 49 import gfx.core.rc : makeRc; 50 import gfx.gl3 : GlInstance; 51 import gfx.gl3.context : GlAttribs; 52 import gfx.window.win32.context : Win32GlContext; 53 auto ctx = makeRc!Win32GlContext(GlAttribs.init); 54 trace("Creating an OpenGL instance"); 55 _instance = new GlInstance(ctx); 56 } 57 catch (Exception ex) { 58 warningf("OpenGL is not available. %s", ex.msg); 59 } 60 break; 61 } 62 if (_instance) break; 63 } 64 65 if (!_instance) { 66 throw new Exception("Could not instantiate a backend"); 67 } 68 } 69 70 override void dispose() { 71 auto openWindows = _iwindows; 72 foreach (w; openWindows) { // Window.close modifies _iwindows 73 w.close(); 74 } 75 assert(!_iwindows.length); 76 } 77 78 override @property Instance instance() { 79 return _instance; 80 } 81 override @property Window[] windows() { 82 return _iwindows; 83 } 84 override Window createWindow() { 85 return new Win32Window(this); 86 } 87 override void pollAndDispatch() { 88 MSG msg; 89 while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE)) { 90 TranslateMessage(&msg); 91 DispatchMessageW(&msg); 92 } 93 } 94 95 96 private void registerWindow(HWND hWnd, Win32Window w) 97 { 98 _windows[hWnd] = w; 99 _iwindows ~= w; 100 } 101 102 private void unregisterWindow(HWND hWnd) 103 { 104 import std.algorithm : remove; 105 _windows.remove(hWnd); 106 _iwindows = _iwindows.remove!((Window w) { 107 return (cast(Win32Window)w).hWnd == hWnd; 108 }); 109 } 110 111 private Win32Window findWithHWnd(HWND hWnd) 112 { 113 Win32Window *w = (hWnd in _windows); 114 return w ? *w : null; 115 } 116 117 private bool wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, out LRESULT res) 118 { 119 res = 0; 120 121 Win32Window wnd = findWithHWnd(hWnd); 122 if (!wnd) { 123 return false; 124 } 125 126 switch(msg) 127 { 128 case WM_CLOSE: 129 if (wnd.closeHandler) { 130 wnd._closeFlag = wnd.closeHandler(); 131 } 132 else { 133 wnd._closeFlag = true; 134 } 135 return true; 136 case WM_LBUTTONDOWN: 137 case WM_LBUTTONUP: 138 case WM_MBUTTONDOWN: 139 case WM_MBUTTONUP: 140 case WM_RBUTTONDOWN: 141 case WM_RBUTTONUP: 142 case WM_MOUSEMOVE: 143 case WM_MOUSELEAVE: 144 return wnd.handleMouse(msg, wParam, lParam); 145 case WM_KEYDOWN: 146 case WM_KEYUP: 147 case WM_CHAR: 148 return wnd.handleKey(msg, wParam, lParam); 149 default: 150 return false; 151 } 152 } 153 154 } 155 156 class Win32Window : Window 157 { 158 import gfx.core.rc : Rc; 159 160 private Win32Display dpy; 161 private HWND hWnd; 162 private Rc!Surface gfxSurface; 163 164 private MouseHandler moveHandler; 165 private MouseHandler onHandler; 166 private MouseHandler offHandler; 167 private KeyHandler keyOnHandler; 168 private KeyHandler keyOffHandler; 169 private CloseHandler closeHandler; 170 private bool mouseOut; 171 private bool _closeFlag; 172 173 this(Win32Display dpy) { 174 this.dpy = dpy; 175 import std.utf : toUTF16z; 176 177 HINSTANCE hInstance = GetModuleHandle(null); 178 179 hWnd = enforce( 180 CreateWindowEx( 181 WS_EX_CLIENTEDGE, // | WS_EX_LAYERED, 182 wndClassName.toUTF16z, 183 "null", 184 WS_OVERLAPPEDWINDOW, 185 CW_USEDEFAULT, 186 CW_USEDEFAULT, 187 CW_USEDEFAULT, 188 CW_USEDEFAULT, 189 null, null, hInstance, null 190 ), 191 "could not create win32 window" 192 ); 193 194 // What follow is a non-portable way to have alpha value of framebuffer used in desktop composition 195 // (by default composition makes window completely opaque) 196 // only works on Windows 7 and therefore disabled 197 198 // DWM_BLURBEHIND bb; 199 // bb.dwFlags = DWM_BB_ENABLE; 200 // bb.fEnable = TRUE; 201 // bb.hRgnBlur = NULL; 202 // DwmEnableBlurBehindWindow(hWnd, &bb); 203 // MARGINS m = { -1 }; 204 // DwmExtendFrameIntoClientArea(hWnd, &m); 205 206 import gfx.graal : Backend; 207 final switch (dpy.instance.backend) { 208 case Backend.vulkan: 209 import gfx.vulkan.wsi : createVulkanWin32Surface; 210 gfxSurface = createVulkanWin32Surface(dpy.instance, GetModuleHandle(null), hWnd); 211 break; 212 213 case Backend.gl3: 214 import gfx.gl3 : GlInstance; 215 import gfx.gl3.swapchain : GlSurface; 216 import gfx.window.win32.context : Win32GlContext; 217 218 gfxSurface = new GlSurface(cast(size_t)hWnd); 219 auto glInst = cast(GlInstance)dpy.instance; 220 auto ctx = cast(Win32GlContext)glInst.ctx; 221 ctx.setPixelFormat(hWnd); 222 ctx.makeCurrent(cast(size_t)hWnd); 223 break; 224 } 225 226 dpy.registerWindow(hWnd, this); 227 } 228 229 override void show(uint width, uint height) { 230 RECT r; 231 r.right = width; 232 r.bottom = height; 233 AdjustWindowRectEx(&r, WS_BORDER | WS_CAPTION, FALSE, 0); 234 SetWindowPos(hWnd, HWND_TOP, 0, 0, r.right - r.left, r.bottom - r.top, SWP_SHOWWINDOW | SWP_NOMOVE); 235 } 236 237 override void close() { 238 DestroyWindow(hWnd); 239 gfxSurface.unload(); 240 dpy.unregisterWindow(hWnd); 241 } 242 243 override @property bool closeFlag() const { 244 return _closeFlag; 245 } 246 override @property void closeFlag(in bool flag) { 247 _closeFlag = flag; 248 } 249 250 override @property void onMouseMove(MouseHandler handler) { 251 moveHandler = handler; 252 } 253 override @property void onMouseOn(MouseHandler handler) { 254 onHandler = handler; 255 } 256 override @property void onMouseOff(MouseHandler handler) { 257 offHandler = handler; 258 } 259 override @property void onKeyOn(KeyHandler handler) { 260 keyOnHandler = handler; 261 } 262 override @property void onKeyOff(KeyHandler handler) { 263 keyOffHandler = handler; 264 } 265 override @property void onClose(CloseHandler handler) { 266 closeHandler = handler; 267 } 268 269 override @property Surface surface() { 270 return gfxSurface.obj; 271 } 272 273 private bool handleMouse(UINT msg, WPARAM wParam, LPARAM lParam) { 274 const x = GET_X_LPARAM(lParam); 275 const y = GET_Y_LPARAM(lParam); 276 277 switch (msg) { 278 case WM_LBUTTONDOWN: 279 case WM_MBUTTONDOWN: 280 case WM_RBUTTONDOWN: 281 if (onHandler) { 282 onHandler(x, y); 283 return true; 284 } 285 break; 286 case WM_LBUTTONUP: 287 case WM_MBUTTONUP: 288 case WM_RBUTTONUP: 289 if (offHandler) { 290 offHandler(x, y); 291 return true; 292 } 293 break; 294 case WM_MOUSEMOVE: 295 if (mouseOut) { 296 mouseOut = false; 297 // mouse was out, deliver enter event (TODO) 298 // and register for leave event 299 TRACKMOUSEEVENT tm; 300 tm.cbSize = TRACKMOUSEEVENT.sizeof; 301 tm.dwFlags = TME_LEAVE; 302 tm.hwndTrack = hWnd; 303 tm.dwHoverTime = 0; 304 TrackMouseEvent(&tm); 305 } 306 if (moveHandler) { 307 moveHandler(x, y); 308 return true; 309 } 310 break; 311 case WM_MOUSELEAVE: 312 mouseOut = true; 313 // TODO: deliver leave event 314 break; 315 default: 316 break; 317 } 318 319 return false; 320 } 321 322 private bool handleKey(UINT msg, WPARAM wParam, LPARAM lParam) { 323 KeyHandler handler; 324 if (msg == WM_KEYDOWN) { 325 handler = keyOnHandler; 326 } 327 else if (msg == WM_KEYUP) { 328 handler = keyOffHandler; 329 } 330 331 if (handler) { 332 handler(cast(int)lParam); // TODO: sym, text, scancode and repeat from dgt 333 return true; 334 } 335 else { 336 return false; 337 } 338 } 339 } 340 341 private __gshared Win32Display g_dpy; 342 package immutable wstring wndClassName = "GfxDWin32WindowClass"w; 343 344 345 package void registerWindowClass() 346 { 347 import std.utf : toUTF16z; 348 349 static bool registered; 350 if (registered) return; 351 registered = true; 352 353 WNDCLASSEX wc; 354 wc.cbSize = WNDCLASSEX.sizeof; 355 wc.style = CS_OWNDC; 356 wc.lpfnWndProc = &win32WndProc; 357 wc.cbClsExtra = 0; 358 wc.cbWndExtra = 0; 359 wc.hInstance = GetModuleHandle(null); 360 wc.hIcon = LoadIcon(null, IDI_APPLICATION); 361 wc.hCursor = LoadCursor(null, IDC_ARROW); 362 wc.hbrBackground = null; 363 wc.lpszMenuName = null; 364 wc.lpszClassName = wndClassName.toUTF16z; 365 wc.hIconSm = LoadIcon(null, IDI_APPLICATION); 366 367 enforce(RegisterClassExW(&wc), "could not register win32 window class"); 368 } 369 370 package int GET_X_LPARAM(in LPARAM lp) pure 371 { 372 return cast(int)(lp & 0x0000ffff); 373 } 374 375 package int GET_Y_LPARAM(in LPARAM lp) pure 376 { 377 return cast(int)((lp & 0xffff0000) >> 16); 378 } 379 380 extern(Windows) nothrow 381 private LRESULT win32WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 382 { 383 import std.exception : collectExceptionMsg; 384 LRESULT res; 385 try 386 { 387 if (!g_dpy || !g_dpy.wndProc(hwnd, msg, wParam, lParam, res)) 388 { 389 res = DefWindowProc(hwnd, msg, wParam, lParam); 390 } 391 } 392 catch(Exception ex) 393 { 394 import std.experimental.logger : errorf; 395 try { errorf("Win32 Proc exception: %s", ex.msg); } 396 catch(Exception) {} 397 } 398 return res; 399 } 400 401 // a few missing bindings 402 403 private: 404 405 // see comment in Win32Window ctor 406 407 // struct DWM_BLURBEHIND { 408 // DWORD dwFlags; 409 // BOOL fEnable; 410 // HRGN hRgnBlur; 411 // BOOL fTransitionOnMaximized; 412 // } 413 414 // struct MARGINS { 415 // int left; int right; int top; int bottom; 416 // } 417 418 // enum DWM_BB_ENABLE = 0x00000001; 419 420 // extern(Windows) HRESULT DwmEnableBlurBehindWindow( 421 // HWND hWnd, 422 // const(DWM_BLURBEHIND)* pBlurBehind 423 // ); 424 // extern(Windows) HRESULT DwmExtendFrameIntoClientArea( 425 // HWND hWnd, 426 // const(MARGINS)* pMarInset 427 // );