window.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. #if defined(Hiro_Window)
  2. namespace hiro {
  3. static auto CALLBACK Window_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
  4. if(Application::state().quit) return DefWindowProc(hwnd, msg, wparam, lparam);
  5. if(auto window = (mWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
  6. if(auto self = window->self()) {
  7. if(auto result = self->windowProc(hwnd, msg, wparam, lparam)) {
  8. return result();
  9. }
  10. }
  11. }
  12. return Shared_windowProc(DefWindowProc, hwnd, msg, wparam, lparam);
  13. }
  14. static const uint PopupStyle = WS_POPUP | WS_CLIPCHILDREN;
  15. static const uint FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER | WS_CLIPCHILDREN;
  16. static const uint ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CLIPCHILDREN;
  17. uint pWindow::minimumStatusHeight = 0;
  18. auto pWindow::initialize() -> void {
  19. pApplication::state().modalTimer.setInterval(1);
  20. pApplication::state().modalTimer.onActivate([] { Application::doMain(); });
  21. HWND hwnd = CreateWindow(L"hiroWindow", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0);
  22. HWND hstatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0);
  23. SetWindowPos(hstatus, nullptr, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
  24. RECT rc;
  25. GetWindowRect(hstatus, &rc);
  26. minimumStatusHeight = rc.bottom - rc.top;
  27. DestroyWindow(hstatus);
  28. DestroyWindow(hwnd);
  29. }
  30. auto pWindow::construct() -> void {
  31. hwnd = CreateWindow(L"hiroWindow", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0);
  32. SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
  33. setDroppable(state().droppable);
  34. setGeometry({128, 128, 256, 256});
  35. windows.append(self().instance);
  36. }
  37. auto pWindow::destruct() -> void {
  38. if(auto position = windows.find(self().instance)) windows.remove(*position);
  39. if(hbrush) { DeleteObject(hbrush); hbrush = nullptr; }
  40. DestroyWindow(hwnd);
  41. }
  42. auto pWindow::append(sMenuBar menuBar) -> void {
  43. }
  44. auto pWindow::append(sSizable sizable) -> void {
  45. }
  46. auto pWindow::append(sStatusBar statusBar) -> void {
  47. }
  48. auto pWindow::focused() const -> bool {
  49. return (GetForegroundWindow() == hwnd);
  50. }
  51. auto pWindow::frameMargin() const -> Geometry {
  52. RECT rc{0, 0, 640, 480};
  53. uint style = state().fullScreen ? 0 : state().resizable ? ResizableStyle : FixedStyle;
  54. bool menuVisible = state().menuBar && state().menuBar->visible();
  55. AdjustWindowRect(&rc, style, menuVisible);
  56. auto& efb = state().fullScreen ? settings.efbPopup : !state().resizable ? settings.efbFixed : settings.efbResizable;
  57. return {
  58. abs(rc.left) - efb.x,
  59. abs(rc.top) - efb.y,
  60. (rc.right - rc.left) - 640 - efb.width,
  61. (rc.bottom - rc.top) + _statusHeight() - 480 - efb.height
  62. };
  63. }
  64. auto pWindow::handle() const -> uintptr_t {
  65. return (uintptr_t)hwnd;
  66. }
  67. auto pWindow::monitor() const -> uint {
  68. //TODO
  69. return 0;
  70. }
  71. auto pWindow::remove(sMenuBar menuBar) -> void {
  72. }
  73. auto pWindow::remove(sSizable sizable) -> void {
  74. }
  75. auto pWindow::remove(sStatusBar statusBar) -> void {
  76. }
  77. auto pWindow::setBackgroundColor(Color color) -> void {
  78. hbrushColor = CreateRGB(color);
  79. if(hbrush) { DeleteObject(hbrush); hbrush = nullptr; }
  80. if(color) hbrush = CreateSolidBrush(hbrushColor);
  81. }
  82. auto pWindow::setDismissable(bool dismissable) -> void {
  83. }
  84. auto pWindow::setDroppable(bool droppable) -> void {
  85. DragAcceptFiles(hwnd, droppable);
  86. }
  87. auto pWindow::setEnabled(bool enabled) -> void {
  88. if(auto& sizable = state().sizable) {
  89. if(auto self = sizable->self()) self->setEnabled(sizable->enabled(true));
  90. }
  91. }
  92. auto pWindow::setFocused() -> void {
  93. if(!self().visible()) self().setVisible(true);
  94. SetFocus(hwnd);
  95. }
  96. auto pWindow::setFont(const Font& font) -> void {
  97. if(auto& sizable = state().sizable) {
  98. if(auto self = sizable->self()) self->setFont(sizable->font(true));
  99. }
  100. }
  101. auto pWindow::setFullScreen(bool fullScreen) -> void {
  102. auto lock = acquire();
  103. auto style = GetWindowLongPtr(hwnd, GWL_STYLE) & WS_VISIBLE;
  104. if(fullScreen) {
  105. windowedGeometry = self().geometry();
  106. HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
  107. MONITORINFOEX info;
  108. memset(&info, 0, sizeof(MONITORINFOEX));
  109. info.cbSize = sizeof(MONITORINFOEX);
  110. GetMonitorInfo(monitor, &info);
  111. RECT rc = info.rcMonitor;
  112. Geometry geometry = {rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top};
  113. SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_POPUP);
  114. Geometry margin = frameMargin();
  115. self().setGeometry({
  116. geometry.x() + margin.x(), geometry.y() + margin.y(),
  117. geometry.width() - margin.width(), geometry.height() - margin.height()
  118. });
  119. } else {
  120. SetWindowLongPtr(hwnd, GWL_STYLE, style | (state().resizable ? ResizableStyle : FixedStyle));
  121. self().setGeometry(windowedGeometry);
  122. }
  123. }
  124. auto pWindow::setGeometry(Geometry geometry) -> void {
  125. auto lock = acquire();
  126. Geometry margin = frameMargin();
  127. auto& efb = state().fullScreen ? settings.efbPopup : !state().resizable ? settings.efbFixed : settings.efbResizable;
  128. SetWindowPos(
  129. hwnd, nullptr,
  130. geometry.x() - margin.x() - efb.x,
  131. geometry.y() - margin.y() - efb.y,
  132. geometry.width() + margin.width() + efb.width,
  133. geometry.height() + margin.height() + efb.height,
  134. SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED
  135. );
  136. if(auto& statusBar = state().statusBar) {
  137. if(auto self = statusBar->self()) {
  138. SetWindowPos(self->hwnd, nullptr, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
  139. }
  140. }
  141. if(auto& sizable = state().sizable) {
  142. sizable->setGeometry(geometry.setPosition());
  143. }
  144. }
  145. auto pWindow::setMaximized(bool maximized) -> void {
  146. if(state().minimized) return;
  147. auto lock = acquire();
  148. ShowWindow(hwnd, maximized ? SW_MAXIMIZE : SW_SHOWNOACTIVATE);
  149. }
  150. auto pWindow::setMaximumSize(Size size) -> void {
  151. }
  152. auto pWindow::setMinimized(bool minimized) -> void {
  153. auto lock = acquire();
  154. ShowWindow(hwnd, minimized ? SW_MINIMIZE : state().maximized ? SW_MAXIMIZE : SW_SHOWNOACTIVATE);
  155. }
  156. auto pWindow::setMinimumSize(Size size) -> void {
  157. }
  158. //never call this directly: use Window::setModal() instead
  159. //this function does not confirm the modality has actually changed before adjusting modalCount
  160. auto pWindow::setModal(bool modality) -> void {
  161. if(modality) {
  162. modalIncrement();
  163. _modalityUpdate();
  164. while(!Application::state().quit && state().modal) {
  165. if(Application::state().onMain) {
  166. Application::doMain();
  167. } else {
  168. usleep(20 * 1000);
  169. }
  170. Application::processEvents();
  171. }
  172. _modalityUpdate();
  173. } else {
  174. modalDecrement();
  175. }
  176. }
  177. auto pWindow::setResizable(bool resizable) -> void {
  178. auto style = GetWindowLongPtr(hwnd, GWL_STYLE) & WS_VISIBLE;
  179. SetWindowLongPtr(hwnd, GWL_STYLE, style | (state().resizable ? ResizableStyle : FixedStyle));
  180. setGeometry(state().geometry);
  181. }
  182. auto pWindow::setTitle(string text) -> void {
  183. SetWindowText(hwnd, utf16_t(text));
  184. }
  185. auto pWindow::setVisible(bool visible) -> void {
  186. auto lock = acquire();
  187. ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE);
  188. if(auto& sizable = state().sizable) {
  189. sizable->setGeometry(self().geometry().setPosition());
  190. }
  191. if(!visible) self().setModal(false);
  192. //calculate window extended frame bounds: DwmGetWindowAttributes is only valid after the window is visible
  193. //by then, it's too late to position the window correctly, but we can cache the results here for next time
  194. //because GetWindowRect and DwmGetWindowAttribute returns different unit types, the hiro application *must* be DPI aware
  195. if(visible) {
  196. //in logical units
  197. RECT rc;
  198. GetWindowRect(hwnd, &rc);
  199. //in physical units
  200. RECT fc;
  201. DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &fc, sizeof(RECT));
  202. //convert to offsets useful to hiro
  203. auto& efb = state().fullScreen ? settings.efbPopup : !state().resizable ? settings.efbFixed : settings.efbResizable;
  204. efb.x = fc.left - rc.left;
  205. efb.y = fc.top - rc.top;
  206. efb.width = efb.x + (rc.right - fc.right);
  207. efb.height = efb.y + (rc.bottom - fc.bottom);
  208. //sanitize inputs: if the bounds are obviously nonsense, give up on trying to compensate for them
  209. if(efb.x > 100 || efb.y > 100 || efb.width > 100 || efb.height > 100) efb = {};
  210. }
  211. }
  212. //
  213. auto pWindow::modalIncrement() -> void {
  214. if(pApplication::state().modalCount++ == 0) {
  215. pApplication::state().modalTimer.setEnabled(true);
  216. }
  217. }
  218. auto pWindow::modalDecrement() -> void {
  219. if(--pApplication::state().modalCount == 0) {
  220. pApplication::state().modalTimer.setEnabled(false);
  221. }
  222. }
  223. auto pWindow::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
  224. if(msg == WM_CLOSE || (msg == WM_KEYDOWN && wparam == VK_ESCAPE && state().dismissable)) {
  225. if(state().onClose) {
  226. self().doClose();
  227. //doClose() may end up destroying the window when terminating the application ...
  228. //forcefully return early in said case, so that the modal check below doesn't access the destroyed pWindow object
  229. if(Application::state().quit) return true;
  230. } else {
  231. self().setVisible(false);
  232. }
  233. if(state().modal && !self().visible()) self().setModal(false);
  234. return true;
  235. }
  236. if(msg == WM_MOVE && !locked()) {
  237. state().geometry.setPosition(_geometry().position());
  238. self().doMove();
  239. }
  240. if(msg == WM_SIZE && !locked()) {
  241. if(auto statusBar = state().statusBar) {
  242. if(auto self = statusBar->self()) {
  243. SetWindowPos(self->hwnd, nullptr, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
  244. }
  245. }
  246. state().geometry.setSize(_geometry().size());
  247. if(auto& sizable = state().sizable) {
  248. sizable->setGeometry(_geometry().setPosition());
  249. }
  250. self().doSize();
  251. }
  252. if(msg == WM_DROPFILES) {
  253. if(auto paths = DropPaths(wparam)) self().doDrop(paths);
  254. return false;
  255. }
  256. if(msg == WM_ERASEBKGND && hbrush) {
  257. RECT rc;
  258. GetClientRect(hwnd, &rc);
  259. PAINTSTRUCT ps;
  260. BeginPaint(hwnd, &ps);
  261. FillRect(ps.hdc, &rc, hbrush);
  262. EndPaint(hwnd, &ps);
  263. return true;
  264. }
  265. if(msg == WM_ENTERMENULOOP || msg == WM_ENTERSIZEMOVE) {
  266. modalIncrement();
  267. return false;
  268. }
  269. if(msg == WM_EXITMENULOOP || msg == WM_EXITSIZEMOVE) {
  270. modalDecrement();
  271. return false;
  272. }
  273. if(msg == WM_SYSCOMMAND) {
  274. if(wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
  275. if(!Application::screenSaver()) return 0;
  276. }
  277. }
  278. return {};
  279. }
  280. //
  281. auto pWindow::_geometry() -> Geometry {
  282. Geometry margin = frameMargin();
  283. RECT rc;
  284. if(IsIconic(hwnd)) {
  285. //GetWindowRect returns x=-32000,y=-32000 when window is minimized
  286. WINDOWPLACEMENT wp;
  287. GetWindowPlacement(hwnd, &wp);
  288. rc = wp.rcNormalPosition;
  289. } else {
  290. GetWindowRect(hwnd, &rc);
  291. }
  292. auto& efb = state().fullScreen ? settings.efbPopup : !state().resizable ? settings.efbFixed : settings.efbResizable;
  293. auto x = rc.left + margin.x() + efb.x;
  294. auto y = rc.top + margin.y() + efb.y;
  295. auto width = (rc.right - rc.left) - margin.width() - efb.width;
  296. auto height = (rc.bottom - rc.top) - margin.height() - efb.height;
  297. return {x, y, width, height};
  298. }
  299. auto pWindow::_modalityCount() -> unsigned {
  300. unsigned modalWindows = 0;
  301. for(auto& weak : windows) {
  302. if(auto object = weak.acquire()) {
  303. if(auto window = dynamic_cast<mWindow*>(object.data())) {
  304. if(window->modal()) modalWindows++;
  305. }
  306. }
  307. }
  308. return modalWindows;
  309. }
  310. auto pWindow::_modalityDisabled() -> bool {
  311. if(_modalityCount() == 0) return false;
  312. return !state().modal;
  313. }
  314. auto pWindow::_modalityUpdate() -> void {
  315. unsigned modalWindows = _modalityCount();
  316. for(auto& weak : windows) {
  317. if(auto object = weak.acquire()) {
  318. if(auto window = dynamic_cast<mWindow*>(object.data())) {
  319. if(auto self = window->self()) {
  320. bool enabled = !modalWindows || window->modal();
  321. if(IsWindowEnabled(self->hwnd) != enabled) {
  322. EnableWindow(self->hwnd, enabled);
  323. }
  324. }
  325. }
  326. }
  327. }
  328. }
  329. auto pWindow::_statusHeight() const -> int {
  330. int height = 0;
  331. if(auto& statusBar = state().statusBar) {
  332. if(statusBar->visible()) {
  333. auto& text = statusBar->state.text;
  334. height = statusBar->font(true).size(text ? text : " ").height();
  335. height = max(height, minimumStatusHeight);
  336. }
  337. }
  338. return height;
  339. }
  340. }
  341. #endif