DemosCanvas.mod 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. IMPLEMENTATION MODULE DemosCanvas ;
  2. (*
  3. m2-GTK4 - the drawing/canvas gtk-demo demos:
  4. drawingarea.c -> DoDrawingArea
  5. gestures.c -> DoGestures
  6. mask.c -> DoMask
  7. snapping.c -> DoSnapping
  8. The gtk-demo contract is: on the first call build the window and store
  9. it in a module-level variable; on later calls toggle the window's
  10. visibility, destroying it (and clearing the variable) when it is
  11. already visible. Return the window, or NIL once destroyed.
  12. Only bindings under src/ are used, so a few C facilities are missing and
  13. those spots are simplified (each marked "SIMPLIFIED" below):
  14. * gtk_widget_queue_draw() is not bound. Gesture/scribble state is
  15. still recorded, but the drawing areas cannot be repainted until GTK
  16. redraws them for another reason (e.g. an expose or resize).
  17. * drawingarea's groups_draw uses cairo_surface_create_similar(),
  18. cairo_get_target() and CAIRO_OPERATOR_DEST_OUT/ADD, none of which
  19. are bound; the knockout/composite is approximated by a black disc
  20. with the three coloured sub-discs drawn on top.
  21. * cairo_image_surface_get_width()/get_height() are not bound, so the
  22. scribble surface is (re)created on the "resize" signal only.
  23. * gtk_window_set_display(), the accessible-role/an accessible-relation
  24. setup and g_object_new(drawing area) are not bound; a plain
  25. gtk_drawing_area_new() is used.
  26. * gestures' rotate/zoom visualisation reads the gesture deltas from
  27. the signal arguments instead of gtk_gesture_*_get_*_delta(), and uses
  28. the widget centre instead of gtk_gesture_get_bounding_box_center();
  29. the touchpad 3-finger swipe and gtk_gesture_is_recognized() are not
  30. reproduced.
  31. * mask's Demo4Widget is a custom GskMaskNode widget with a Pango
  32. layout and a frame-clock tick callback; subclassing, GskMaskNode and
  33. gtk_widget_add_tick_callback() are not bound, so the "123" text is
  34. filled with a moving 8-stop gradient via Cairo instead.
  35. * snapping's GtkTiler is a custom GdkPaintable; a GtkDrawingArea is
  36. used instead, drawing the QR code on a pixel-snapped 43x43 grid with
  37. linear gradients between differing neighbours (the conic/radial
  38. corner cases are left white).
  39. *)
  40. FROM SYSTEM IMPORT ADDRESS, ADR;
  41. FROM GtkWindow IMPORT gtk_window_new, gtk_window_set_title,
  42. gtk_window_set_default_size, gtk_window_set_child, gtk_window_destroy;
  43. FROM GtkWidget IMPORT gtk_widget_set_visible, gtk_widget_get_visible,
  44. gtk_widget_set_margin_start, gtk_widget_set_margin_end,
  45. gtk_widget_set_margin_top, gtk_widget_set_margin_bottom,
  46. gtk_widget_set_vexpand, gtk_widget_set_hexpand,
  47. gtk_widget_add_css_class, gtk_widget_get_width, gtk_widget_get_height,
  48. gtk_widget_add_controller;
  49. FROM GtkEnums IMPORT GtkVertical, GtkHorizontal;
  50. FROM GtkBox IMPORT gtk_box_new, gtk_box_append;
  51. FROM GtkLabel IMPORT gtk_label_new;
  52. FROM GtkFrame IMPORT gtk_frame_new, gtk_frame_set_child;
  53. FROM GtkDrawingArea IMPORT gtk_drawing_area_new,
  54. gtk_drawing_area_set_content_width, gtk_drawing_area_set_content_height,
  55. gtk_drawing_area_set_draw_func;
  56. FROM GtkGestures IMPORT gtk_gesture_drag_new, gtk_gesture_single_set_button,
  57. gtk_gesture_swipe_new, gtk_gesture_long_press_new,
  58. gtk_gesture_rotate_new, gtk_gesture_zoom_new;
  59. FROM GtkEventController IMPORT gtk_event_controller_set_propagation_phase,
  60. GtkPhaseBubble;
  61. FROM GtkScale IMPORT gtk_scale_new_with_range;
  62. FROM GtkRange IMPORT gtk_range_set_value, gtk_range_get_value;
  63. FROM GObject IMPORT g_signal_connect_data, GConnectDefault;
  64. FROM GtkClosures IMPORT Connect2;
  65. FROM Cairo IMPORT
  66. cairo_create, cairo_destroy, cairo_set_source_rgb, cairo_paint,
  67. cairo_set_source_surface, cairo_move_to, cairo_line_to,
  68. cairo_set_line_width, cairo_set_line_cap, cairo_stroke,
  69. cairo_save, cairo_restore, cairo_translate, cairo_scale, cairo_rotate,
  70. cairo_arc,
  71. cairo_close_path, cairo_rectangle, cairo_fill, cairo_set_source_rgba,
  72. cairo_surface_destroy, cairo_image_surface_create,
  73. cairo_pattern_create_linear, cairo_pattern_add_color_stop_rgb,
  74. cairo_set_source, cairo_pattern_destroy, cairo_select_font_face,
  75. cairo_set_font_size, cairo_text_path,
  76. CairoFormatARGB32, CairoLineCapRound,
  77. CairoFontSlantNormal, CairoFontWeightBold;
  78. CONST
  79. Pi = 3.14159265358979;
  80. VAR
  81. (* drawingarea *)
  82. drawingWindow, drawSurface: ADDRESS;
  83. dragStartX, dragStartY: REAL;
  84. (* gestures *)
  85. gesturesWindow: ADDRESS;
  86. swipeX, swipeY, rotateDelta, zoomScale: REAL;
  87. longPressed, hasRotate, hasZoom: BOOLEAN;
  88. (* mask *)
  89. maskWindow, maskScale: ADDRESS;
  90. maskProgress: REAL;
  91. (* snapping *)
  92. snappingWindow: ADDRESS;
  93. qrData: ARRAY [0..20] OF ARRAY [0..20] OF CHAR;
  94. (* ------------------------------------------------------------------ *)
  95. (* small helpers *)
  96. (* ------------------------------------------------------------------ *)
  97. PROCEDURE IMin (a, b: INTEGER) : INTEGER;
  98. BEGIN
  99. IF a < b THEN RETURN a ELSE RETURN b END
  100. END IMin;
  101. (* The 21x21 QR matrix of snapping.c, one row per string. *)
  102. PROCEDURE InitQrData;
  103. BEGIN
  104. qrData[0] := "000000010111110000000";
  105. qrData[1] := "011111011100110111110";
  106. qrData[2] := "010001011010010100010";
  107. qrData[3] := "010001010011110100010";
  108. qrData[4] := "010001010001010100010";
  109. qrData[5] := "011111010100110111110";
  110. qrData[6] := "000000010101010000000";
  111. qrData[7] := "111111110000011111111";
  112. qrData[8] := "011101000000100000110";
  113. qrData[9] := "111100100000011100011";
  114. qrData[10] := "110010010110110001001";
  115. qrData[11] := "111001100101101111100";
  116. qrData[12] := "101100000011001100110";
  117. qrData[13] := "111111110011011100011";
  118. qrData[14] := "000000010011000101001";
  119. qrData[15] := "011111011000001000100";
  120. qrData[16] := "010001010010111011101";
  121. qrData[17] := "010001011001000000100";
  122. qrData[18] := "010001011010110100011";
  123. qrData[19] := "011111011001001111111";
  124. qrData[20] := "000000010100001011110"
  125. END InitQrData;
  126. (* ------------------------------------------------------------------ *)
  127. (* drawing area *)
  128. (* ------------------------------------------------------------------ *)
  129. PROCEDURE CreateSurface (widget: ADDRESS);
  130. VAR
  131. cr: ADDRESS;
  132. BEGIN
  133. IF drawSurface # NIL THEN
  134. cairo_surface_destroy(drawSurface)
  135. END;
  136. drawSurface := cairo_image_surface_create(CairoFormatARGB32,
  137. gtk_widget_get_width(widget),
  138. gtk_widget_get_height(widget));
  139. cr := cairo_create(drawSurface);
  140. cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
  141. cairo_paint(cr);
  142. cairo_destroy(cr)
  143. END CreateSurface;
  144. (* void scribble_resize(GtkWidget *widget, int width, int height, gpointer) *)
  145. PROCEDURE ScribbleResize (widget: ADDRESS; width, height: INTEGER;
  146. data: ADDRESS);
  147. BEGIN
  148. CreateSurface(widget)
  149. END ScribbleResize;
  150. PROCEDURE ScribbleDraw (area: ADDRESS; cr: ADDRESS;
  151. width, height: INTEGER; data: ADDRESS);
  152. BEGIN
  153. IF drawSurface # NIL THEN
  154. cairo_set_source_surface(cr, drawSurface, 0.0, 0.0);
  155. cairo_paint(cr)
  156. END
  157. END ScribbleDraw;
  158. PROCEDURE DrawBrush (widget: ADDRESS; x, y: REAL);
  159. VAR
  160. cr: ADDRESS;
  161. BEGIN
  162. IF drawSurface = NIL THEN
  163. CreateSurface(widget)
  164. END;
  165. cr := cairo_create(drawSurface);
  166. cairo_move_to(cr, x, y);
  167. cairo_line_to(cr, x, y);
  168. cairo_set_line_width(cr, 6.0);
  169. cairo_set_line_cap(cr, CairoLineCapRound);
  170. cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
  171. cairo_stroke(cr);
  172. cairo_destroy(cr)
  173. (* gtk_widget_queue_draw(widget) is not bound (see header). *)
  174. END DrawBrush;
  175. PROCEDURE DragBegin (gesture: ADDRESS; x, y: REAL; area: ADDRESS);
  176. BEGIN
  177. dragStartX := x;
  178. dragStartY := y;
  179. DrawBrush(area, x, y)
  180. END DragBegin;
  181. PROCEDURE DragUpdate (gesture: ADDRESS; x, y: REAL; area: ADDRESS);
  182. BEGIN
  183. DrawBrush(area, dragStartX + x, dragStartY + y)
  184. END DragUpdate;
  185. PROCEDURE DragEnd (gesture: ADDRESS; x, y: REAL; area: ADDRESS);
  186. BEGIN
  187. DrawBrush(area, dragStartX + x, dragStartY + y)
  188. END DragEnd;
  189. PROCEDURE OvalPath (cr: ADDRESS; xc, yc, xr, yr: REAL);
  190. BEGIN
  191. cairo_save(cr);
  192. cairo_translate(cr, xc, yc);
  193. cairo_scale(cr, 1.0, yr / xr);
  194. cairo_move_to(cr, xr, 0.0);
  195. cairo_arc(cr, 0.0, 0.0, xr, 0.0, 2.0 * Pi);
  196. cairo_close_path(cr);
  197. cairo_restore(cr)
  198. END OvalPath;
  199. PROCEDURE FillChecks (cr: ADDRESS; x, y, width, height: INTEGER);
  200. CONST
  201. CheckSize = 16;
  202. VAR
  203. i, j: INTEGER;
  204. BEGIN
  205. cairo_rectangle(cr, VAL(REAL, x), VAL(REAL, y),
  206. VAL(REAL, width), VAL(REAL, height));
  207. cairo_set_source_rgb(cr, 0.4, 0.4, 0.4);
  208. cairo_fill(cr);
  209. (* x & (-CHECK_SIZE), valid for CHECK_SIZE a power of two. *)
  210. j := x - (x MOD CheckSize);
  211. WHILE j < height DO
  212. i := y - (y MOD CheckSize);
  213. WHILE i < width DO
  214. IF ((i DIV CheckSize) + (j DIV CheckSize)) MOD 2 = 0 THEN
  215. cairo_rectangle(cr, VAL(REAL, i), VAL(REAL, j),
  216. VAL(REAL, CheckSize), VAL(REAL, CheckSize))
  217. END;
  218. INC(i, CheckSize)
  219. END;
  220. INC(j, CheckSize)
  221. END;
  222. cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
  223. cairo_fill(cr)
  224. END FillChecks;
  225. PROCEDURE Draw3Circles (cr: ADDRESS; xc, yc, radius, alpha: REAL);
  226. CONST
  227. SubRadius = 0.56666667; (* 2/3 - 0.1 *)
  228. Third = 0.33333333;
  229. Cos30 = 0.86602540;
  230. VAR
  231. sr: REAL;
  232. BEGIN
  233. sr := radius * SubRadius;
  234. cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, alpha);
  235. OvalPath(cr, xc, yc - radius * Third, sr, sr);
  236. cairo_fill(cr);
  237. cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, alpha);
  238. OvalPath(cr, xc - radius * Third * Cos30,
  239. yc + radius * Third * 0.5, sr, sr);
  240. cairo_fill(cr);
  241. cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, alpha);
  242. OvalPath(cr, xc + radius * Third * Cos30,
  243. yc + radius * Third * 0.5, sr, sr);
  244. cairo_fill(cr)
  245. END Draw3Circles;
  246. PROCEDURE GroupsDraw (area: ADDRESS; cr: ADDRESS;
  247. width, height: INTEGER; data: ADDRESS);
  248. VAR
  249. radius, xc, yc: REAL;
  250. BEGIN
  251. radius := 0.5 * VAL(REAL, IMin(width, height)) - 10.0;
  252. xc := VAL(REAL, width) / 2.0;
  253. yc := VAL(REAL, height) / 2.0;
  254. FillChecks(cr, 0, 0, width, height);
  255. (* SIMPLIFIED: no similar surfaces / DEST_OUT / ADD. *)
  256. cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
  257. OvalPath(cr, xc, yc, radius, radius);
  258. cairo_fill(cr);
  259. Draw3Circles(cr, xc, yc, radius, 1.0)
  260. END GroupsDraw;
  261. PROCEDURE OnDrawingDestroy (widget: ADDRESS; data: ADDRESS);
  262. BEGIN
  263. IF drawSurface # NIL THEN
  264. cairo_surface_destroy(drawSurface);
  265. drawSurface := NIL
  266. END
  267. END OnDrawingDestroy;
  268. PROCEDURE DoDrawingArea (doWidget: ADDRESS) : ADDRESS;
  269. VAR
  270. vbox, da, label, frame, drag: ADDRESS;
  271. BEGIN
  272. IF drawingWindow = NIL THEN
  273. drawingWindow := gtk_window_new();
  274. gtk_window_set_title(drawingWindow, "Drawing Area");
  275. gtk_window_set_default_size(drawingWindow, 250, -1);
  276. Connect2(drawingWindow, "destroy", OnDrawingDestroy, NIL);
  277. vbox := gtk_box_new(GtkVertical, 8);
  278. gtk_widget_set_margin_start(vbox, 16);
  279. gtk_widget_set_margin_end(vbox, 16);
  280. gtk_widget_set_margin_top(vbox, 16);
  281. gtk_widget_set_margin_bottom(vbox, 16);
  282. gtk_window_set_child(drawingWindow, vbox);
  283. (* groups area *)
  284. label := gtk_label_new("Knockout groups");
  285. gtk_widget_add_css_class(label, "heading");
  286. gtk_box_append(vbox, label);
  287. frame := gtk_frame_new("");
  288. gtk_widget_set_vexpand(frame, 1);
  289. gtk_box_append(vbox, frame);
  290. da := gtk_drawing_area_new();
  291. gtk_frame_set_child(frame, da);
  292. gtk_drawing_area_set_content_width(da, 100);
  293. gtk_drawing_area_set_content_height(da, 100);
  294. gtk_drawing_area_set_draw_func(da, ADR(GroupsDraw), NIL, NIL);
  295. (* scribble area *)
  296. label := gtk_label_new("Scribble area");
  297. gtk_widget_add_css_class(label, "heading");
  298. gtk_box_append(vbox, label);
  299. frame := gtk_frame_new("");
  300. gtk_widget_set_vexpand(frame, 1);
  301. gtk_box_append(vbox, frame);
  302. da := gtk_drawing_area_new();
  303. gtk_frame_set_child(frame, da);
  304. gtk_drawing_area_set_content_width(da, 100);
  305. gtk_drawing_area_set_content_height(da, 100);
  306. gtk_drawing_area_set_draw_func(da, ADR(ScribbleDraw), NIL, NIL);
  307. g_signal_connect_data(da, "resize", ADR(ScribbleResize), NIL, NIL,
  308. GConnectDefault);
  309. drag := gtk_gesture_drag_new();
  310. gtk_gesture_single_set_button(drag, 0);
  311. gtk_widget_add_controller(da, drag);
  312. g_signal_connect_data(drag, "drag-begin", ADR(DragBegin), da, NIL,
  313. GConnectDefault);
  314. g_signal_connect_data(drag, "drag-update", ADR(DragUpdate), da, NIL,
  315. GConnectDefault);
  316. g_signal_connect_data(drag, "drag-end", ADR(DragEnd), da, NIL,
  317. GConnectDefault)
  318. END;
  319. IF gtk_widget_get_visible(drawingWindow) = 0 THEN
  320. gtk_widget_set_visible(drawingWindow, 1)
  321. ELSE
  322. gtk_window_destroy(drawingWindow);
  323. drawingWindow := NIL
  324. END;
  325. RETURN drawingWindow
  326. END DoDrawingArea;
  327. (* ------------------------------------------------------------------ *)
  328. (* gestures *)
  329. (* ------------------------------------------------------------------ *)
  330. PROCEDURE SwipeSwept (gesture: ADDRESS; vx, vy: REAL; widget: ADDRESS);
  331. BEGIN
  332. swipeX := vx / 10.0;
  333. swipeY := vy / 10.0
  334. END SwipeSwept;
  335. PROCEDURE LongPressPressed (gesture: ADDRESS; x, y: REAL; widget: ADDRESS);
  336. BEGIN
  337. longPressed := TRUE
  338. END LongPressPressed;
  339. PROCEDURE LongPressEnd (gesture: ADDRESS; sequence: ADDRESS; widget: ADDRESS);
  340. BEGIN
  341. longPressed := FALSE
  342. END LongPressEnd;
  343. PROCEDURE RotationChanged (gesture: ADDRESS; angle, delta: REAL;
  344. widget: ADDRESS);
  345. BEGIN
  346. rotateDelta := delta;
  347. hasRotate := TRUE
  348. END RotationChanged;
  349. PROCEDURE ZoomChanged (gesture: ADDRESS; scale: REAL; widget: ADDRESS);
  350. BEGIN
  351. zoomScale := scale;
  352. hasZoom := TRUE
  353. END ZoomChanged;
  354. PROCEDURE GesturesDraw (area: ADDRESS; cr: ADDRESS;
  355. width, height: INTEGER; data: ADDRESS);
  356. VAR
  357. pat: ADDRESS;
  358. cx, cy: REAL;
  359. BEGIN
  360. cx := VAL(REAL, width) / 2.0;
  361. cy := VAL(REAL, height) / 2.0;
  362. IF (swipeX # 0.0) OR (swipeY # 0.0) THEN
  363. cairo_save(cr);
  364. cairo_set_line_width(cr, 6.0);
  365. cairo_move_to(cr, cx, cy);
  366. cairo_line_to(cr, cx + swipeX, cy + swipeY);
  367. cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.5);
  368. cairo_stroke(cr);
  369. cairo_restore(cr)
  370. END;
  371. IF hasRotate OR hasZoom THEN
  372. pat := cairo_pattern_create_linear(-100.0, 0.0, 200.0, 0.0);
  373. cairo_pattern_add_color_stop_rgb(pat, 0.0, 0.0, 0.0, 1.0);
  374. cairo_pattern_add_color_stop_rgb(pat, 1.0, 1.0, 0.0, 0.0);
  375. cairo_save(cr);
  376. cairo_translate(cr, cx, cy);
  377. IF hasZoom AND (zoomScale > 0.0) THEN
  378. cairo_scale(cr, zoomScale, zoomScale)
  379. END;
  380. IF hasRotate THEN
  381. cairo_rotate(cr, rotateDelta)
  382. END;
  383. cairo_rectangle(cr, -100.0, -100.0, 200.0, 200.0);
  384. cairo_set_source(cr, pat);
  385. cairo_fill(cr);
  386. cairo_restore(cr);
  387. cairo_pattern_destroy(pat)
  388. END;
  389. IF longPressed THEN
  390. cairo_save(cr);
  391. cairo_arc(cr, cx, cy, 50.0, 0.0, 2.0 * Pi);
  392. cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.5);
  393. cairo_stroke(cr);
  394. cairo_restore(cr)
  395. END
  396. END GesturesDraw;
  397. PROCEDURE DoGestures (doWidget: ADDRESS) : ADDRESS;
  398. VAR
  399. da, gesture: ADDRESS;
  400. BEGIN
  401. IF gesturesWindow = NIL THEN
  402. gesturesWindow := gtk_window_new();
  403. gtk_window_set_title(gesturesWindow, "Gestures");
  404. gtk_window_set_default_size(gesturesWindow, 400, 400);
  405. da := gtk_drawing_area_new();
  406. gtk_window_set_child(gesturesWindow, da);
  407. gtk_drawing_area_set_draw_func(da, ADR(GesturesDraw), NIL, NIL);
  408. (* swipe *)
  409. gesture := gtk_gesture_swipe_new();
  410. g_signal_connect_data(gesture, "swipe", ADR(SwipeSwept), da, NIL,
  411. GConnectDefault);
  412. gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble);
  413. gtk_widget_add_controller(da, gesture);
  414. (* long press *)
  415. gesture := gtk_gesture_long_press_new();
  416. g_signal_connect_data(gesture, "pressed", ADR(LongPressPressed), da,
  417. NIL, GConnectDefault);
  418. g_signal_connect_data(gesture, "end", ADR(LongPressEnd), da, NIL,
  419. GConnectDefault);
  420. gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble);
  421. gtk_widget_add_controller(da, gesture);
  422. (* rotate *)
  423. gesture := gtk_gesture_rotate_new();
  424. g_signal_connect_data(gesture, "angle-changed", ADR(RotationChanged),
  425. da, NIL, GConnectDefault);
  426. gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble);
  427. gtk_widget_add_controller(da, gesture);
  428. (* zoom *)
  429. gesture := gtk_gesture_zoom_new();
  430. g_signal_connect_data(gesture, "scale-changed", ADR(ZoomChanged), da,
  431. NIL, GConnectDefault);
  432. gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble);
  433. gtk_widget_add_controller(da, gesture)
  434. END;
  435. IF gtk_widget_get_visible(gesturesWindow) = 0 THEN
  436. gtk_widget_set_visible(gesturesWindow, 1)
  437. ELSE
  438. gtk_window_destroy(gesturesWindow);
  439. gesturesWindow := NIL
  440. END;
  441. RETURN gesturesWindow
  442. END DoGestures;
  443. (* ------------------------------------------------------------------ *)
  444. (* masking *)
  445. (* ------------------------------------------------------------------ *)
  446. PROCEDURE OnMaskChanged (range: ADDRESS; data: ADDRESS);
  447. BEGIN
  448. maskProgress := gtk_range_get_value(range)
  449. END OnMaskChanged;
  450. PROCEDURE MaskDraw (area: ADDRESS; cr: ADDRESS;
  451. width, height: INTEGER; data: ADDRESS);
  452. VAR
  453. pat: ADDRESS;
  454. w, h, x0, y0: REAL;
  455. BEGIN
  456. w := VAL(REAL, width);
  457. h := VAL(REAL, height);
  458. cairo_set_source_rgb(cr, 0.08, 0.08, 0.10);
  459. cairo_rectangle(cr, 0.0, 0.0, w, h);
  460. cairo_fill(cr);
  461. (* SIMPLIFIED: fill the "123" glyphs with a moving gradient in place of
  462. the GskMaskNode composition. *)
  463. cairo_select_font_face(cr, "sans", CairoFontSlantNormal,
  464. CairoFontWeightBold);
  465. cairo_set_font_size(cr, 200.0);
  466. x0 := w / 2.0 - 165.0;
  467. y0 := h / 2.0 + 70.0;
  468. cairo_move_to(cr, x0, y0);
  469. cairo_text_path(cr, "123");
  470. pat := cairo_pattern_create_linear(maskProgress * w - w, 0.0,
  471. maskProgress * w, h);
  472. cairo_pattern_add_color_stop_rgb(pat, 0.0, 1.0, 0.0, 0.0);
  473. cairo_pattern_add_color_stop_rgb(pat, 1.0 / 7.0, 1.0, 0.5, 0.0);
  474. cairo_pattern_add_color_stop_rgb(pat, 2.0 / 7.0, 1.0, 1.0, 0.0);
  475. cairo_pattern_add_color_stop_rgb(pat, 3.0 / 7.0, 0.0, 1.0, 0.0);
  476. cairo_pattern_add_color_stop_rgb(pat, 4.0 / 7.0, 0.0, 1.0, 1.0);
  477. cairo_pattern_add_color_stop_rgb(pat, 5.0 / 7.0, 0.0, 0.0, 1.0);
  478. cairo_pattern_add_color_stop_rgb(pat, 6.0 / 7.0, 0.6, 0.0, 1.0);
  479. cairo_pattern_add_color_stop_rgb(pat, 1.0, 1.0, 0.0, 1.0);
  480. cairo_set_source(cr, pat);
  481. cairo_fill(cr);
  482. cairo_pattern_destroy(pat)
  483. END MaskDraw;
  484. PROCEDURE DoMask (doWidget: ADDRESS) : ADDRESS;
  485. VAR
  486. box, da: ADDRESS;
  487. BEGIN
  488. IF maskWindow = NIL THEN
  489. maskWindow := gtk_window_new();
  490. gtk_window_set_title(maskWindow, "Mask Nodes");
  491. gtk_window_set_default_size(maskWindow, 600, 400);
  492. box := gtk_box_new(GtkVertical, 0);
  493. gtk_window_set_child(maskWindow, box);
  494. da := gtk_drawing_area_new();
  495. gtk_widget_set_hexpand(da, 1);
  496. gtk_widget_set_vexpand(da, 1);
  497. gtk_drawing_area_set_draw_func(da, ADR(MaskDraw), NIL, NIL);
  498. gtk_box_append(box, da);
  499. maskScale := gtk_scale_new_with_range(GtkHorizontal, 0.0, 1.0, 0.1);
  500. gtk_range_set_value(maskScale, 0.5);
  501. maskProgress := 0.5;
  502. Connect2(maskScale, "value-changed", OnMaskChanged, NIL);
  503. gtk_box_append(box, maskScale)
  504. END;
  505. IF gtk_widget_get_visible(maskWindow) = 0 THEN
  506. gtk_widget_set_visible(maskWindow, 1)
  507. ELSE
  508. gtk_window_destroy(maskWindow);
  509. maskWindow := NIL
  510. END;
  511. RETURN maskWindow
  512. END DoMask;
  513. (* ------------------------------------------------------------------ *)
  514. (* snapping *)
  515. (* ------------------------------------------------------------------ *)
  516. PROCEDURE QrOn (x, y: INTEGER) : BOOLEAN;
  517. BEGIN
  518. RETURN qrData[y][x] = '1'
  519. END QrOn;
  520. PROCEDURE SetOnColor (cr: ADDRESS; on: BOOLEAN);
  521. BEGIN
  522. IF on THEN
  523. cairo_set_source_rgb(cr, 0.2, 0.6, 1.0) (* ON *)
  524. ELSE
  525. cairo_set_source_rgb(cr, 1.0, 0.95, 0.7) (* OFF *)
  526. END
  527. END SetOnColor;
  528. (* A 1x1 tile with a horizontal gradient between differing neighbours. *)
  529. PROCEDURE HBetween (cr: ADDRESS; x, y: REAL; a, b: BOOLEAN);
  530. VAR
  531. pat: ADDRESS;
  532. BEGIN
  533. IF a = b THEN
  534. SetOnColor(cr, a)
  535. ELSE
  536. pat := cairo_pattern_create_linear(x, y, x + 1.0, y);
  537. IF a THEN
  538. cairo_pattern_add_color_stop_rgb(pat, 0.3, 0.2, 0.6, 1.0);
  539. cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0);
  540. cairo_pattern_add_color_stop_rgb(pat, 0.7, 1.0, 0.95, 0.7)
  541. ELSE
  542. cairo_pattern_add_color_stop_rgb(pat, 0.3, 1.0, 0.95, 0.7);
  543. cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0);
  544. cairo_pattern_add_color_stop_rgb(pat, 0.7, 0.2, 0.6, 1.0)
  545. END;
  546. cairo_set_source(cr, pat)
  547. END;
  548. cairo_rectangle(cr, x, y, 1.0, 1.0);
  549. cairo_fill(cr);
  550. IF a # b THEN
  551. cairo_pattern_destroy(pat)
  552. END
  553. END HBetween;
  554. (* A 1x1 tile with a vertical gradient between differing neighbours. *)
  555. PROCEDURE VBetween (cr: ADDRESS; x, y: REAL; a, b: BOOLEAN);
  556. VAR
  557. pat: ADDRESS;
  558. BEGIN
  559. IF a = b THEN
  560. SetOnColor(cr, a)
  561. ELSE
  562. pat := cairo_pattern_create_linear(x, y, x, y + 1.0);
  563. IF a THEN
  564. cairo_pattern_add_color_stop_rgb(pat, 0.3, 0.2, 0.6, 1.0);
  565. cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0);
  566. cairo_pattern_add_color_stop_rgb(pat, 0.7, 1.0, 0.95, 0.7)
  567. ELSE
  568. cairo_pattern_add_color_stop_rgb(pat, 0.3, 1.0, 0.95, 0.7);
  569. cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0);
  570. cairo_pattern_add_color_stop_rgb(pat, 0.7, 0.2, 0.6, 1.0)
  571. END;
  572. cairo_set_source(cr, pat)
  573. END;
  574. cairo_rectangle(cr, x, y, 1.0, 1.0);
  575. cairo_fill(cr);
  576. IF a # b THEN
  577. cairo_pattern_destroy(pat)
  578. END
  579. END VBetween;
  580. PROCEDURE SnappingDraw (area: ADDRESS; cr: ADDRESS;
  581. width, height: INTEGER; data: ADDRESS);
  582. VAR
  583. cell, ox, oy, x, y: INTEGER;
  584. BEGIN
  585. (* 21 tiles plus 21 gaps make 43 units; snap to whole device pixels. *)
  586. cell := IMin(width, height) DIV 43;
  587. IF cell < 1 THEN cell := 1 END;
  588. ox := (width - 43 * cell) DIV 2;
  589. oy := (height - 43 * cell) DIV 2;
  590. cairo_save(cr);
  591. cairo_translate(cr, VAL(REAL, ox), VAL(REAL, oy));
  592. cairo_scale(cr, VAL(REAL, cell), VAL(REAL, cell));
  593. cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
  594. cairo_rectangle(cr, 0.0, 0.0, 43.0, 43.0);
  595. cairo_fill(cr);
  596. (* tile centres at odd coordinates 1, 3, ..., 41 *)
  597. FOR y := 0 TO 20 DO
  598. FOR x := 0 TO 20 DO
  599. SetOnColor(cr, QrOn(x, y));
  600. cairo_rectangle(cr, VAL(REAL, 2 * x + 1), VAL(REAL, 2 * y + 1),
  601. 1.0, 1.0);
  602. cairo_fill(cr)
  603. END
  604. END;
  605. (* horizontal gaps between columns *)
  606. FOR y := 0 TO 20 DO
  607. FOR x := 0 TO 19 DO
  608. HBetween(cr, VAL(REAL, 2 * x + 2), VAL(REAL, 2 * y + 1),
  609. QrOn(x, y), QrOn(x + 1, y))
  610. END
  611. END;
  612. (* vertical gaps between rows *)
  613. FOR y := 0 TO 19 DO
  614. FOR x := 0 TO 20 DO
  615. VBetween(cr, VAL(REAL, 2 * x + 1), VAL(REAL, 2 * y + 2),
  616. QrOn(x, y), QrOn(x, y + 1))
  617. END
  618. END;
  619. cairo_restore(cr)
  620. END SnappingDraw;
  621. PROCEDURE DoSnapping (doWidget: ADDRESS) : ADDRESS;
  622. VAR
  623. da: ADDRESS;
  624. BEGIN
  625. IF snappingWindow = NIL THEN
  626. snappingWindow := gtk_window_new();
  627. gtk_window_set_title(snappingWindow, "Snapping");
  628. gtk_window_set_default_size(snappingWindow, 600, 400);
  629. da := gtk_drawing_area_new();
  630. gtk_window_set_child(snappingWindow, da);
  631. gtk_drawing_area_set_draw_func(da, ADR(SnappingDraw), NIL, NIL)
  632. END;
  633. IF gtk_widget_get_visible(snappingWindow) = 0 THEN
  634. gtk_widget_set_visible(snappingWindow, 1)
  635. ELSE
  636. gtk_window_destroy(snappingWindow);
  637. snappingWindow := NIL
  638. END;
  639. RETURN snappingWindow
  640. END DoSnapping;
  641. BEGIN
  642. drawingWindow := NIL;
  643. drawSurface := NIL;
  644. dragStartX := 0.0;
  645. dragStartY := 0.0;
  646. gesturesWindow := NIL;
  647. swipeX := 0.0;
  648. swipeY := 0.0;
  649. rotateDelta := 0.0;
  650. zoomScale := 1.0;
  651. longPressed := FALSE;
  652. hasRotate := FALSE;
  653. hasZoom := FALSE;
  654. maskWindow := NIL;
  655. maskScale := NIL;
  656. maskProgress := 0.5;
  657. snappingWindow := NIL;
  658. InitQrData
  659. END DemosCanvas.