/win32_scrolling.d

http://github.com/AndrejMitrovic/cairoDSamples · D · 752 lines · 560 code · 146 blank · 46 comment · 23 complexity · 7179a80fe0ee68658098e63eae852a33 MD5 · raw file

  1. module win32_clipped_draw;
  2. /+
  3. + Copyright Andrej Mitrovic 2011.
  4. + Distributed under the Boost Software License, Version 1.0.
  5. + (See accompanying file LICENSE_1_0.txt or copy at
  6. + http://www.boost.org/LICENSE_1_0.txt)
  7. +/
  8. import core.memory;
  9. import core.runtime;
  10. import core.thread;
  11. import core.stdc.config;
  12. import std.algorithm;
  13. import std.array;
  14. import std.conv;
  15. import std.exception;
  16. import std.functional;
  17. import std.math;
  18. import std.random;
  19. import std.range;
  20. import std.stdio;
  21. import std.string;
  22. import std.traits;
  23. import std.utf;
  24. pragma(lib, "gdi32.lib");
  25. import win32.windef;
  26. import win32.winuser;
  27. import win32.wingdi;
  28. alias std.algorithm.min min; // conflict resolution
  29. alias std.algorithm.max max; // conflict resolution
  30. import cairo.cairo;
  31. import cairo.win32;
  32. alias cairo.cairo.RGB RGB; // conflict resolution
  33. struct StateContext
  34. {
  35. Context ctx;
  36. this(Context ctx)
  37. {
  38. this.ctx = ctx;
  39. ctx.save();
  40. }
  41. ~this()
  42. {
  43. ctx.restore();
  44. }
  45. alias ctx this;
  46. }
  47. class PaintBuffer
  48. {
  49. this(HDC localHdc, int cxClient, int cyClient)
  50. {
  51. hdc = localHdc;
  52. width = cxClient;
  53. height = cyClient;
  54. hBuffer = CreateCompatibleDC(localHdc);
  55. hBitmap = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
  56. hOldBitmap = SelectObject(hBuffer, hBitmap);
  57. surf = new Win32Surface(hBuffer);
  58. ctx = Context(surf);
  59. initialized = true;
  60. }
  61. ~this()
  62. {
  63. if (initialized)
  64. {
  65. clear();
  66. }
  67. }
  68. void clear()
  69. {
  70. ctx.dispose();
  71. surf.finish();
  72. surf.dispose();
  73. SelectObject(hBuffer, hOldBitmap);
  74. DeleteObject(hBitmap);
  75. DeleteDC(hBuffer);
  76. initialized = false;
  77. }
  78. HDC hdc;
  79. bool initialized;
  80. int width, height;
  81. HDC hBuffer;
  82. HBITMAP hBitmap;
  83. HBITMAP hOldBitmap;
  84. Context ctx;
  85. Surface surf;
  86. }
  87. abstract class Widget
  88. {
  89. Widget parent;
  90. PAINTSTRUCT ps;
  91. PaintBuffer mainPaintBuff;
  92. PaintBuffer paintBuffer;
  93. HWND hwnd;
  94. int width, height;
  95. int xOffset, yOffset;
  96. bool needsRedraw = true;
  97. bool selected;
  98. this(HWND hwnd, int width, int height)
  99. {
  100. this.hwnd = hwnd;
  101. this.width = width;
  102. this.height = height;
  103. //~ SetTimer(hwnd, 100, 1, NULL);
  104. }
  105. @property Size!int size()
  106. {
  107. return Size!int(width, height);
  108. }
  109. abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  110. {
  111. switch (message)
  112. {
  113. case WM_ERASEBKGND:
  114. {
  115. return 1;
  116. }
  117. case WM_PAINT:
  118. {
  119. OnPaint(hwnd, message, wParam, lParam);
  120. return 0;
  121. }
  122. case WM_SIZE:
  123. {
  124. width = cast(short)LOWORD(lParam);
  125. height = cast(short)HIWORD(lParam);
  126. auto localHdc = GetDC(hwnd);
  127. if (paintBuffer !is null)
  128. {
  129. paintBuffer.clear();
  130. }
  131. paintBuffer = new PaintBuffer(localHdc, width, height);
  132. ReleaseDC(hwnd, localHdc);
  133. needsRedraw = true;
  134. InvalidateRect(hwnd, null, true);
  135. return 0;
  136. }
  137. case WM_TIMER:
  138. {
  139. InvalidateRect(hwnd, null, true);
  140. return 0;
  141. }
  142. case WM_MOVE:
  143. {
  144. xOffset = cast(short)LOWORD(lParam);
  145. yOffset = cast(short)HIWORD(lParam);
  146. writeln(yOffset);
  147. return 0;
  148. }
  149. case WM_LBUTTONDOWN:
  150. {
  151. selected ^= 1; // flip
  152. needsRedraw = true;
  153. InvalidateRect(hwnd, NULL, false);
  154. //~ auto xPos = cast(short)LOWORD(lParam);
  155. //~ auto yPos = cast(short)HIWORD(lParam);
  156. //~ writefln("xPos: %s, yPos: %s", xPos, yPos);
  157. return 0;
  158. }
  159. default:
  160. }
  161. return DefWindowProc(hwnd, message, wParam, lParam);
  162. }
  163. abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  164. abstract void draw(StateContext ctx);
  165. }
  166. class TestWidget2 : Widget
  167. {
  168. this(HWND hwnd, int width, int height)
  169. {
  170. super(hwnd, width, height);
  171. }
  172. override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  173. {
  174. return super.process(message, wParam, lParam);
  175. }
  176. override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  177. {
  178. auto ctx = paintBuffer.ctx;
  179. auto hBuffer = paintBuffer.hBuffer;
  180. auto hdc = BeginPaint(hwnd, &ps);
  181. auto boundRect = ps.rcPaint;
  182. if (needsRedraw)
  183. {
  184. //~ writeln("drawing");
  185. draw(StateContext(ctx));
  186. needsRedraw = false;
  187. }
  188. with (boundRect)
  189. {
  190. //~ writeln("blitting");
  191. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  192. }
  193. EndPaint(hwnd, &ps);
  194. }
  195. override void draw(StateContext ctx)
  196. {
  197. ctx.setSourceRGB(1, 1, 1);
  198. ctx.paint();
  199. ctx.save();
  200. ctx.scale(width, height);
  201. ctx.moveTo(0, 0);
  202. ctx.rectangle(0, 0, 1, 1);
  203. ctx.setSourceRGBA(1, 1, 1, 0);
  204. ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
  205. ctx.fill();
  206. ctx.setSourceRGB(0, 0, 0);
  207. ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);
  208. auto linpat = new LinearGradient(0, 0, 1, 1);
  209. linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
  210. linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
  211. auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
  212. radpat.addColorStopRGBA(0, RGBA(0, 0, 0, 1));
  213. radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));
  214. ctx.setSource(linpat);
  215. ctx.mask(radpat);
  216. ctx.restore();
  217. if (selected)
  218. {
  219. ctx.moveTo(0, 0);
  220. ctx.setLineWidth(3);
  221. ctx.setSourceRGB(1, 0, 0);
  222. ctx.rectangle(0, 0, width, height);
  223. ctx.stroke();
  224. }
  225. }
  226. }
  227. class TestWidget : Widget
  228. {
  229. RGB backColor;
  230. this(HWND hwnd, int width, int height)
  231. {
  232. super(hwnd, width, height);
  233. this.backColor = RGB(1, 0, 0);
  234. auto localHdc = GetDC(hwnd);
  235. auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
  236. WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  237. 0, 0, 0, 0,
  238. hwnd, cast(HANDLE)1, // child ID
  239. cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
  240. NULL);
  241. auto widget = new TestWidget2(hWindow, width / 2, width / 2);
  242. WidgetHandles[hWindow] = widget;
  243. auto size = widget.size;
  244. MoveWindow(hWindow, size.width / 2, size.height / 2, size.width, size.height, true);
  245. }
  246. override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  247. {
  248. return super.process(message, wParam, lParam);
  249. }
  250. override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  251. {
  252. auto ctx = paintBuffer.ctx;
  253. auto hBuffer = paintBuffer.hBuffer;
  254. auto hdc = BeginPaint(hwnd, &ps);
  255. auto boundRect = ps.rcPaint;
  256. if (needsRedraw)
  257. {
  258. //~ writeln("drawing");
  259. draw(StateContext(ctx));
  260. needsRedraw = false;
  261. }
  262. with (boundRect)
  263. {
  264. //~ writeln("blitting");
  265. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  266. }
  267. EndPaint(hwnd, &ps);
  268. }
  269. override void draw(StateContext ctx)
  270. {
  271. ctx.save();
  272. ctx.scale(width, height);
  273. ctx.moveTo(0, 0);
  274. ctx.rectangle(0, 0, 1, 1);
  275. ctx.setSourceRGBA(1, 1, 1, 0);
  276. ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
  277. ctx.fill();
  278. ctx.setSourceRGB(0, 0, 0);
  279. ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);
  280. auto linpat = new LinearGradient(0, 0, 1, 1);
  281. linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
  282. linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
  283. auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
  284. radpat.addColorStopRGBA(0, RGBA(0, 0, 0, 1));
  285. radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));
  286. ctx.setSource(linpat);
  287. ctx.mask(radpat);
  288. ctx.moveTo(0.1, 0.5);
  289. ctx.restore();
  290. ctx.setSourceRGB(1, 1, 1);
  291. ctx.selectFontFace("Tahoma", FontSlant.CAIRO_FONT_SLANT_NORMAL, FontWeight.CAIRO_FONT_WEIGHT_NORMAL);
  292. ctx.setFontSize(20);
  293. ctx.showText("weeeeeeeeeeeeeeeeeeeeeeeeeee");
  294. if (selected)
  295. {
  296. ctx.moveTo(0, 0);
  297. ctx.setLineWidth(3);
  298. ctx.setSourceRGB(1, 0, 0);
  299. ctx.rectangle(0, 0, width, height);
  300. ctx.stroke();
  301. }
  302. }
  303. }
  304. /* A place to hold Widget objects. Since each window has a unique HWND,
  305. * we can use this hash type to store references to Widgets and call
  306. * their window processing methods.
  307. */
  308. Widget[HWND] WidgetHandles;
  309. /*
  310. * All Widget windows have this window procedure registered via RegisterClass(),
  311. * we use it to dispatch to the appropriate Widget window processing method.
  312. *
  313. * A similar technique is used in the DFL and DGUI libraries for all of its
  314. * windows and widgets.
  315. */
  316. extern (Windows)
  317. LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  318. {
  319. auto widget = hwnd in WidgetHandles;
  320. if (widget !is null)
  321. {
  322. return widget.process(message, wParam, lParam);
  323. }
  324. return DefWindowProc(hwnd, message, wParam, lParam);
  325. }
  326. extern (Windows)
  327. LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  328. {
  329. static PaintBuffer paintBuffer;
  330. static int width, height;
  331. static int TimerID = 16;
  332. // Note: this was copied from another example, most of these are not required here
  333. static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;
  334. static int iDeltaPerLine, iAccumDelta; // for mouse wheel logic
  335. int i, x, y, iVertPos, iHorzPos, iPaintStart, iPaintEnd;
  336. SCROLLINFO si;
  337. TEXTMETRIC tm;
  338. ULONG ulScrollLines; // for mouse wheel logic
  339. static HMENU widgetID = cast(HMENU)0; // todo: each widget has its own HMENU ID
  340. void draw(StateContext ctx)
  341. {
  342. ctx.setSourceRGB(0.2, 0.2, 0.2);
  343. ctx.paint();
  344. }
  345. switch (message)
  346. {
  347. case WM_CREATE:
  348. auto hdc = GetDC(hwnd);
  349. GetTextMetrics(hdc, &tm);
  350. cxChar = tm.tmAveCharWidth;
  351. cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
  352. cyChar = tm.tmHeight + tm.tmExternalLeading;
  353. ReleaseDC(hwnd, hdc);
  354. // Save the width of the three columns
  355. iMaxWidth = 40 * cxChar + 22 * cxCaps;
  356. auto hDesk = GetDesktopWindow();
  357. RECT rc;
  358. GetClientRect(hDesk, &rc);
  359. auto localHdc = GetDC(hwnd);
  360. paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom);
  361. auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
  362. WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  363. 0, 0, 0, 0,
  364. hwnd, widgetID, // child ID
  365. cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
  366. NULL);
  367. auto widget = new TestWidget(hWindow, 400, 400);
  368. WidgetHandles[hWindow] = widget;
  369. auto size = widget.size;
  370. MoveWindow(hWindow, size.width / 3, size.width / 3, size.width, size.height, true);
  371. goto case;
  372. // Fall through for mouse wheel information
  373. case WM_SETTINGCHANGE:
  374. {
  375. SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0);
  376. // ulScrollLines usually equals 3 or 0 (for no scrolling)
  377. // WHEEL_DELTA equals 120, so iDeltaPerLine will be 40
  378. if (ulScrollLines)
  379. iDeltaPerLine = WHEEL_DELTA / ulScrollLines;
  380. else
  381. iDeltaPerLine = 0;
  382. return 0;
  383. }
  384. case WM_LBUTTONDOWN:
  385. {
  386. SetFocus(hwnd);
  387. return 0;
  388. }
  389. case WM_SIZE:
  390. {
  391. width = LOWORD(lParam);
  392. height = HIWORD(lParam);
  393. enum windowLength = 100;
  394. // Set vertical scroll bar range and page size
  395. si.fMask = SIF_RANGE | SIF_PAGE;
  396. si.nMin = 0;
  397. si.nMax = windowLength;
  398. si.nPage = height / cyChar;
  399. SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
  400. // Set horizontal scroll bar range and page size
  401. si.fMask = SIF_RANGE | SIF_PAGE;
  402. si.nMin = 0;
  403. si.nMax = 2 + iMaxWidth / cxChar;
  404. si.nPage = width / cxChar;
  405. SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
  406. return 0;
  407. }
  408. case WM_VSCROLL:
  409. {
  410. // Get all the vertical scroll bar information
  411. si.fMask = SIF_ALL;
  412. GetScrollInfo(hwnd, SB_VERT, &si);
  413. // Save the position for comparison later on
  414. iVertPos = si.nPos;
  415. switch (LOWORD(wParam))
  416. {
  417. case SB_TOP:
  418. si.nPos = si.nMin;
  419. break;
  420. case SB_BOTTOM:
  421. si.nPos = si.nMax;
  422. break;
  423. case SB_LINEUP:
  424. si.nPos -= 1;
  425. break;
  426. case SB_LINEDOWN:
  427. si.nPos += 1;
  428. break;
  429. case SB_PAGEUP:
  430. si.nPos -= si.nPage;
  431. break;
  432. case SB_PAGEDOWN:
  433. si.nPos += si.nPage;
  434. break;
  435. case SB_THUMBTRACK:
  436. si.nPos = si.nTrackPos;
  437. break;
  438. default:
  439. break;
  440. }
  441. // Set the position and then retrieve it. Due to adjustments
  442. // by Windows it may not be the same as the value set.
  443. si.fMask = SIF_POS;
  444. SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
  445. GetScrollInfo(hwnd, SB_VERT, &si);
  446. // If the position has changed, scroll the window and update it
  447. if (si.nPos != iVertPos)
  448. {
  449. auto rect = RECT(0, 0, width, 400);
  450. // second to last argument is area that is scrolled
  451. //~ ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, &rect); // last arg is boundary
  452. ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, NULL);
  453. UpdateWindow(hwnd);
  454. }
  455. return 0;
  456. }
  457. case WM_HSCROLL:
  458. {
  459. // Get all the vertical scroll bar information
  460. si.fMask = SIF_ALL;
  461. // Save the position for comparison later on
  462. GetScrollInfo(hwnd, SB_HORZ, &si);
  463. iHorzPos = si.nPos;
  464. switch (LOWORD(wParam))
  465. {
  466. case SB_LINELEFT:
  467. si.nPos -= 1;
  468. break;
  469. case SB_LINERIGHT:
  470. si.nPos += 1;
  471. break;
  472. case SB_PAGELEFT:
  473. si.nPos -= si.nPage;
  474. break;
  475. case SB_PAGERIGHT:
  476. si.nPos += si.nPage;
  477. break;
  478. case SB_THUMBPOSITION:
  479. si.nPos = si.nTrackPos;
  480. break;
  481. default:
  482. break;
  483. }
  484. // Set the position and then retrieve it. Due to adjustments
  485. // by Windows it may not be the same as the value set.
  486. si.fMask = SIF_POS;
  487. SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
  488. GetScrollInfo(hwnd, SB_HORZ, &si);
  489. // If the position has changed, scroll the window
  490. if (si.nPos != iHorzPos)
  491. {
  492. ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0, NULL, NULL);
  493. }
  494. return 0;
  495. }
  496. case WM_MOUSEWHEEL:
  497. {
  498. if (iDeltaPerLine == 0) // no scroll
  499. break;
  500. iAccumDelta += cast(short) HIWORD(wParam); // 120 or -120
  501. while (iAccumDelta >= iDeltaPerLine)
  502. {
  503. SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
  504. iAccumDelta -= iDeltaPerLine;
  505. }
  506. while (iAccumDelta <= -iDeltaPerLine)
  507. {
  508. SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
  509. iAccumDelta += iDeltaPerLine;
  510. }
  511. return 0;
  512. }
  513. case WM_PAINT:
  514. {
  515. auto ctx = paintBuffer.ctx;
  516. auto hBuffer = paintBuffer.hBuffer;
  517. PAINTSTRUCT ps;
  518. auto hdc = BeginPaint(hwnd, &ps);
  519. auto boundRect = ps.rcPaint;
  520. draw(StateContext(paintBuffer.ctx));
  521. with (boundRect)
  522. {
  523. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  524. }
  525. EndPaint(hwnd, &ps);
  526. return 0;
  527. }
  528. case WM_TIMER:
  529. {
  530. InvalidateRect(hwnd, null, true);
  531. return 0;
  532. }
  533. case WM_DESTROY:
  534. {
  535. PostQuitMessage(0);
  536. return 0;
  537. }
  538. default:
  539. }
  540. return DefWindowProc(hwnd, message, wParam, lParam);
  541. }
  542. string WidgetClass = "WidgetClass";
  543. int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  544. {
  545. string appName = "layered drawing";
  546. HWND hwnd;
  547. MSG msg;
  548. WNDCLASS wndclass;
  549. /* One class for the main window */
  550. wndclass.lpfnWndProc = &mainWinProc;
  551. wndclass.cbClsExtra = 0;
  552. wndclass.cbWndExtra = 0;
  553. wndclass.hInstance = hInstance;
  554. wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  555. wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  556. wndclass.hbrBackground = null;
  557. wndclass.lpszMenuName = NULL;
  558. wndclass.lpszClassName = appName.toUTF16z;
  559. if (!RegisterClass(&wndclass))
  560. {
  561. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  562. return 0;
  563. }
  564. /* Separate window class for Widgets. */
  565. wndclass.hbrBackground = null;
  566. wndclass.lpfnWndProc = &winDispatch;
  567. wndclass.cbWndExtra = 0;
  568. wndclass.hIcon = NULL;
  569. wndclass.lpszClassName = WidgetClass.toUTF16z;
  570. if (!RegisterClass(&wndclass))
  571. {
  572. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  573. return 0;
  574. }
  575. hwnd = CreateWindow(appName.toUTF16z, "layered drawing",
  576. WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  577. CW_USEDEFAULT, CW_USEDEFAULT,
  578. CW_USEDEFAULT, CW_USEDEFAULT,
  579. NULL, NULL, hInstance, NULL);
  580. ShowWindow(hwnd, iCmdShow);
  581. UpdateWindow(hwnd);
  582. while (GetMessage(&msg, NULL, 0, 0))
  583. {
  584. TranslateMessage(&msg);
  585. DispatchMessage(&msg);
  586. }
  587. return msg.wParam;
  588. }
  589. extern (Windows)
  590. int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  591. {
  592. int result;
  593. void exceptionHandler(Throwable e) { throw e; }
  594. try
  595. {
  596. Runtime.initialize(&exceptionHandler);
  597. myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
  598. Runtime.terminate(&exceptionHandler);
  599. }
  600. catch (Throwable o)
  601. {
  602. MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
  603. result = -1;
  604. }
  605. return result;
  606. }