Win32Window.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. #include "../DFPSR/api/imageAPI.h"
  2. #include "../DFPSR/gui/BackendWindow.h"
  3. /*
  4. Link to these dependencies for MS Windows:
  5. gdi32
  6. user32
  7. kernel32
  8. comctl32
  9. */
  10. #include <tchar.h>
  11. #include <windows.h>
  12. #include <windowsx.h>
  13. class Win32Window : public dsr::BackendWindow {
  14. public:
  15. // The native windows handle
  16. HWND hwnd;
  17. // Double buffering to allow drawing to a canvas while displaying the previous one
  18. // The image which can be drawn to
  19. dsr::AlignedImageRgbaU8 canvas;
  20. // Remembers the dimensions of the window from creation and resize events
  21. // This allow requesting the size of the window at any time
  22. int windowWidth = 0, windowHeight = 0;
  23. private:
  24. // Called before the application fetches events from the input queue
  25. // Closing the window, moving the mouse, pressing a key, et cetera
  26. void prefetchEvents() override;
  27. private:
  28. // Helper methods specific to calling XLib
  29. void updateTitle();
  30. private:
  31. // Canvas methods
  32. dsr::AlignedImageRgbaU8 getCanvas() override { return this->canvas; }
  33. void resizeCanvas(int width, int height) override;
  34. // Window methods
  35. void setTitle(const dsr::String &newTitle) override {
  36. this->title = newTitle;
  37. this->updateTitle();
  38. }
  39. void removeOldWindow();
  40. void createWindow(const dsr::String& title, int width, int height);
  41. void createWindowed(const dsr::String& title, int width, int height);
  42. void createFullscreen();
  43. void prepareWindow();
  44. int windowState = 0; // 0=none, 1=windowed, 2=fullscreen
  45. public:
  46. // Constructors
  47. Win32Window(const Win32Window&) = delete; // Non-copyable because of pointer aliasing.
  48. Win32Window(const dsr::String& title, int width, int height);
  49. int getWidth() const override { return this->windowWidth; };
  50. int getHeight() const override { return this->windowHeight; };
  51. // Destructor
  52. ~Win32Window();
  53. // Interface
  54. void setFullScreen(bool enabled) override;
  55. bool isFullScreen() override { return this->windowState == 2; }
  56. void redraw(HWND& hwnd); // HWND is passed by argument because drawing might be called before the constructor has assigned it to this->hwnd
  57. void showCanvas() override;
  58. };
  59. static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  60. static TCHAR windowClassName[] = _T("DfpsrWindowApplication");
  61. void Win32Window::updateTitle() {
  62. /* TODO: Test on Windows
  63. if (!SetWindowTextA(this->hwnd, this->title.toStdString().c_str())) {
  64. dsr::printText("Warning! Could not assign the window title ", dsr::string_mangleQuote(this->title), ".\n");
  65. }
  66. */
  67. }
  68. void Win32Window::setFullScreen(bool enabled) {
  69. if (this->windowState == 1 && enabled) {
  70. // Clean up any previous window
  71. removeOldWindow();
  72. // Create the new window and graphics context
  73. this->createFullscreen();
  74. } else if (this->windowState == 2 && !enabled) {
  75. // Clean up any previous window
  76. removeOldWindow();
  77. // Create the new window and graphics context
  78. this->createWindowed(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode
  79. }
  80. }
  81. void Win32Window::removeOldWindow() {
  82. if (this->windowState != 0) {
  83. DestroyWindow(this->hwnd);
  84. }
  85. this->windowState = 0;
  86. }
  87. void Win32Window::prepareWindow() {
  88. // Reallocate the canvas
  89. this->resizeCanvas(this->windowWidth, this->windowHeight);
  90. // Show the window
  91. ShowWindow(this->hwnd, SW_NORMAL);
  92. // Repaint
  93. UpdateWindow(this->hwnd);
  94. }
  95. void Win32Window::createWindow(const dsr::String& title, int width, int height) {
  96. // Request to resize the canvas and interface according to the new window
  97. this->windowWidth = width;
  98. this->windowHeight = height;
  99. this->receivedWindowResize(width, height);
  100. // The Window structure
  101. WNDCLASSEX wincl;
  102. memset(&wincl, 0, sizeof(WNDCLASSEX));
  103. wincl.hInstance = NULL;
  104. wincl.lpszClassName = windowClassName;
  105. wincl.lpfnWndProc = WindowProcedure;
  106. wincl.style = 0;
  107. wincl.cbSize = sizeof(WNDCLASSEX);
  108. // Use default icon and mouse-pointer
  109. wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  110. wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  111. wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
  112. wincl.lpszMenuName = NULL; // No menu
  113. wincl.cbClsExtra = 0; // No extra bytes after the window class
  114. wincl.cbWndExtra = sizeof(LPVOID); // structure or the window instance
  115. // Use Windows's default color as the background of the window
  116. wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND; // TODO: Make black
  117. // Register the window class, and if it fails quit the program
  118. if (!RegisterClassEx (&wincl)) {
  119. dsr::throwError("Call to RegisterClassEx failed!\n");
  120. }
  121. // The class is registered, let's create the program
  122. this->hwnd = CreateWindowEx(
  123. 0, // Extended possibilites for variation
  124. windowClassName, // Classname
  125. _T("MyWindow"), // Title Text
  126. WS_OVERLAPPEDWINDOW, // default window
  127. CW_USEDEFAULT, // Windows decides the position
  128. CW_USEDEFAULT, // where the window ends up on the screen
  129. width, // The programs width
  130. height, // and height in pixels
  131. HWND_DESKTOP, // The window is a child-window to desktop
  132. NULL, // No menu
  133. NULL, // Program Instance handler
  134. (LPVOID)this // Pointer to the window wrapper
  135. );
  136. // TODO: Set the title
  137. this->updateTitle();
  138. }
  139. void Win32Window::createWindowed(const dsr::String& title, int width, int height) {
  140. // Create the window
  141. this->createWindow(title, width, height);
  142. this->windowState = 1;
  143. this->prepareWindow();
  144. }
  145. void Win32Window::createFullscreen() {
  146. // TODO: Implement borderless, decorationless, maximized full-screen
  147. createWindowed("Full-screen is not yet supported on Windows", 800, 600);
  148. this->windowState = 2;
  149. this->prepareWindow();
  150. }
  151. Win32Window::Win32Window(const dsr::String& title, int width, int height) {
  152. bool fullScreen = false;
  153. if (width < 1 || height < 1) {
  154. fullScreen = true;
  155. }
  156. // Remember the title
  157. this->title = title;
  158. // Create a window
  159. if (fullScreen) {
  160. this->createFullscreen();
  161. } else {
  162. this->createWindowed(title, width, height);
  163. }
  164. }
  165. static dsr::DsrKey getDsrKey(WPARAM keyCode) {
  166. dsr::DsrKey result = dsr::DsrKey_Unhandled;
  167. if (keyCode == VK_ESCAPE) {
  168. result = dsr::DsrKey_Escape;
  169. } else if (keyCode == VK_F1) {
  170. result = dsr::DsrKey_F1;
  171. } else if (keyCode == VK_F2) {
  172. result = dsr::DsrKey_F2;
  173. } else if (keyCode == VK_F3) {
  174. result = dsr::DsrKey_F3;
  175. } else if (keyCode == VK_F4) {
  176. result = dsr::DsrKey_F4;
  177. } else if (keyCode == VK_F5) {
  178. result = dsr::DsrKey_F5;
  179. } else if (keyCode == VK_F6) {
  180. result = dsr::DsrKey_F6;
  181. } else if (keyCode == VK_F7) {
  182. result = dsr::DsrKey_F7;
  183. } else if (keyCode == VK_F8) {
  184. result = dsr::DsrKey_F8;
  185. } else if (keyCode == VK_F9) {
  186. result = dsr::DsrKey_F9;
  187. } else if (keyCode == VK_F10) {
  188. result = dsr::DsrKey_F10;
  189. } else if (keyCode == VK_F11) {
  190. result = dsr::DsrKey_F11;
  191. } else if (keyCode == VK_F12) {
  192. result = dsr::DsrKey_F12;
  193. } else if (keyCode == VK_PAUSE) {
  194. result = dsr::DsrKey_Pause;
  195. } else if (keyCode == VK_SPACE) {
  196. result = dsr::DsrKey_Space;
  197. } else if (keyCode == VK_TAB) {
  198. result = dsr::DsrKey_Tab;
  199. } else if (keyCode == VK_RETURN) {
  200. result = dsr::DsrKey_Return;
  201. } else if (keyCode == VK_BACK) {
  202. result = dsr::DsrKey_BackSpace;
  203. } else if (keyCode == VK_LSHIFT) {
  204. result = dsr::DsrKey_LeftShift;
  205. } else if (keyCode == VK_RSHIFT) {
  206. result = dsr::DsrKey_RightShift;
  207. } else if (keyCode == VK_LCONTROL) {
  208. result = dsr::DsrKey_LeftControl;
  209. } else if (keyCode == VK_RCONTROL) {
  210. result = dsr::DsrKey_RightControl;
  211. } else if (keyCode == VK_LMENU) {
  212. result = dsr::DsrKey_LeftAlt;
  213. } else if (keyCode == VK_RMENU) {
  214. result = dsr::DsrKey_RightAlt;
  215. } else if (keyCode == VK_DELETE) {
  216. result = dsr::DsrKey_Delete;
  217. } else if (keyCode == VK_LEFT) {
  218. result = dsr::DsrKey_LeftArrow;
  219. } else if (keyCode == VK_RIGHT) {
  220. result = dsr::DsrKey_RightArrow;
  221. } else if (keyCode == VK_UP) {
  222. result = dsr::DsrKey_UpArrow;
  223. } else if (keyCode == VK_DOWN) {
  224. result = dsr::DsrKey_DownArrow;
  225. } else if (keyCode == 0x30) {
  226. result = dsr::DsrKey_0;
  227. } else if (keyCode == 0x31) {
  228. result = dsr::DsrKey_1;
  229. } else if (keyCode == 0x32) {
  230. result = dsr::DsrKey_2;
  231. } else if (keyCode == 0x33) {
  232. result = dsr::DsrKey_3;
  233. } else if (keyCode == 0x34) {
  234. result = dsr::DsrKey_4;
  235. } else if (keyCode == 0x35) {
  236. result = dsr::DsrKey_5;
  237. } else if (keyCode == 0x36) {
  238. result = dsr::DsrKey_6;
  239. } else if (keyCode == 0x37) {
  240. result = dsr::DsrKey_7;
  241. } else if (keyCode == 0x38) {
  242. result = dsr::DsrKey_8;
  243. } else if (keyCode == 0x39) {
  244. result = dsr::DsrKey_9;
  245. } else if (keyCode == 0x41) {
  246. result = dsr::DsrKey_A;
  247. } else if (keyCode == 0x42) {
  248. result = dsr::DsrKey_B;
  249. } else if (keyCode == 0x43) {
  250. result = dsr::DsrKey_C;
  251. } else if (keyCode == 0x44) {
  252. result = dsr::DsrKey_D;
  253. } else if (keyCode == 0x45) {
  254. result = dsr::DsrKey_E;
  255. } else if (keyCode == 0x46) {
  256. result = dsr::DsrKey_F;
  257. } else if (keyCode == 0x47) {
  258. result = dsr::DsrKey_G;
  259. } else if (keyCode == 0x48) {
  260. result = dsr::DsrKey_H;
  261. } else if (keyCode == 0x49) {
  262. result = dsr::DsrKey_I;
  263. } else if (keyCode == 0x4A) {
  264. result = dsr::DsrKey_J;
  265. } else if (keyCode == 0x4B) {
  266. result = dsr::DsrKey_K;
  267. } else if (keyCode == 0x4C) {
  268. result = dsr::DsrKey_L;
  269. } else if (keyCode == 0x4D) {
  270. result = dsr::DsrKey_M;
  271. } else if (keyCode == 0x4E) {
  272. result = dsr::DsrKey_N;
  273. } else if (keyCode == 0x4F) {
  274. result = dsr::DsrKey_O;
  275. } else if (keyCode == 0x50) {
  276. result = dsr::DsrKey_P;
  277. } else if (keyCode == 0x51) {
  278. result = dsr::DsrKey_Q;
  279. } else if (keyCode == 0x52) {
  280. result = dsr::DsrKey_R;
  281. } else if (keyCode == 0x53) {
  282. result = dsr::DsrKey_S;
  283. } else if (keyCode == 0x54) {
  284. result = dsr::DsrKey_T;
  285. } else if (keyCode == 0x55) {
  286. result = dsr::DsrKey_U;
  287. } else if (keyCode == 0x56) {
  288. result = dsr::DsrKey_V;
  289. } else if (keyCode == 0x57) {
  290. result = dsr::DsrKey_W;
  291. } else if (keyCode == 0x58) {
  292. result = dsr::DsrKey_X;
  293. } else if (keyCode == 0x59) {
  294. result = dsr::DsrKey_Y;
  295. } else if (keyCode == 0x5A) {
  296. result = dsr::DsrKey_Z;
  297. }
  298. return result;
  299. }
  300. // Called from DispatchMessage via prefetchEvents
  301. static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
  302. // Get the Win32Window owning the given hwnd
  303. Win32Window *parent = nullptr;
  304. if (message == WM_CREATE) {
  305. // Cast the pointer argument into CREATESTRUCT and get the lParam given to the window on creation
  306. CREATESTRUCT *createStruct = (CREATESTRUCT*)lParam;
  307. parent = (Win32Window*)createStruct->lpCreateParams;
  308. if (parent == nullptr) {
  309. dsr::throwError("Null handle retreived from lParam (", (intptr_t)parent, ") in WM_CREATE message.\n");
  310. }
  311. SetWindowLongPtr(hwnd, GWLP_USERDATA, (intptr_t)parent);
  312. } else {
  313. // Get the parent
  314. parent = (Win32Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  315. if (parent == nullptr) {
  316. // Don't try to handle global events unrelated to any window
  317. return DefWindowProc(hwnd, message, wParam, lParam);
  318. }
  319. }
  320. // Check that we're using the correct window instance (This might not be the case while toggling full-screen)
  321. // Handle the message
  322. int result = 0;
  323. switch (message) {
  324. case -1:
  325. dsr::throwError("Unknown error in Window handler!\n");
  326. break;
  327. case WM_QUIT:
  328. PostQuitMessage(wParam);
  329. break;
  330. case WM_CLOSE:
  331. parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Close, parent->windowWidth, parent->windowHeight));
  332. DestroyWindow(hwnd);
  333. break;
  334. case WM_LBUTTONDOWN:
  335. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  336. break;
  337. case WM_LBUTTONUP:
  338. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  339. break;
  340. case WM_RBUTTONDOWN:
  341. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  342. break;
  343. case WM_RBUTTONUP:
  344. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  345. break;
  346. case WM_MBUTTONDOWN:
  347. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  348. break;
  349. case WM_MBUTTONUP:
  350. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  351. break;
  352. case WM_MOUSEMOVE:
  353. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  354. break;
  355. case WM_MOUSEWHEEL:
  356. {
  357. int delta = GET_WHEEL_DELTA_WPARAM(wParam);
  358. if (delta > 0) {
  359. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollUp, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  360. } else if (delta < 0) {
  361. parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollDown, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))));
  362. }
  363. }
  364. break;
  365. case WM_KEYDOWN: case WM_KEYUP:
  366. {
  367. char character = wParam; // System specific key-code
  368. dsr::DsrKey dsrKey = getDsrKey(wParam); // Portable key-code
  369. if (message == WM_KEYDOWN) {
  370. // Physical key down
  371. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyDown, character, dsrKey));
  372. // First press typing
  373. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
  374. } else { // message == WM_KEYUP
  375. // Physical key up
  376. parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyUp, character, dsrKey));
  377. }
  378. }
  379. break;
  380. case WM_PAINT:
  381. parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Redraw, parent->windowWidth, parent->windowHeight));
  382. // BeginPaint and EndPaint must be called with the given hwnd to prevent having the redraw message sent again
  383. parent->redraw(hwnd);
  384. // Passing on the event to prevent flooding with more messages. This is only a temporary solution.
  385. // TODO: Avoid overwriting the result with any background color.
  386. //result = DefWindowProc(hwnd, message, wParam, lParam);
  387. break;
  388. case WM_SIZE:
  389. // If there's no size during minimization, don't try to resize the canvas
  390. if (wParam != SIZE_MINIMIZED) {
  391. int width = LOWORD(lParam);
  392. int height = HIWORD(lParam);
  393. parent->windowWidth = width;
  394. parent->windowHeight = height;
  395. parent->receivedWindowResize(width, height);
  396. }
  397. // Resize the window as requested
  398. result = DefWindowProc(hwnd, message, wParam, lParam);
  399. break;
  400. // TODO: Keyboard presses & typing
  401. default:
  402. result = DefWindowProc(hwnd, message, wParam, lParam);
  403. }
  404. return result;
  405. }
  406. void Win32Window::prefetchEvents() {
  407. MSG messages;
  408. // Windows hangs unless we process application events for each window
  409. while (PeekMessage(&messages, NULL, 0, 0, PM_REMOVE)) {
  410. //dsr::printText("Received an event ", messages.message, "(", messages.wParam, ", ", (intptr_t)messages.lParam, ")\n");
  411. TranslateMessage(&messages);
  412. DispatchMessage(&messages); // Calling WindowProcedure for each window instance
  413. }
  414. }
  415. // Locked because it overrides
  416. void Win32Window::resizeCanvas(int width, int height) {
  417. // Create a new canvas
  418. // Even thou Windows is using RGBA pack order for the window, the bitmap format used for drawing is using BGRA order
  419. this->canvas = dsr::image_create_RgbaU8_native(width, height, dsr::PackOrderIndex::BGRA);
  420. }
  421. Win32Window::~Win32Window() {
  422. // Destroy the native window
  423. DestroyWindow(this->hwnd);
  424. }
  425. void Win32Window::redraw(HWND& hwnd) {
  426. // Let the source bitmap use a padded width to safely handle the stride
  427. // Windows require 8-byte alignment, but the image format uses 16-byte alignment.
  428. int paddedWidth = dsr::image_getStride(this->canvas) / 4;
  429. //int width = dsr::image_getWidth(this->canvas);
  430. int height = dsr::image_getHeight(this->canvas);
  431. InvalidateRect(this->hwnd, NULL, false);
  432. PAINTSTRUCT paintStruct;
  433. HDC targetContext = BeginPaint(this->hwnd, &paintStruct);
  434. BITMAPINFO bmi = {};
  435. bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
  436. bmi.bmiHeader.biWidth = paddedWidth;
  437. bmi.bmiHeader.biHeight = -height;
  438. bmi.bmiHeader.biPlanes = 1;
  439. bmi.bmiHeader.biBitCount = 32;
  440. bmi.bmiHeader.biCompression = BI_RGB;
  441. SetDIBitsToDevice(targetContext, 0, 0, paddedWidth, height, 0, 0, 0, height, dsr::image_dangerous_getData(this->canvas), &bmi, DIB_RGB_COLORS);
  442. EndPaint(this->hwnd, &paintStruct);
  443. }
  444. void Win32Window::showCanvas() {
  445. this->prefetchEvents();
  446. this->redraw(this->hwnd);
  447. }
  448. std::shared_ptr<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
  449. auto backend = std::make_shared<Win32Window>(title, width, height);
  450. return std::dynamic_pointer_cast<dsr::BackendWindow>(backend);
  451. }