Win32Window.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. 
  2. /*
  3. Link to these dependencies for MS Windows:
  4. gdi32
  5. user32
  6. kernel32
  7. comctl32
  8. */
  9. #include <tchar.h>
  10. #include <windows.h>
  11. #include <windowsx.h>
  12. #include "../DFPSR/api/imageAPI.h"
  13. #include "../DFPSR/api/drawAPI.h"
  14. #include "../DFPSR/gui/BackendWindow.h"
  15. #include <mutex>
  16. #include <future>
  17. static std::mutex windowLock;
  18. // Enable this macro to disable multi-threading
  19. //#define DISABLE_MULTI_THREADING
  20. static const int bufferCount = 2;
  21. class Win32Window : public dsr::BackendWindow {
  22. public:
  23. // The native windows handle
  24. HWND hwnd;
  25. // The cursors
  26. HCURSOR noCursor, defaultCursor;
  27. // Keep track of when the cursor is inside of the window,
  28. // so that we can show it again when leaving the window
  29. bool cursorIsInside = false;
  30. // Double buffering to allow drawing to a canvas while displaying the previous one
  31. dsr::AlignedImageRgbaU8 canvas[bufferCount];
  32. int drawIndex = 0 % bufferCount;
  33. int showIndex = 1 % bufferCount;
  34. bool firstFrame = true;
  35. #ifndef DISABLE_MULTI_THREADING
  36. // The background worker for displaying the result using a separate thread protected by a mutex
  37. std::future<void> displayFuture;
  38. #endif
  39. // Remembers the dimensions of the window from creation and resize events
  40. // This allow requesting the size of the window at any time
  41. int windowWidth = 0, windowHeight = 0;
  42. private:
  43. // Called before the application fetches events from the input queue
  44. // Closing the window, moving the mouse, pressing a key, et cetera
  45. void prefetchEvents() override;
  46. // Called to change the cursor visibility and returning true on success
  47. bool setCursorVisibility(bool visible) override;
  48. // Place the cursor within the window
  49. void setCursorPosition(int x, int y) override;
  50. private:
  51. // Helper methods specific to calling XLib
  52. void updateTitle_locked();
  53. private:
  54. // Canvas methods
  55. dsr::AlignedImageRgbaU8 getCanvas() override { return this->canvas[this->drawIndex]; }
  56. void resizeCanvas(int width, int height) override;
  57. // Window methods
  58. void setTitle(const dsr::String &newTitle) override {
  59. this->title = newTitle;
  60. this->updateTitle_locked();
  61. }
  62. void removeOldWindow_locked();
  63. void createWindowed_locked(const dsr::String& title, int width, int height);
  64. void createFullscreen_locked();
  65. void prepareWindow_locked();
  66. int windowState = 0; // 0=none, 1=windowed, 2=fullscreen
  67. public:
  68. // Constructors
  69. Win32Window(const Win32Window&) = delete; // Non-copyable because of pointer aliasing.
  70. Win32Window(const dsr::String& title, int width, int height);
  71. int getWidth() const override { return this->windowWidth; };
  72. int getHeight() const override { return this->windowHeight; };
  73. // Destructor
  74. ~Win32Window();
  75. // Interface
  76. void setFullScreen(bool enabled) override;
  77. bool isFullScreen() override { return this->windowState == 2; }
  78. void redraw(HWND& hwnd, bool locked); // HWND is passed by argument because drawing might be called before the constructor has assigned it to this->hwnd
  79. void showCanvas() override;
  80. };
  81. static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  82. static TCHAR windowClassName[] = _T("DfpsrWindowApplication");
  83. void Win32Window::updateTitle_locked() {
  84. windowLock.lock();
  85. if (!SetWindowTextA(this->hwnd, this->title.toStdString().c_str())) {
  86. dsr::printText("Warning! Could not assign the window title ", dsr::string_mangleQuote(this->title), ".\n");
  87. }
  88. windowLock.unlock();
  89. }
  90. // The method can be seen as locked, but it overrides a virtual method that is independent of threading.
  91. void Win32Window::setCursorPosition(int x, int y) {
  92. windowLock.lock();
  93. POINT point; point.x = x; point.y = y;
  94. ClientToScreen(this->hwnd, &point);
  95. SetCursorPos(point.x, point.y);
  96. windowLock.unlock();
  97. }
  98. bool Win32Window::setCursorVisibility(bool visible) {
  99. // Cursor visibility is deferred, so no need to lock access here.
  100. // Remember the cursor's visibility for anyone asking
  101. this->visibleCursor = visible;
  102. // Indicate that the feature is implemented
  103. return true;
  104. }
  105. void Win32Window::setFullScreen(bool enabled) {
  106. if (this->windowState == 1 && enabled) {
  107. // Clean up any previous window
  108. removeOldWindow_locked();
  109. // Create the new window and graphics context
  110. this->createFullscreen_locked();
  111. } else if (this->windowState == 2 && !enabled) {
  112. // Clean up any previous window
  113. removeOldWindow_locked();
  114. // Create the new window and graphics context
  115. this->createWindowed_locked(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode
  116. }
  117. }
  118. void Win32Window::removeOldWindow_locked() {
  119. windowLock.lock();
  120. if (this->windowState != 0) {
  121. DestroyWindow(this->hwnd);
  122. }
  123. this->windowState = 0;
  124. windowLock.unlock();
  125. }
  126. void Win32Window::prepareWindow_locked() {
  127. windowLock.lock();
  128. // Reallocate the canvas
  129. this->resizeCanvas(this->windowWidth, this->windowHeight);
  130. // Show the window
  131. ShowWindow(this->hwnd, SW_NORMAL);
  132. // Repaint
  133. UpdateWindow(this->hwnd);
  134. windowLock.unlock();
  135. }
  136. static bool registered = false;
  137. static void registerIfNeeded() {
  138. if (!registered) {
  139. // The Window structure
  140. WNDCLASSEX wincl;
  141. memset(&wincl, 0, sizeof(WNDCLASSEX));
  142. wincl.hInstance = NULL;
  143. wincl.lpszClassName = windowClassName;
  144. wincl.lpfnWndProc = WindowProcedure;
  145. wincl.style = 0;
  146. wincl.cbSize = sizeof(WNDCLASSEX);
  147. // Use default icon and mouse-pointer
  148. wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  149. wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  150. wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
  151. wincl.lpszMenuName = NULL;
  152. wincl.cbClsExtra = 0;
  153. wincl.cbWndExtra = sizeof(LPVOID);
  154. // Use Windows's default color as the background of the window
  155. wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND; // TODO: Make black
  156. // Register the window class, and if it fails quit the program
  157. if (!RegisterClassEx (&wincl)) {
  158. dsr::throwError("Call to RegisterClassEx failed!\n");
  159. }
  160. registered = true;
  161. }
  162. }
  163. void Win32Window::createWindowed_locked(const dsr::String& title, int width, int height) {
  164. // Request to resize the canvas and interface according to the new window
  165. this->windowWidth = width;
  166. this->windowHeight = height;
  167. this->receivedWindowResize(width, height);
  168. windowLock.lock();
  169. // Register the Window class during first creation
  170. registerIfNeeded();
  171. // The class is registered, let's create the program
  172. this->hwnd = CreateWindowEx(
  173. 0, // dwExStyle
  174. windowClassName, // lpClassName
  175. _T(""), // lpWindowName
  176. WS_OVERLAPPEDWINDOW, // dwStyle
  177. CW_USEDEFAULT, // x
  178. CW_USEDEFAULT, // y
  179. width, // nWidth
  180. height, // nHeight
  181. HWND_DESKTOP, // hWndParent
  182. NULL, // hMenu
  183. NULL, // hInstance
  184. (LPVOID)this // lpParam
  185. );
  186. windowLock.unlock();
  187. this->updateTitle_locked();
  188. this->windowState = 1;
  189. this->prepareWindow_locked();
  190. }
  191. void Win32Window::createFullscreen_locked() {
  192. windowLock.lock();
  193. int screenWidth = GetSystemMetrics(SM_CXSCREEN);
  194. int screenHeight = GetSystemMetrics(SM_CYSCREEN);
  195. // Request to resize the canvas and interface according to the new window
  196. this->windowWidth = screenWidth;
  197. this->windowHeight = screenHeight;
  198. this->receivedWindowResize(screenWidth, screenHeight);
  199. // Register the Window class during first creation
  200. registerIfNeeded();
  201. // The class is registered, let's create the program
  202. this->hwnd = CreateWindowEx(
  203. 0, // dwExStyle
  204. windowClassName, // lpClassName
  205. _T(""), // lpWindowName
  206. WS_POPUP | WS_VISIBLE, // dwStyle
  207. 0, // x
  208. 0, // y
  209. screenWidth, // nWidth
  210. screenHeight, // nHeight
  211. HWND_DESKTOP, // hWndParent
  212. NULL, // hMenu
  213. NULL, // hInstance
  214. (LPVOID)this // lpParam
  215. );
  216. windowLock.unlock();
  217. this->windowState = 2;
  218. this->prepareWindow_locked();
  219. }
  220. Win32Window::Win32Window(const dsr::String& title, int width, int height) {
  221. bool fullScreen = false;
  222. if (width < 1 || height < 1) {
  223. fullScreen = true;
  224. }
  225. // Remember the title
  226. this->title = title;
  227. windowLock.lock();
  228. // Get the default cursor
  229. this->defaultCursor = LoadCursor(0, IDC_ARROW);
  230. // Create an invisible cursor using masks padded to 32 bits for safety
  231. uint32_t cursorAndMask = 0b11111111;
  232. uint32_t cursorXorMask = 0b00000000;
  233. this->noCursor = CreateCursor(NULL, 0, 0, 1, 1, (const void*)&cursorAndMask, (const void*)&cursorXorMask);
  234. windowLock.unlock();
  235. // Create a window
  236. if (fullScreen) {
  237. this->createFullscreen_locked();
  238. } else {
  239. this->createWindowed_locked(title, width, height);
  240. }
  241. }
  242. static dsr::DsrKey getDsrKey(WPARAM keyCode) {
  243. dsr::DsrKey result = dsr::DsrKey_Unhandled;
  244. if (keyCode == VK_ESCAPE) {
  245. result = dsr::DsrKey_Escape;
  246. } else if (keyCode == VK_F1) {
  247. result = dsr::DsrKey_F1;
  248. } else if (keyCode == VK_F2) {
  249. result = dsr::DsrKey_F2;
  250. } else if (keyCode == VK_F3) {
  251. result = dsr::DsrKey_F3;
  252. } else if (keyCode == VK_F4) {
  253. result = dsr::DsrKey_F4;
  254. } else if (keyCode == VK_F5) {
  255. result = dsr::DsrKey_F5;
  256. } else if (keyCode == VK_F6) {
  257. result = dsr::DsrKey_F6;
  258. } else if (keyCode == VK_F7) {
  259. result = dsr::DsrKey_F7;
  260. } else if (keyCode == VK_F8) {
  261. result = dsr::DsrKey_F8;
  262. } else if (keyCode == VK_F9) {
  263. result = dsr::DsrKey_F9;
  264. } else if (keyCode == VK_F10) {
  265. result = dsr::DsrKey_F10;
  266. } else if (keyCode == VK_F11) {
  267. result = dsr::DsrKey_F11;
  268. } else if (keyCode == VK_F12) {
  269. result = dsr::DsrKey_F12;
  270. } else if (keyCode == VK_PAUSE) {
  271. result = dsr::DsrKey_Pause;
  272. } else if (keyCode == VK_SPACE) {
  273. result = dsr::DsrKey_Space;
  274. } else if (keyCode == VK_TAB) {
  275. result = dsr::DsrKey_Tab;
  276. } else if (keyCode == VK_RETURN) {
  277. result = dsr::DsrKey_Return;
  278. } else if (keyCode == VK_BACK) {
  279. result = dsr::DsrKey_BackSpace;
  280. } else if (keyCode == VK_LSHIFT || keyCode == VK_SHIFT) {
  281. // If the keyboard does not tell if the shift is left or right, treat it like a left shift.
  282. result = dsr::DsrKey_LeftShift;
  283. } else if (keyCode == VK_RSHIFT) {
  284. result = dsr::DsrKey_RightShift;
  285. } else if (keyCode == VK_LCONTROL || keyCode == VK_CONTROL) {
  286. // If the keyboard does not tell if control is left or right, treat it like left control.
  287. result = dsr::DsrKey_LeftControl;
  288. } else if (keyCode == VK_RCONTROL) {
  289. result = dsr::DsrKey_RightControl;
  290. } else if (keyCode == VK_LMENU || keyCode == VK_MENU) {
  291. // If the keyboard does not tell if alt is left or right, treat it like left alt.
  292. result = dsr::DsrKey_LeftAlt;
  293. } else if (keyCode == VK_RMENU) {
  294. result = dsr::DsrKey_RightAlt;
  295. } else if (keyCode == VK_DELETE) {
  296. result = dsr::DsrKey_Delete;
  297. } else if (keyCode == VK_LEFT) {
  298. result = dsr::DsrKey_LeftArrow;
  299. } else if (keyCode == VK_RIGHT) {
  300. result = dsr::DsrKey_RightArrow;
  301. } else if (keyCode == VK_UP) {
  302. result = dsr::DsrKey_UpArrow;
  303. } else if (keyCode == VK_DOWN) {
  304. result = dsr::DsrKey_DownArrow;
  305. } else if (keyCode == 0x30) {
  306. result = dsr::DsrKey_0;
  307. } else if (keyCode == 0x31) {
  308. result = dsr::DsrKey_1;
  309. } else if (keyCode == 0x32) {
  310. result = dsr::DsrKey_2;
  311. } else if (keyCode == 0x33) {
  312. result = dsr::DsrKey_3;
  313. } else if (keyCode == 0x34) {
  314. result = dsr::DsrKey_4;
  315. } else if (keyCode == 0x35) {
  316. result = dsr::DsrKey_5;
  317. } else if (keyCode == 0x36) {
  318. result = dsr::DsrKey_6;
  319. } else if (keyCode == 0x37) {
  320. result = dsr::DsrKey_7;
  321. } else if (keyCode == 0x38) {
  322. result = dsr::DsrKey_8;
  323. } else if (keyCode == 0x39) {
  324. result = dsr::DsrKey_9;
  325. } else if (keyCode == 0x41) {
  326. result = dsr::DsrKey_A;
  327. } else if (keyCode == 0x42) {
  328. result = dsr::DsrKey_B;
  329. } else if (keyCode == 0x43) {
  330. result = dsr::DsrKey_C;
  331. } else if (keyCode == 0x44) {
  332. result = dsr::DsrKey_D;
  333. } else if (keyCode == 0x45) {
  334. result = dsr::DsrKey_E;
  335. } else if (keyCode == 0x46) {
  336. result = dsr::DsrKey_F;
  337. } else if (keyCode == 0x47) {
  338. result = dsr::DsrKey_G;
  339. } else if (keyCode == 0x48) {
  340. result = dsr::DsrKey_H;
  341. } else if (keyCode == 0x49) {
  342. result = dsr::DsrKey_I;
  343. } else if (keyCode == 0x4A) {
  344. result = dsr::DsrKey_J;
  345. } else if (keyCode == 0x4B) {
  346. result = dsr::DsrKey_K;
  347. } else if (keyCode == 0x4C) {
  348. result = dsr::DsrKey_L;
  349. } else if (keyCode == 0x4D) {
  350. result = dsr::DsrKey_M;
  351. } else if (keyCode == 0x4E) {
  352. result = dsr::DsrKey_N;
  353. } else if (keyCode == 0x4F) {
  354. result = dsr::DsrKey_O;
  355. } else if (keyCode == 0x50) {
  356. result = dsr::DsrKey_P;
  357. } else if (keyCode == 0x51) {
  358. result = dsr::DsrKey_Q;
  359. } else if (keyCode == 0x52) {
  360. result = dsr::DsrKey_R;
  361. } else if (keyCode == 0x53) {
  362. result = dsr::DsrKey_S;
  363. } else if (keyCode == 0x54) {
  364. result = dsr::DsrKey_T;
  365. } else if (keyCode == 0x55) {
  366. result = dsr::DsrKey_U;
  367. } else if (keyCode == 0x56) {
  368. result = dsr::DsrKey_V;
  369. } else if (keyCode == 0x57) {
  370. result = dsr::DsrKey_W;
  371. } else if (keyCode == 0x58) {
  372. result = dsr::DsrKey_X;
  373. } else if (keyCode == 0x59) {
  374. result = dsr::DsrKey_Y;
  375. } else if (keyCode == 0x5A) {
  376. result = dsr::DsrKey_Z;
  377. } else if (keyCode == 0x2D) {
  378. result = dsr::DsrKey_Insert;
  379. } else if (keyCode == 0x24) {
  380. result = dsr::DsrKey_Home;
  381. } else if (keyCode == 0x23) {
  382. result = dsr::DsrKey_End;
  383. } else if (keyCode == 0x21) {
  384. result = dsr::DsrKey_PageUp;
  385. } else if (keyCode == 0x22) {
  386. result = dsr::DsrKey_PageDown;
  387. }
  388. return result;
  389. }
  390. // Called from DispatchMessage via prefetchEvents
  391. static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
  392. // Get the Win32Window owning the given hwnd
  393. Win32Window *parent = nullptr;
  394. if (message == WM_CREATE) {
  395. // Cast the pointer argument into CREATESTRUCT and get the lParam given to the window on creation
  396. CREATESTRUCT *createStruct = (CREATESTRUCT*)lParam;
  397. parent = (Win32Window*)createStruct->lpCreateParams;
  398. if (parent == nullptr) {
  399. dsr::throwError("Null handle retreived from lParam (", (intptr_t)parent, ") in WM_CREATE message.\n");
  400. }
  401. SetWindowLongPtr(hwnd, GWLP_USERDATA, (intptr_t)parent);
  402. } else {
  403. // Get the parent
  404. parent = (Win32Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  405. if (parent == nullptr) {
  406. // Don't try to handle global events unrelated to any window
  407. return DefWindowProc(hwnd, message, wParam, lParam);
  408. }
  409. }
  410. // Check that we're using the correct window instance (This might not be the case while toggling full-screen)
  411. // Handle the message
  412. int result = 0;
  413. switch (message) {
  414. case WM_QUIT:
  415. PostQuitMessage(wParam);
  416. break;
  417. case WM_CLOSE:
  418. parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Close, parent->windowWidth, parent->windowHeight));
  419. DestroyWindow(hwnd);
  420. break;
  421. case WM_LBUTTONDOWN:
  422. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  423. break;
  424. case WM_LBUTTONUP:
  425. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  426. break;
  427. case WM_RBUTTONDOWN:
  428. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  429. break;
  430. case WM_RBUTTONUP:
  431. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  432. break;
  433. case WM_MBUTTONDOWN:
  434. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  435. break;
  436. case WM_MBUTTONUP:
  437. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  438. break;
  439. case WM_MOUSEMOVE:
  440. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  441. break;
  442. case WM_SETCURSOR:
  443. if (LOWORD(lParam) == HTCLIENT) {
  444. if (parent->visibleCursor) {
  445. SetCursor(parent->defaultCursor);
  446. } else {
  447. SetCursor(parent->noCursor);
  448. }
  449. }
  450. break;
  451. case WM_MOUSEWHEEL:
  452. {
  453. int delta = GET_WHEEL_DELTA_WPARAM(wParam);
  454. if (delta > 0) {
  455. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollUp, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  456. } else if (delta < 0) {
  457. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollDown, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  458. }
  459. }
  460. break;
  461. case WM_KEYDOWN: case WM_KEYUP:
  462. {
  463. dsr::DsrChar character;
  464. if (IsWindowUnicode(hwnd)) {
  465. dsr::CharacterEncoding encoding = dsr::CharacterEncoding::BOM_UTF16LE;
  466. dsr::String characterString = dsr::string_dangerous_decodeFromData((const void*)&wParam, encoding);
  467. character = characterString[0]; // Convert from UTF-16 surrogate to UTF-32 Unicode character
  468. } else {
  469. character = wParam; // Raw ansi character
  470. }
  471. dsr::DsrKey dsrKey = getDsrKey(wParam); // Portable key-code
  472. bool previouslyPressed = lParam & (1 << 30);
  473. if (message == WM_KEYDOWN) {
  474. // If not repeated
  475. if (!previouslyPressed) {
  476. // Physical key down
  477. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyDown, character, dsrKey));
  478. }
  479. // Press typing with repeat
  480. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
  481. } else { // message == WM_KEYUP
  482. // Physical key up
  483. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyUp, character, dsrKey));
  484. }
  485. }
  486. break;
  487. case WM_PAINT:
  488. parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Redraw, parent->windowWidth, parent->windowHeight));
  489. // BeginPaint and EndPaint must be called with the given hwnd to prevent having the redraw message sent again
  490. parent->redraw(hwnd, false);
  491. // Passing on the event to prevent flooding with more messages. This is only a temporary solution.
  492. // TODO: Avoid overwriting the result with any background color.
  493. //result = DefWindowProc(hwnd, message, wParam, lParam);
  494. break;
  495. case WM_SIZE:
  496. // If there's no size during minimization, don't try to resize the canvas
  497. if (wParam != SIZE_MINIMIZED) {
  498. int width = LOWORD(lParam);
  499. int height = HIWORD(lParam);
  500. parent->windowWidth = width;
  501. parent->windowHeight = height;
  502. parent->receivedWindowResize(width, height);
  503. }
  504. // Resize the window as requested
  505. result = DefWindowProc(hwnd, message, wParam, lParam);
  506. break;
  507. default:
  508. result = DefWindowProc(hwnd, message, wParam, lParam);
  509. }
  510. return result;
  511. }
  512. void Win32Window::prefetchEvents() {
  513. // Only prefetch new events if nothing else is locking.
  514. if (windowLock.try_lock()) {
  515. MSG messages;
  516. if (IsWindowUnicode(this->hwnd)) {
  517. while (PeekMessageW(&messages, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&messages); DispatchMessage(&messages); }
  518. } else {
  519. while (PeekMessage(&messages, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&messages); DispatchMessage(&messages); }
  520. }
  521. windowLock.unlock();
  522. }
  523. }
  524. // Locked because it overrides
  525. void Win32Window::resizeCanvas(int width, int height) {
  526. // Create a new canvas
  527. // Even thou Windows is using RGBA pack order for the window, the bitmap format used for drawing is using BGRA order
  528. for (int bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
  529. this->canvas[bufferIndex] = dsr::image_create_RgbaU8_native(width, height, dsr::PackOrderIndex::BGRA);
  530. }
  531. }
  532. Win32Window::~Win32Window() {
  533. #ifndef DISABLE_MULTI_THREADING
  534. // Wait for the last update of the window to finish so that it doesn't try to operate on freed resources
  535. if (this->displayFuture.valid()) {
  536. this->displayFuture.wait();
  537. }
  538. #endif
  539. windowLock.lock();
  540. // Destroy the invisible cursor
  541. DestroyCursor(this->noCursor);
  542. // Destroy the native window
  543. DestroyWindow(this->hwnd);
  544. windowLock.unlock();
  545. }
  546. // The lock argument must be true if not already within a lock and false if inside of a lock.
  547. void Win32Window::redraw(HWND& hwnd, bool lock) {
  548. #ifndef DISABLE_MULTI_THREADING
  549. // Wait for the previous update to finish, to avoid flooding the system with new threads waiting for windowLock
  550. if (this->displayFuture.valid()) {
  551. this->displayFuture.wait();
  552. }
  553. #endif
  554. if (lock) {
  555. windowLock.lock();
  556. }
  557. this->drawIndex = (this->drawIndex + 1) % bufferCount;
  558. this->showIndex = (this->showIndex + 1) % bufferCount;
  559. this->prefetchEvents();
  560. int displayIndex = this->showIndex;
  561. std::function<void()> task = [this, displayIndex, lock]() {
  562. // Let the source bitmap use a padded width to safely handle the stride
  563. // Windows requires 8-byte alignment, but the image format uses larger alignment.
  564. int paddedWidth = dsr::image_getStride(this->canvas[displayIndex]) / 4;
  565. //int width = dsr::image_getWidth(this->canvas[displayIndex]);
  566. int height = dsr::image_getHeight(this->canvas[displayIndex]);
  567. InvalidateRect(this->hwnd, NULL, false);
  568. PAINTSTRUCT paintStruct;
  569. HDC targetContext = BeginPaint(this->hwnd, &paintStruct);
  570. BITMAPINFO bmi = {};
  571. bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
  572. bmi.bmiHeader.biWidth = paddedWidth;
  573. bmi.bmiHeader.biHeight = -height;
  574. bmi.bmiHeader.biPlanes = 1;
  575. bmi.bmiHeader.biBitCount = 32;
  576. bmi.bmiHeader.biCompression = BI_RGB;
  577. SetDIBitsToDevice(targetContext, 0, 0, paddedWidth, height, 0, 0, 0, height, dsr::image_dangerous_getData(this->canvas[displayIndex]), &bmi, DIB_RGB_COLORS);
  578. EndPaint(this->hwnd, &paintStruct);
  579. if (lock) {
  580. windowLock.unlock();
  581. }
  582. };
  583. #ifdef DISABLE_MULTI_THREADING
  584. // Perform instantly
  585. task();
  586. #else
  587. if (this->firstFrame) {
  588. // The first frame will be cloned when double buffering.
  589. if (bufferCount == 2) {
  590. dsr::draw_copy(this->canvas[this->drawIndex], this->canvas[this->showIndex]);
  591. }
  592. // Single-thread the first frame to keep it safe.
  593. task();
  594. this->firstFrame = false;
  595. } else {
  596. // Run in the background while doing other things
  597. this->displayFuture = std::async(std::launch::async, task);
  598. }
  599. #endif
  600. }
  601. void Win32Window::showCanvas() {
  602. this->redraw(this->hwnd, true);
  603. }
  604. std::shared_ptr<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
  605. auto backend = std::make_shared<Win32Window>(title, width, height);
  606. return std::dynamic_pointer_cast<dsr::BackendWindow>(backend);
  607. }