Win32Window.cpp 25 KB

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