1 /* 2 Copyright Chris Jones 2020. 3 Distributed under the Boost Software License, Version 1.0. 4 See accompanying file Licence.txt or copy at... 5 https://www.boost.org/LICENSE_1_0.txt 6 */ 7 8 module window; 9 10 import std.stdio; 11 import core.sys.windows.windows; 12 import std..string; 13 import std.conv; 14 import dg2d; 15 16 import dg2d.roundrect : dg2dRoundRect = RoundRect; // need as RoundRect is in wingdi too. 17 18 static import gdi = core.sys.windows.wingdi; 19 20 pragma(lib, "gdi32"); 21 pragma(lib, "user32"); 22 23 /* 24 Windows Message Loop 25 */ 26 27 int WindowsMessageLoop() 28 { 29 MSG msg; 30 while (GetMessageA(&msg, null, 0, 0)) 31 { 32 TranslateMessage(&msg); 33 DispatchMessageA(&msg); 34 } 35 return cast(int) msg.wParam; 36 } 37 38 /* 39 Window Proc, not sure what to do about catching Errors/Exceptions, seems to 40 just hang no matter what, tried assert(0), ExitProcess etc.. just doesnt close 41 */ 42 43 extern(Windows) 44 LRESULT WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow 45 { 46 try 47 { 48 auto window = cast(Window) (cast(void*) GetWindowLongPtr(hwnd, GWLP_USERDATA)); 49 50 if (window is null) 51 return DefWindowProcA(hwnd, msg, wparam, lparam); 52 else 53 return window.windowProc(hwnd, msg, wparam, lparam); 54 } 55 catch (Exception e) 56 { 57 try { writeln(e.toString()); } 58 catch(Exception what) {} 59 PostQuitMessage(0); 60 return 0; 61 } 62 } 63 64 /* 65 Window class, bare bones WinAPI wrapper 66 */ 67 68 class Window 69 { 70 private: 71 72 HWND m_handle; 73 DWORD m_style; 74 DWORD m_exstyle; 75 string m_title; 76 int m_width; 77 int m_height; 78 Canvas m_canvas; 79 80 Widget m_client; 81 82 static immutable char[] wndclass = "GFXWindow"; 83 static HINSTANCE hinstance; 84 85 UINT_PTR m_timer; 86 87 static this() 88 { 89 hinstance = HINSTANCE(GetModuleHandleA(NULL)); 90 91 WNDCLASSEXA wcx; 92 wcx.cbSize = wcx.sizeof; 93 wcx.style = CS_DBLCLKS; 94 wcx.lpfnWndProc = &WindowProc; 95 wcx.cbClsExtra = 0; 96 wcx.cbWndExtra = (void*).sizeof; 97 wcx.hInstance = hinstance; 98 wcx.hIcon = NULL; 99 wcx.hCursor = LoadCursor(NULL, IDC_ARROW); 100 wcx.hbrBackground = NULL; 101 wcx.lpszMenuName = NULL; 102 wcx.lpszClassName = wndclass.ptr; 103 wcx.hIconSm = NULL; 104 105 RegisterClassExA(&wcx); 106 } 107 108 public: 109 110 void createWindow(int x, int y, int w, int h, string title) 111 { 112 if (m_handle) destroyWindow(); 113 114 m_width = w; 115 m_height = h; 116 117 m_style = WS_OVERLAPPEDWINDOW; 118 m_exstyle = WS_EX_APPWINDOW; 119 120 RECT rect; 121 rect.left = x; 122 rect.top = y; 123 rect.right = x+w; 124 rect.bottom = y+h; 125 126 AdjustWindowRectEx(&rect, m_style, false, m_exstyle); 127 128 m_handle = CreateWindowExA( 129 WS_EX_APPWINDOW, wndclass.ptr, toStringz(title), WS_OVERLAPPEDWINDOW, 130 rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, 131 null, null, hinstance, NULL 132 ); 133 134 if (m_handle) 135 { 136 SetWindowLongPtrA(m_handle, GWLP_USERDATA, cast(LONG_PTR)( cast(void*)this )); 137 ShowWindow(m_handle, SW_SHOW); 138 139 m_timer = SetTimer(m_handle, 0, 1000/20, NULL); 140 } 141 else 142 { 143 writeln("oops... coud not create window"); 144 PostQuitMessage(0); 145 } 146 } 147 148 void destroyWindow() 149 { 150 if (m_handle) 151 { 152 DestroyWindow(m_handle); 153 m_handle = null; 154 } 155 } 156 157 this() 158 { 159 } 160 161 ~this() 162 { 163 destroyWindow(); 164 } 165 166 void setBounds(int x, int y, int w, int h) 167 { 168 assert((w >= 0) && (h >= 0)); 169 170 m_width = w; 171 m_height = h; 172 173 if (m_handle) 174 { 175 RECT r = RECT(x, y, x+w, y+h); 176 AdjustWindowRectEx(&r, m_style, false, m_exstyle); 177 MoveWindow(m_handle, r.left, r.top, r.right-r.left, 178 r.bottom-r.top, true); 179 } 180 } 181 182 bool isVisible() 183 { 184 if (m_handle) return (IsWindowVisible(m_handle) != 0); 185 return false; 186 } 187 188 void repaint() 189 { 190 InvalidateRect(m_handle,null,0); 191 } 192 193 void repaint(int x0, int y0, int x1, int y1) 194 { 195 RECT rect; 196 rect.left = x0; 197 rect.top = y0; 198 rect.right = x1; 199 rect.bottom = y1; 200 InvalidateRect(m_handle,&rect,0); 201 } 202 203 // Window proc handler 204 205 LRESULT windowProc(HWND hwnd, UINT msg, WPARAM _wparam, LPARAM _lparam) 206 { 207 WPARAM wparam = _wparam; 208 LPARAM lparam = _lparam; 209 210 switch (msg) 211 { 212 case(WM_PAINT): wm_Paint(); break; 213 case(WM_CLOSE): wm_Close(); break; 214 case(WM_DESTROY): wm_Destroy(); break; 215 case(WM_LBUTTONDOWN): wm_Mouse(MouseEvent.LeftDown, cast(uint)wparam, cast(uint)lparam); break; 216 case(WM_LBUTTONDBLCLK): wm_Mouse(MouseEvent.LeftDblCk, cast(uint)wparam, cast(uint)lparam); break; 217 case(WM_RBUTTONDOWN): wm_Mouse(MouseEvent.RightDown, cast(uint)wparam, cast(uint)lparam); break; 218 case(WM_RBUTTONDBLCLK): wm_Mouse(MouseEvent.RightDblCk, cast(uint)wparam, cast(uint)lparam); break; 219 case(WM_MOUSEMOVE): wm_Mouse(MouseEvent.Move, cast(uint)wparam, cast(uint)lparam); break; 220 case(WM_TIMER): wm_Timer(); break; 221 222 default: return DefWindowProc(hwnd, msg, wparam, lparam); // can't take m_handle there because of WM_CREATE 223 } 224 return 0; 225 } 226 227 void wm_Paint() 228 { 229 PAINTSTRUCT ps; 230 BeginPaint(m_handle, &ps); 231 int l = ps.rcPaint.left; 232 int t = ps.rcPaint.top; 233 int r = ps.rcPaint.right; 234 int b = ps.rcPaint.bottom; 235 236 if (m_canvas is null) m_canvas = new Canvas(r, b); 237 238 if ((m_canvas.width < r) || (m_canvas.height < b)) 239 { 240 m_canvas.resize(r, b); 241 } 242 243 m_canvas.resetView(); 244 m_canvas.setClip(l,t,r,b); 245 246 onPaint(m_canvas); 247 if (m_client !is null) m_client.internalPaint(m_canvas); 248 249 BITMAPINFO info; 250 info.bmiHeader.biSize = info.sizeof; 251 info.bmiHeader.biWidth = m_canvas.stride; 252 info.bmiHeader.biHeight = -m_canvas.height; 253 info.bmiHeader.biPlanes = 1; 254 info.bmiHeader.biBitCount = 32; 255 info.bmiHeader.biCompression = BI_RGB; 256 info.bmiHeader.biSizeImage = m_canvas.stride*m_canvas.height*4; 257 info.bmiHeader.biXPelsPerMeter = 0; 258 info.bmiHeader.biYPelsPerMeter = 0; 259 info.bmiHeader.biClrUsed = 0; 260 info.bmiHeader.biClrImportant = 0; 261 262 SetDIBitsToDevice( 263 ps.hdc, 0, 0, m_canvas.stride, m_canvas.height,0, 0, 0, 264 m_canvas.height, m_canvas.pixels, &info, DIB_RGB_COLORS); 265 266 EndPaint(m_handle, &ps); 267 } 268 269 void wm_Mouse(MouseEvent evt, uint wparam, uint lparam) 270 { 271 if (m_client !is null) 272 { 273 MouseMsg msg; 274 msg.event = evt; 275 msg.left = ((wparam & MK_LBUTTON) != 0); 276 msg.middle = ((wparam & MK_MBUTTON) != 0); 277 msg.right = ((wparam & MK_RBUTTON) != 0); 278 msg.x = cast(short)(lparam); // couldnt find GET_X_PARAM etc... 279 msg.y = cast(short)(lparam>>16); 280 m_client.internalMouse(msg); 281 } 282 } 283 284 void wm_Close() 285 { 286 PostQuitMessage(0); 287 } 288 289 void wm_Destroy() 290 { 291 m_handle = null; 292 } 293 294 void wm_Timer() 295 { 296 if (m_client !is null) m_client.internalTimer(); 297 } 298 299 void onPaint(Canvas canvas) 300 { 301 302 } 303 304 void addClient(Widget widget) 305 { 306 if (widget.m_parent !is null) widget.m_parent.removeChild(widget); 307 widget.m_window = this; 308 m_client = widget; 309 } 310 311 void removeClient(Widget widget) 312 { 313 if (widget is m_client) 314 { 315 widget.m_window = null; 316 m_client = null; 317 } 318 } 319 320 } 321 322 // mouse stuff 323 324 enum MouseEvent 325 { 326 LeftDown, MiddleDown, RightDown, 327 LeftUp, MiddleUp, RightUp, 328 LeftDblCk, MiddleDblCk, RightDblCk, 329 Move, Enter, Exit, EndFocus, 330 LeftDrag, RightDrag, 331 Wheel 332 } 333 334 struct MouseMsg 335 { 336 MouseEvent event; 337 int x,y,w; 338 bool focused; 339 bool left; 340 bool middle; 341 bool right; 342 bool shift; 343 bool ctrl; 344 bool alt; 345 } 346 347 // Widget 348 349 class Widget 350 { 351 this(int x, int y, int width, int height) 352 { 353 m_x = x; 354 m_y = y; 355 m_width = width; 356 m_height = height; 357 } 358 359 void addChild(Widget widget) 360 { 361 if (widget.m_parent !is null) widget.m_parent.removeChild(widget); 362 if (widget.m_window !is null) widget.m_window.removeClient(widget); 363 m_widgets ~= widget; 364 widget.m_parent = this; 365 } 366 367 void removeChild(Widget widget) 368 { 369 foreach(i, child; m_widgets) 370 { 371 if (child is widget) 372 { 373 m_widgets[i..$-1] = m_widgets[i+1..$]; 374 m_widgets.length = m_widgets.length-1; 375 widget.m_parent = null; 376 return; 377 } 378 } 379 } 380 381 void repaint() 382 { 383 repaint(0,0,m_width,m_height); 384 } 385 386 void repaint(int x0, int y0, int x1, int y1) 387 { 388 if (m_parent !is null) 389 { 390 m_parent.repaint(m_x+x0,m_y+y0,m_x+x1,m_y+y1); 391 } 392 else if (m_window !is null) 393 { 394 m_window.repaint(m_x+x0,m_y+y0,m_x+x1,m_y+y1); 395 } 396 } 397 398 void onPaint(Canvas canvas) 399 { 400 } 401 402 void onMouse(MouseMsg msg) 403 { 404 } 405 406 void onTimer() 407 { 408 } 409 410 int right() 411 { 412 return m_x+m_width; 413 } 414 415 int bottom() 416 { 417 return m_y+m_height; 418 } 419 420 bool contains(int x, int y) 421 { 422 return ((x >= m_x) && (x < m_x+m_width) 423 && (y >= m_y) && (y < m_y+m_height)); 424 } 425 426 private: 427 428 void internalPaint(Canvas canvas) 429 { 430 onPaint(canvas); 431 auto state = canvas.getViewState(); 432 433 foreach(widget; m_widgets) 434 { 435 canvas.setView(state, widget.m_x, widget.m_y, widget.right, widget.bottom); 436 if (!canvas.isClipEmpty) widget.internalPaint(canvas); 437 } 438 439 canvas.resetState(state); 440 } 441 442 // returns the widget that got the message 443 444 Widget internalMouse(MouseMsg msg) 445 { 446 foreach_reverse(widget; m_widgets) 447 { 448 if (widget.contains(msg.x,msg.y)) 449 { 450 msg.x -= widget.m_x; 451 msg.y -= widget.m_y; 452 return widget.internalMouse(msg); 453 } 454 } 455 onMouse(msg); 456 return this; 457 } 458 459 void internalTimer() 460 { 461 foreach_reverse(widget; m_widgets) 462 { 463 widget.onTimer(); 464 } 465 onTimer(); 466 } 467 468 Widget m_parent; 469 Window m_window; 470 Widget[] m_widgets; 471 472 int m_x,m_y,m_width,m_height; 473 } 474 475 /* 476 Button class 477 */ 478 479 alias ButtonClick = void delegate(); 480 481 class Button : Widget 482 { 483 this(int x,int y, int w, int h, string text, Font f) 484 { 485 super(x,y,w,h); 486 m_text = text; 487 m_font = f; 488 } 489 490 void setOnClick(ButtonClick onclick) 491 { 492 m_onclick = onclick; 493 } 494 495 bool hitTest(int x, int y) 496 { 497 return ((x >= m_x) && (x < m_x+m_width) 498 && (y >= m_y) && (y < m_y+m_height)); 499 } 500 501 override void onPaint(Canvas c) 502 { 503 c.draw(dg2dRoundRect(0,0,m_width,m_height,10).asPath, 0x80a0c0ff, WindingRule.NonZero); 504 c.draw(dg2dRoundRect(2,2,m_width-2,m_height-2,10).asPath, 0xFF000000, WindingRule.NonZero); 505 506 int tx = 20; 507 int ty = m_height - cast(int) (m_height - m_font.height) / 2; 508 c.drawText(tx,ty,m_text,m_font,0xFFffffff); 509 } 510 511 override void onMouse(MouseMsg msg) 512 { 513 if ((msg.event == MouseEvent.LeftDown) || 514 (msg.event == MouseEvent.LeftDblCk)) 515 { 516 if (m_onclick !is null) m_onclick(); 517 } 518 } 519 520 void setText(string txt) 521 { 522 m_text = txt; 523 } 524 525 private: 526 527 string m_text; 528 Font m_font; 529 ButtonClick m_onclick; 530 } 531 532 class Label : Widget 533 { 534 this(int x,int y, int w, int h, string text, Font f) 535 { 536 super(x,y,w,h); 537 m_text = text; 538 m_font = f; 539 } 540 541 override void onPaint(Canvas c) 542 { 543 c.draw( dg2dRoundRect(0,0,m_width,m_height,4).asPath, 0x80000000, WindingRule.NonZero); 544 c.draw( dg2dRoundRect(1,1,m_width-1,m_height-1,4).asPath, 0xFFFFFFFF, WindingRule.NonZero); 545 int tx = cast(int) (m_width - m_font.getStrWidth(m_text)) / 2; 546 int ty = m_height - cast(int) (m_height - m_font.height) / 2; 547 c.drawText(tx,ty,m_text,m_font,0xFF000000); 548 } 549 550 void setText(string text) 551 { 552 m_text = text; 553 } 554 555 private: 556 557 string m_text; 558 Font m_font; 559 }