Win32Window.cpp 26 KB

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