/win32_steps.d

http://github.com/AndrejMitrovic/cairoDSamples · D · 634 lines · 477 code · 116 blank · 41 comment · 19 complexity · fa06aef1bf4905382117d8096371c6b3 MD5 · raw file

  1. module win32_steps;
  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. /+
  9. + A prototype step-sequencer Widget example. You can press and hold
  10. + the left mouse button and drag the mouse to activate multiple steps.
  11. + If you first clicked on an active step, the mode is changed to
  12. + deactivating, so the steps you drag over will be deactivated.
  13. +/
  14. import core.memory;
  15. import core.runtime;
  16. import core.thread;
  17. import core.stdc.config;
  18. import std.algorithm;
  19. import std.array;
  20. import std.conv;
  21. import std.exception;
  22. import std.functional;
  23. import std.math;
  24. import std.random;
  25. import std.range;
  26. import std.stdio;
  27. import std.string;
  28. import std.traits;
  29. import std.utf;
  30. pragma(lib, "gdi32.lib");
  31. import win32.windef;
  32. import win32.winuser;
  33. import win32.wingdi;
  34. alias std.algorithm.min min;
  35. alias std.algorithm.max max;
  36. import cairo.cairo;
  37. import cairo.win32;
  38. alias cairo.cairo.RGB RGB;
  39. struct StateContext
  40. {
  41. Context ctx;
  42. this(Context ctx)
  43. {
  44. this.ctx = ctx;
  45. ctx.save();
  46. }
  47. ~this()
  48. {
  49. ctx.restore();
  50. }
  51. alias ctx this;
  52. }
  53. /* Each allocation consumes 3 GDI objects. */
  54. class PaintBuffer
  55. {
  56. this(HDC localHdc, int cxClient, int cyClient)
  57. {
  58. width = cxClient;
  59. height = cyClient;
  60. hBuffer = CreateCompatibleDC(localHdc);
  61. hBitmap = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
  62. hOldBitmap = SelectObject(hBuffer, hBitmap);
  63. surf = new Win32Surface(hBuffer);
  64. ctx = Context(surf);
  65. initialized = true;
  66. }
  67. ~this()
  68. {
  69. if (initialized)
  70. {
  71. clear();
  72. }
  73. }
  74. void clear()
  75. {
  76. surf.dispose();
  77. ctx.dispose();
  78. surf.finish();
  79. SelectObject(hBuffer, hOldBitmap);
  80. DeleteObject(hBitmap);
  81. DeleteDC(hBuffer);
  82. initialized = false;
  83. }
  84. bool initialized;
  85. int width, height;
  86. HDC hBuffer;
  87. HBITMAP hBitmap;
  88. HBITMAP hOldBitmap;
  89. Context ctx;
  90. Surface surf;
  91. }
  92. abstract class Widget
  93. {
  94. Widget parent;
  95. PaintBuffer paintBuffer;
  96. PAINTSTRUCT ps;
  97. HWND hwnd;
  98. int width, height;
  99. bool needsRedraw = true;
  100. this(Widget parent, HWND hwnd, int width, int height)
  101. {
  102. this.parent = parent;
  103. this.hwnd = hwnd;
  104. this.width = width;
  105. this.height = height;
  106. }
  107. @property Size!int size()
  108. {
  109. return Size!int (width, height);
  110. }
  111. abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  112. {
  113. switch (message)
  114. {
  115. case WM_ERASEBKGND:
  116. {
  117. return 1;
  118. }
  119. case WM_PAINT:
  120. {
  121. OnPaint(hwnd, message, wParam, lParam);
  122. return 0;
  123. }
  124. case WM_SIZE:
  125. {
  126. width = LOWORD(lParam);
  127. height = HIWORD(lParam);
  128. auto localHdc = GetDC(hwnd);
  129. if (paintBuffer !is null)
  130. {
  131. paintBuffer.clear();
  132. }
  133. paintBuffer = new PaintBuffer(localHdc, width, height);
  134. ReleaseDC(hwnd, localHdc);
  135. needsRedraw = true;
  136. blit();
  137. return 0;
  138. }
  139. case WM_TIMER:
  140. {
  141. blit();
  142. return 0;
  143. }
  144. case WM_DESTROY:
  145. {
  146. // @BUG@
  147. // Not doing this here causes exceptions being thrown from within cairo
  148. // when calling surface.dispose(). I'm not sure why yet.
  149. paintBuffer.clear();
  150. return 0;
  151. }
  152. default:
  153. }
  154. return DefWindowProc(hwnd, message, wParam, lParam);
  155. }
  156. void redraw()
  157. {
  158. needsRedraw = true;
  159. blit();
  160. }
  161. void blit()
  162. {
  163. InvalidateRect(hwnd, null, true);
  164. }
  165. abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  166. abstract void draw(StateContext ctx);
  167. }
  168. // Ported from http://cairographics.org/cookbook/roundedrectangles/
  169. void DrawRoundedRect(Context ctx, int x, int y, int width, int height, int radius = 10)
  170. {
  171. // "Draw a rounded rectangle"
  172. // A****BQ
  173. // H C
  174. // * *
  175. // G D
  176. // F****E
  177. ctx.moveTo(x + radius, y); // Move to A
  178. ctx.lineTo(x + width - radius, y); // Straight line to B
  179. ctx.curveTo(x + width, y, x + width, y, x + width, y + radius); // Curve to C, Control points are both at Q
  180. ctx.lineTo(x + width, y + height - radius); // Move to D
  181. ctx.curveTo(x + width, y + height, x + width, y + height, x + width - radius, y + height); // Curve to E
  182. ctx.lineTo(x + radius, y + height); // Line to F
  183. ctx.curveTo(x, y + height, x, y + height, x, y + height - radius); // Curve to G
  184. ctx.lineTo(x, y + radius); // Line to H
  185. ctx.curveTo(x, y, x, y, x + radius, y); // Curve to A
  186. }
  187. class Step : Widget
  188. {
  189. static bool multiSelectState;
  190. Steps steps;
  191. bool _selected;
  192. RGB backColor;
  193. size_t noteIndex;
  194. @property void selected(bool state)
  195. {
  196. _selected = state;
  197. redraw();
  198. }
  199. @property bool selected()
  200. {
  201. return _selected;
  202. }
  203. this(Widget parent, size_t noteIndex, HWND hwnd, int width, int height)
  204. {
  205. this.noteIndex = noteIndex;
  206. super(parent, hwnd, width, height);
  207. this.steps = cast(Steps)parent;
  208. enforce(this.steps !is null);
  209. }
  210. void setStepState(bool state)
  211. {
  212. steps.setStepState(this, noteIndex, state);
  213. }
  214. override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  215. {
  216. switch (message)
  217. {
  218. case WM_LBUTTONDOWN:
  219. {
  220. selected = !selected;
  221. setStepState(selected);
  222. multiSelectState = selected;
  223. break;
  224. }
  225. case WM_MOUSEMOVE:
  226. {
  227. if (wParam & MK_LBUTTON)
  228. {
  229. setStepState(multiSelectState);
  230. }
  231. break;
  232. }
  233. default:
  234. }
  235. return super.process(message, wParam, lParam);
  236. }
  237. override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  238. {
  239. auto ctx = paintBuffer.ctx;
  240. auto hBuffer = paintBuffer.hBuffer;
  241. auto hdc = BeginPaint(hwnd, &ps);
  242. auto boundRect = ps.rcPaint;
  243. if (needsRedraw)
  244. {
  245. //~ writeln("drawing");
  246. draw(StateContext(ctx));
  247. needsRedraw = false;
  248. }
  249. with (boundRect)
  250. {
  251. //~ writeln("blitting");
  252. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  253. }
  254. EndPaint(hwnd, &ps);
  255. }
  256. override void draw(StateContext ctx)
  257. {
  258. ctx.rectangle(1, 1, width - 2, height - 2);
  259. ctx.setSourceRGB(0, 0, 0.8);
  260. ctx.fill();
  261. if (selected)
  262. {
  263. auto darkCyan = RGB(0, 0.6, 1);
  264. ctx.setSourceRGB(darkCyan);
  265. DrawRoundedRect(ctx, 5, 5, width - 10, height - 10, 15);
  266. ctx.fill();
  267. ctx.setSourceRGB(brightness(darkCyan, + 0.4));
  268. DrawRoundedRect(ctx, 10, 10, width - 20, height - 20, 15);
  269. ctx.fill();
  270. }
  271. }
  272. }
  273. RGB brightness(RGB rgb, double amount)
  274. {
  275. with (rgb)
  276. {
  277. if (red > 0)
  278. red = max(0, min(1.0, red + amount));
  279. if (green > 0)
  280. green = max(0, min(1.0, green + amount));
  281. if (blue > 0)
  282. blue = max(0, min(1.0, blue + amount));
  283. }
  284. return rgb;
  285. }
  286. class Steps : Widget
  287. {
  288. RGB backColor;
  289. Step[8] oldActiveStep;
  290. ubyte[0][Step][8] steps;
  291. void setStepState(Step step, size_t noteIndex, bool active)
  292. {
  293. if (active && (oldActiveStep[noteIndex] in steps[noteIndex]))
  294. {
  295. oldActiveStep[noteIndex].selected = false;
  296. }
  297. if (active)
  298. {
  299. oldActiveStep[noteIndex] = step;
  300. }
  301. step.selected = active;
  302. }
  303. // note: the main window is still not a Widget class, so parent is null
  304. this(Widget parent, HWND hwnd, int width, int height)
  305. {
  306. super(parent, hwnd, width, height);
  307. auto localHdc = GetDC(hwnd);
  308. auto stepWidth = width / 5;
  309. auto stepHeight = height / 10;
  310. foreach (noteIndex; 0 .. steps.length)
  311. foreach (vIndex; 0 .. 8)
  312. {
  313. auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
  314. WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  315. 0, 0, 0, 0,
  316. hwnd, cast(HANDLE)1, // child ID
  317. cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
  318. NULL);
  319. auto widget = new Step(this, noteIndex, hWindow, stepWidth, stepHeight);
  320. WidgetHandles[hWindow] = widget;
  321. steps[noteIndex][widget] = [];
  322. auto size = widget.size;
  323. MoveWindow(hWindow, noteIndex * stepWidth, vIndex * stepHeight, size.width, size.height, true);
  324. }
  325. }
  326. override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
  327. {
  328. return super.process(message, wParam, lParam);
  329. }
  330. override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  331. {
  332. auto ctx = paintBuffer.ctx;
  333. auto hBuffer = paintBuffer.hBuffer;
  334. auto hdc = BeginPaint(hwnd, &ps);
  335. auto boundRect = ps.rcPaint;
  336. if (needsRedraw)
  337. {
  338. //~ writeln("drawing");
  339. draw(StateContext(ctx));
  340. needsRedraw = false;
  341. }
  342. with (boundRect)
  343. {
  344. //~ writeln("blitting");
  345. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  346. }
  347. EndPaint(hwnd, &ps);
  348. }
  349. override void draw(StateContext ctx)
  350. {
  351. ctx.setSourceRGB(1, 1, 1);
  352. ctx.paint();
  353. }
  354. }
  355. /* A place to hold Widget objects. Since each window has a unique HWND,
  356. * we can use this hash type to store references to Widgets and call
  357. * their window processing methods.
  358. */
  359. Widget[HWND] WidgetHandles;
  360. /*
  361. * All Widget windows have this window procedure registered via RegisterClass(),
  362. * we use it to dispatch to the appropriate Widget window processing method.
  363. *
  364. * A similar technique is used in the DFL and DGUI libraries for all of its
  365. * windows and widgets.
  366. */
  367. extern (Windows)
  368. LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  369. {
  370. auto widget = hwnd in WidgetHandles;
  371. if (widget !is null)
  372. {
  373. return widget.process(message, wParam, lParam);
  374. }
  375. return DefWindowProc(hwnd, message, wParam, lParam);
  376. }
  377. extern (Windows)
  378. LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  379. {
  380. static PaintBuffer paintBuffer;
  381. static int width, height;
  382. static int TimerID = 16;
  383. static HMENU widgetID = cast(HMENU)0; // todo: each widget has its own HMENU ID
  384. void draw(StateContext ctx)
  385. {
  386. ctx.setSourceRGB(1, 1, 1);
  387. ctx.paint();
  388. }
  389. switch (message)
  390. {
  391. case WM_CREATE:
  392. {
  393. auto hDesk = GetDesktopWindow();
  394. RECT rc;
  395. GetClientRect(hDesk, &rc);
  396. auto localHdc = GetDC(hwnd);
  397. paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom);
  398. auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
  399. WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  400. 0, 0, 0, 0,
  401. hwnd, widgetID, // child ID
  402. cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
  403. NULL);
  404. auto widget = new Steps(null, hWindow, 400, 400);
  405. WidgetHandles[hWindow] = widget;
  406. auto size = widget.size;
  407. MoveWindow(hWindow, 0, 0, size.width, size.height, true);
  408. return 0;
  409. }
  410. case WM_LBUTTONDOWN:
  411. {
  412. SetFocus(hwnd);
  413. return 0;
  414. }
  415. case WM_SIZE:
  416. {
  417. width = LOWORD(lParam);
  418. height = HIWORD(lParam);
  419. return 0;
  420. }
  421. case WM_PAINT:
  422. {
  423. auto ctx = paintBuffer.ctx;
  424. auto hBuffer = paintBuffer.hBuffer;
  425. PAINTSTRUCT ps;
  426. auto hdc = BeginPaint(hwnd, &ps);
  427. auto boundRect = ps.rcPaint;
  428. draw(StateContext(paintBuffer.ctx));
  429. with (boundRect)
  430. {
  431. BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
  432. }
  433. EndPaint(hwnd, &ps);
  434. return 0;
  435. }
  436. case WM_TIMER:
  437. {
  438. InvalidateRect(hwnd, null, true);
  439. return 0;
  440. }
  441. case WM_MOUSEWHEEL:
  442. {
  443. return 0;
  444. }
  445. case WM_DESTROY:
  446. {
  447. PostQuitMessage(0);
  448. return 0;
  449. }
  450. default:
  451. }
  452. return DefWindowProc(hwnd, message, wParam, lParam);
  453. }
  454. string WidgetClass = "WidgetClass";
  455. int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  456. {
  457. string appName = "Step Sequencer";
  458. HWND hwnd;
  459. MSG msg;
  460. WNDCLASS wndclass;
  461. /* One class for the main window */
  462. wndclass.lpfnWndProc = &mainWinProc;
  463. wndclass.cbClsExtra = 0;
  464. wndclass.cbWndExtra = 0;
  465. wndclass.hInstance = hInstance;
  466. wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  467. wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  468. wndclass.hbrBackground = null;
  469. wndclass.lpszMenuName = NULL;
  470. wndclass.lpszClassName = appName.toUTF16z;
  471. if (!RegisterClass(&wndclass))
  472. {
  473. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  474. return 0;
  475. }
  476. /* Separate window class for Widgets. */
  477. wndclass.hbrBackground = null;
  478. wndclass.lpfnWndProc = &winDispatch;
  479. wndclass.cbWndExtra = 0;
  480. wndclass.hIcon = NULL;
  481. wndclass.lpszClassName = WidgetClass.toUTF16z;
  482. if (!RegisterClass(&wndclass))
  483. {
  484. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  485. return 0;
  486. }
  487. hwnd = CreateWindow(appName.toUTF16z, "step sequencer",
  488. WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary
  489. cast(int)(1680 / 3.3), 1050 / 3,
  490. 400, 400,
  491. NULL, NULL, hInstance, NULL);
  492. ShowWindow(hwnd, iCmdShow);
  493. UpdateWindow(hwnd);
  494. while (GetMessage(&msg, NULL, 0, 0))
  495. {
  496. TranslateMessage(&msg);
  497. DispatchMessage(&msg);
  498. }
  499. return msg.wParam;
  500. }
  501. extern (Windows)
  502. int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  503. {
  504. int result;
  505. void exceptionHandler(Throwable e) { throw e; }
  506. try
  507. {
  508. Runtime.initialize(&exceptionHandler);
  509. myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
  510. Runtime.terminate(&exceptionHandler);
  511. }
  512. catch (Throwable o)
  513. {
  514. MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
  515. result = -1;
  516. }
  517. return result;
  518. }