IMPLEMENTATION MODULE DemosCanvas ; (* m2-GTK4 - the drawing/canvas gtk-demo demos: drawingarea.c -> DoDrawingArea gestures.c -> DoGestures mask.c -> DoMask snapping.c -> DoSnapping The gtk-demo contract is: on the first call build the window and store it in a module-level variable; on later calls toggle the window's visibility, destroying it (and clearing the variable) when it is already visible. Return the window, or NIL once destroyed. Only bindings under src/ are used, so a few C facilities are missing and those spots are simplified (each marked "SIMPLIFIED" below): * gtk_widget_queue_draw() is not bound. Gesture/scribble state is still recorded, but the drawing areas cannot be repainted until GTK redraws them for another reason (e.g. an expose or resize). * drawingarea's groups_draw uses cairo_surface_create_similar(), cairo_get_target() and CAIRO_OPERATOR_DEST_OUT/ADD, none of which are bound; the knockout/composite is approximated by a black disc with the three coloured sub-discs drawn on top. * cairo_image_surface_get_width()/get_height() are not bound, so the scribble surface is (re)created on the "resize" signal only. * gtk_window_set_display(), the accessible-role/an accessible-relation setup and g_object_new(drawing area) are not bound; a plain gtk_drawing_area_new() is used. * gestures' rotate/zoom visualisation reads the gesture deltas from the signal arguments instead of gtk_gesture_*_get_*_delta(), and uses the widget centre instead of gtk_gesture_get_bounding_box_center(); the touchpad 3-finger swipe and gtk_gesture_is_recognized() are not reproduced. * mask's Demo4Widget is a custom GskMaskNode widget with a Pango layout and a frame-clock tick callback; subclassing, GskMaskNode and gtk_widget_add_tick_callback() are not bound, so the "123" text is filled with a moving 8-stop gradient via Cairo instead. * snapping's GtkTiler is a custom GdkPaintable; a GtkDrawingArea is used instead, drawing the QR code on a pixel-snapped 43x43 grid with linear gradients between differing neighbours (the conic/radial corner cases are left white). *) FROM SYSTEM IMPORT ADDRESS, ADR; FROM GtkWindow IMPORT gtk_window_new, gtk_window_set_title, gtk_window_set_default_size, gtk_window_set_child, gtk_window_destroy; FROM GtkWidget IMPORT gtk_widget_set_visible, gtk_widget_get_visible, gtk_widget_set_margin_start, gtk_widget_set_margin_end, gtk_widget_set_margin_top, gtk_widget_set_margin_bottom, gtk_widget_set_vexpand, gtk_widget_set_hexpand, gtk_widget_add_css_class, gtk_widget_get_width, gtk_widget_get_height, gtk_widget_add_controller; FROM GtkEnums IMPORT GtkVertical, GtkHorizontal; FROM GtkBox IMPORT gtk_box_new, gtk_box_append; FROM GtkLabel IMPORT gtk_label_new; FROM GtkFrame IMPORT gtk_frame_new, gtk_frame_set_child; FROM GtkDrawingArea IMPORT gtk_drawing_area_new, gtk_drawing_area_set_content_width, gtk_drawing_area_set_content_height, gtk_drawing_area_set_draw_func; FROM GtkGestures IMPORT gtk_gesture_drag_new, gtk_gesture_single_set_button, gtk_gesture_swipe_new, gtk_gesture_long_press_new, gtk_gesture_rotate_new, gtk_gesture_zoom_new; FROM GtkEventController IMPORT gtk_event_controller_set_propagation_phase, GtkPhaseBubble; FROM GtkScale IMPORT gtk_scale_new_with_range; FROM GtkRange IMPORT gtk_range_set_value, gtk_range_get_value; FROM GObject IMPORT g_signal_connect_data, GConnectDefault; FROM GtkClosures IMPORT Connect2; FROM Cairo IMPORT cairo_create, cairo_destroy, cairo_set_source_rgb, cairo_paint, cairo_set_source_surface, cairo_move_to, cairo_line_to, cairo_set_line_width, cairo_set_line_cap, cairo_stroke, cairo_save, cairo_restore, cairo_translate, cairo_scale, cairo_rotate, cairo_arc, cairo_close_path, cairo_rectangle, cairo_fill, cairo_set_source_rgba, cairo_surface_destroy, cairo_image_surface_create, cairo_pattern_create_linear, cairo_pattern_add_color_stop_rgb, cairo_set_source, cairo_pattern_destroy, cairo_select_font_face, cairo_set_font_size, cairo_text_path, CairoFormatARGB32, CairoLineCapRound, CairoFontSlantNormal, CairoFontWeightBold; CONST Pi = 3.14159265358979; VAR (* drawingarea *) drawingWindow, drawSurface: ADDRESS; dragStartX, dragStartY: REAL; (* gestures *) gesturesWindow: ADDRESS; swipeX, swipeY, rotateDelta, zoomScale: REAL; longPressed, hasRotate, hasZoom: BOOLEAN; (* mask *) maskWindow, maskScale: ADDRESS; maskProgress: REAL; (* snapping *) snappingWindow: ADDRESS; qrData: ARRAY [0..20] OF ARRAY [0..20] OF CHAR; (* ------------------------------------------------------------------ *) (* small helpers *) (* ------------------------------------------------------------------ *) PROCEDURE IMin (a, b: INTEGER) : INTEGER; BEGIN IF a < b THEN RETURN a ELSE RETURN b END END IMin; (* The 21x21 QR matrix of snapping.c, one row per string. *) PROCEDURE InitQrData; BEGIN qrData[0] := "000000010111110000000"; qrData[1] := "011111011100110111110"; qrData[2] := "010001011010010100010"; qrData[3] := "010001010011110100010"; qrData[4] := "010001010001010100010"; qrData[5] := "011111010100110111110"; qrData[6] := "000000010101010000000"; qrData[7] := "111111110000011111111"; qrData[8] := "011101000000100000110"; qrData[9] := "111100100000011100011"; qrData[10] := "110010010110110001001"; qrData[11] := "111001100101101111100"; qrData[12] := "101100000011001100110"; qrData[13] := "111111110011011100011"; qrData[14] := "000000010011000101001"; qrData[15] := "011111011000001000100"; qrData[16] := "010001010010111011101"; qrData[17] := "010001011001000000100"; qrData[18] := "010001011010110100011"; qrData[19] := "011111011001001111111"; qrData[20] := "000000010100001011110" END InitQrData; (* ------------------------------------------------------------------ *) (* drawing area *) (* ------------------------------------------------------------------ *) PROCEDURE CreateSurface (widget: ADDRESS); VAR cr: ADDRESS; BEGIN IF drawSurface # NIL THEN cairo_surface_destroy(drawSurface) END; drawSurface := cairo_image_surface_create(CairoFormatARGB32, gtk_widget_get_width(widget), gtk_widget_get_height(widget)); cr := cairo_create(drawSurface); cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); cairo_paint(cr); cairo_destroy(cr) END CreateSurface; (* void scribble_resize(GtkWidget *widget, int width, int height, gpointer) *) PROCEDURE ScribbleResize (widget: ADDRESS; width, height: INTEGER; data: ADDRESS); BEGIN CreateSurface(widget) END ScribbleResize; PROCEDURE ScribbleDraw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER; data: ADDRESS); BEGIN IF drawSurface # NIL THEN cairo_set_source_surface(cr, drawSurface, 0.0, 0.0); cairo_paint(cr) END END ScribbleDraw; PROCEDURE DrawBrush (widget: ADDRESS; x, y: REAL); VAR cr: ADDRESS; BEGIN IF drawSurface = NIL THEN CreateSurface(widget) END; cr := cairo_create(drawSurface); cairo_move_to(cr, x, y); cairo_line_to(cr, x, y); cairo_set_line_width(cr, 6.0); cairo_set_line_cap(cr, CairoLineCapRound); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_stroke(cr); cairo_destroy(cr) (* gtk_widget_queue_draw(widget) is not bound (see header). *) END DrawBrush; PROCEDURE DragBegin (gesture: ADDRESS; x, y: REAL; area: ADDRESS); BEGIN dragStartX := x; dragStartY := y; DrawBrush(area, x, y) END DragBegin; PROCEDURE DragUpdate (gesture: ADDRESS; x, y: REAL; area: ADDRESS); BEGIN DrawBrush(area, dragStartX + x, dragStartY + y) END DragUpdate; PROCEDURE DragEnd (gesture: ADDRESS; x, y: REAL; area: ADDRESS); BEGIN DrawBrush(area, dragStartX + x, dragStartY + y) END DragEnd; PROCEDURE OvalPath (cr: ADDRESS; xc, yc, xr, yr: REAL); BEGIN cairo_save(cr); cairo_translate(cr, xc, yc); cairo_scale(cr, 1.0, yr / xr); cairo_move_to(cr, xr, 0.0); cairo_arc(cr, 0.0, 0.0, xr, 0.0, 2.0 * Pi); cairo_close_path(cr); cairo_restore(cr) END OvalPath; PROCEDURE FillChecks (cr: ADDRESS; x, y, width, height: INTEGER); CONST CheckSize = 16; VAR i, j: INTEGER; BEGIN cairo_rectangle(cr, VAL(REAL, x), VAL(REAL, y), VAL(REAL, width), VAL(REAL, height)); cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); cairo_fill(cr); (* x & (-CHECK_SIZE), valid for CHECK_SIZE a power of two. *) j := x - (x MOD CheckSize); WHILE j < height DO i := y - (y MOD CheckSize); WHILE i < width DO IF ((i DIV CheckSize) + (j DIV CheckSize)) MOD 2 = 0 THEN cairo_rectangle(cr, VAL(REAL, i), VAL(REAL, j), VAL(REAL, CheckSize), VAL(REAL, CheckSize)) END; INC(i, CheckSize) END; INC(j, CheckSize) END; cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); cairo_fill(cr) END FillChecks; PROCEDURE Draw3Circles (cr: ADDRESS; xc, yc, radius, alpha: REAL); CONST SubRadius = 0.56666667; (* 2/3 - 0.1 *) Third = 0.33333333; Cos30 = 0.86602540; VAR sr: REAL; BEGIN sr := radius * SubRadius; cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, alpha); OvalPath(cr, xc, yc - radius * Third, sr, sr); cairo_fill(cr); cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, alpha); OvalPath(cr, xc - radius * Third * Cos30, yc + radius * Third * 0.5, sr, sr); cairo_fill(cr); cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, alpha); OvalPath(cr, xc + radius * Third * Cos30, yc + radius * Third * 0.5, sr, sr); cairo_fill(cr) END Draw3Circles; PROCEDURE GroupsDraw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER; data: ADDRESS); VAR radius, xc, yc: REAL; BEGIN radius := 0.5 * VAL(REAL, IMin(width, height)) - 10.0; xc := VAL(REAL, width) / 2.0; yc := VAL(REAL, height) / 2.0; FillChecks(cr, 0, 0, width, height); (* SIMPLIFIED: no similar surfaces / DEST_OUT / ADD. *) cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); OvalPath(cr, xc, yc, radius, radius); cairo_fill(cr); Draw3Circles(cr, xc, yc, radius, 1.0) END GroupsDraw; PROCEDURE OnDrawingDestroy (widget: ADDRESS; data: ADDRESS); BEGIN IF drawSurface # NIL THEN cairo_surface_destroy(drawSurface); drawSurface := NIL END END OnDrawingDestroy; PROCEDURE DoDrawingArea (doWidget: ADDRESS) : ADDRESS; VAR vbox, da, label, frame, drag: ADDRESS; BEGIN IF drawingWindow = NIL THEN drawingWindow := gtk_window_new(); gtk_window_set_title(drawingWindow, "Drawing Area"); gtk_window_set_default_size(drawingWindow, 250, -1); Connect2(drawingWindow, "destroy", OnDrawingDestroy, NIL); vbox := gtk_box_new(GtkVertical, 8); gtk_widget_set_margin_start(vbox, 16); gtk_widget_set_margin_end(vbox, 16); gtk_widget_set_margin_top(vbox, 16); gtk_widget_set_margin_bottom(vbox, 16); gtk_window_set_child(drawingWindow, vbox); (* groups area *) label := gtk_label_new("Knockout groups"); gtk_widget_add_css_class(label, "heading"); gtk_box_append(vbox, label); frame := gtk_frame_new(""); gtk_widget_set_vexpand(frame, 1); gtk_box_append(vbox, frame); da := gtk_drawing_area_new(); gtk_frame_set_child(frame, da); gtk_drawing_area_set_content_width(da, 100); gtk_drawing_area_set_content_height(da, 100); gtk_drawing_area_set_draw_func(da, ADR(GroupsDraw), NIL, NIL); (* scribble area *) label := gtk_label_new("Scribble area"); gtk_widget_add_css_class(label, "heading"); gtk_box_append(vbox, label); frame := gtk_frame_new(""); gtk_widget_set_vexpand(frame, 1); gtk_box_append(vbox, frame); da := gtk_drawing_area_new(); gtk_frame_set_child(frame, da); gtk_drawing_area_set_content_width(da, 100); gtk_drawing_area_set_content_height(da, 100); gtk_drawing_area_set_draw_func(da, ADR(ScribbleDraw), NIL, NIL); g_signal_connect_data(da, "resize", ADR(ScribbleResize), NIL, NIL, GConnectDefault); drag := gtk_gesture_drag_new(); gtk_gesture_single_set_button(drag, 0); gtk_widget_add_controller(da, drag); g_signal_connect_data(drag, "drag-begin", ADR(DragBegin), da, NIL, GConnectDefault); g_signal_connect_data(drag, "drag-update", ADR(DragUpdate), da, NIL, GConnectDefault); g_signal_connect_data(drag, "drag-end", ADR(DragEnd), da, NIL, GConnectDefault) END; IF gtk_widget_get_visible(drawingWindow) = 0 THEN gtk_widget_set_visible(drawingWindow, 1) ELSE gtk_window_destroy(drawingWindow); drawingWindow := NIL END; RETURN drawingWindow END DoDrawingArea; (* ------------------------------------------------------------------ *) (* gestures *) (* ------------------------------------------------------------------ *) PROCEDURE SwipeSwept (gesture: ADDRESS; vx, vy: REAL; widget: ADDRESS); BEGIN swipeX := vx / 10.0; swipeY := vy / 10.0 END SwipeSwept; PROCEDURE LongPressPressed (gesture: ADDRESS; x, y: REAL; widget: ADDRESS); BEGIN longPressed := TRUE END LongPressPressed; PROCEDURE LongPressEnd (gesture: ADDRESS; sequence: ADDRESS; widget: ADDRESS); BEGIN longPressed := FALSE END LongPressEnd; PROCEDURE RotationChanged (gesture: ADDRESS; angle, delta: REAL; widget: ADDRESS); BEGIN rotateDelta := delta; hasRotate := TRUE END RotationChanged; PROCEDURE ZoomChanged (gesture: ADDRESS; scale: REAL; widget: ADDRESS); BEGIN zoomScale := scale; hasZoom := TRUE END ZoomChanged; PROCEDURE GesturesDraw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER; data: ADDRESS); VAR pat: ADDRESS; cx, cy: REAL; BEGIN cx := VAL(REAL, width) / 2.0; cy := VAL(REAL, height) / 2.0; IF (swipeX # 0.0) OR (swipeY # 0.0) THEN cairo_save(cr); cairo_set_line_width(cr, 6.0); cairo_move_to(cr, cx, cy); cairo_line_to(cr, cx + swipeX, cy + swipeY); cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.5); cairo_stroke(cr); cairo_restore(cr) END; IF hasRotate OR hasZoom THEN pat := cairo_pattern_create_linear(-100.0, 0.0, 200.0, 0.0); cairo_pattern_add_color_stop_rgb(pat, 0.0, 0.0, 0.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 1.0, 1.0, 0.0, 0.0); cairo_save(cr); cairo_translate(cr, cx, cy); IF hasZoom AND (zoomScale > 0.0) THEN cairo_scale(cr, zoomScale, zoomScale) END; IF hasRotate THEN cairo_rotate(cr, rotateDelta) END; cairo_rectangle(cr, -100.0, -100.0, 200.0, 200.0); cairo_set_source(cr, pat); cairo_fill(cr); cairo_restore(cr); cairo_pattern_destroy(pat) END; IF longPressed THEN cairo_save(cr); cairo_arc(cr, cx, cy, 50.0, 0.0, 2.0 * Pi); cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.5); cairo_stroke(cr); cairo_restore(cr) END END GesturesDraw; PROCEDURE DoGestures (doWidget: ADDRESS) : ADDRESS; VAR da, gesture: ADDRESS; BEGIN IF gesturesWindow = NIL THEN gesturesWindow := gtk_window_new(); gtk_window_set_title(gesturesWindow, "Gestures"); gtk_window_set_default_size(gesturesWindow, 400, 400); da := gtk_drawing_area_new(); gtk_window_set_child(gesturesWindow, da); gtk_drawing_area_set_draw_func(da, ADR(GesturesDraw), NIL, NIL); (* swipe *) gesture := gtk_gesture_swipe_new(); g_signal_connect_data(gesture, "swipe", ADR(SwipeSwept), da, NIL, GConnectDefault); gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble); gtk_widget_add_controller(da, gesture); (* long press *) gesture := gtk_gesture_long_press_new(); g_signal_connect_data(gesture, "pressed", ADR(LongPressPressed), da, NIL, GConnectDefault); g_signal_connect_data(gesture, "end", ADR(LongPressEnd), da, NIL, GConnectDefault); gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble); gtk_widget_add_controller(da, gesture); (* rotate *) gesture := gtk_gesture_rotate_new(); g_signal_connect_data(gesture, "angle-changed", ADR(RotationChanged), da, NIL, GConnectDefault); gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble); gtk_widget_add_controller(da, gesture); (* zoom *) gesture := gtk_gesture_zoom_new(); g_signal_connect_data(gesture, "scale-changed", ADR(ZoomChanged), da, NIL, GConnectDefault); gtk_event_controller_set_propagation_phase(gesture, GtkPhaseBubble); gtk_widget_add_controller(da, gesture) END; IF gtk_widget_get_visible(gesturesWindow) = 0 THEN gtk_widget_set_visible(gesturesWindow, 1) ELSE gtk_window_destroy(gesturesWindow); gesturesWindow := NIL END; RETURN gesturesWindow END DoGestures; (* ------------------------------------------------------------------ *) (* masking *) (* ------------------------------------------------------------------ *) PROCEDURE OnMaskChanged (range: ADDRESS; data: ADDRESS); BEGIN maskProgress := gtk_range_get_value(range) END OnMaskChanged; PROCEDURE MaskDraw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER; data: ADDRESS); VAR pat: ADDRESS; w, h, x0, y0: REAL; BEGIN w := VAL(REAL, width); h := VAL(REAL, height); cairo_set_source_rgb(cr, 0.08, 0.08, 0.10); cairo_rectangle(cr, 0.0, 0.0, w, h); cairo_fill(cr); (* SIMPLIFIED: fill the "123" glyphs with a moving gradient in place of the GskMaskNode composition. *) cairo_select_font_face(cr, "sans", CairoFontSlantNormal, CairoFontWeightBold); cairo_set_font_size(cr, 200.0); x0 := w / 2.0 - 165.0; y0 := h / 2.0 + 70.0; cairo_move_to(cr, x0, y0); cairo_text_path(cr, "123"); pat := cairo_pattern_create_linear(maskProgress * w - w, 0.0, maskProgress * w, h); cairo_pattern_add_color_stop_rgb(pat, 0.0, 1.0, 0.0, 0.0); cairo_pattern_add_color_stop_rgb(pat, 1.0 / 7.0, 1.0, 0.5, 0.0); cairo_pattern_add_color_stop_rgb(pat, 2.0 / 7.0, 1.0, 1.0, 0.0); cairo_pattern_add_color_stop_rgb(pat, 3.0 / 7.0, 0.0, 1.0, 0.0); cairo_pattern_add_color_stop_rgb(pat, 4.0 / 7.0, 0.0, 1.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 5.0 / 7.0, 0.0, 0.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 6.0 / 7.0, 0.6, 0.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 1.0, 1.0, 0.0, 1.0); cairo_set_source(cr, pat); cairo_fill(cr); cairo_pattern_destroy(pat) END MaskDraw; PROCEDURE DoMask (doWidget: ADDRESS) : ADDRESS; VAR box, da: ADDRESS; BEGIN IF maskWindow = NIL THEN maskWindow := gtk_window_new(); gtk_window_set_title(maskWindow, "Mask Nodes"); gtk_window_set_default_size(maskWindow, 600, 400); box := gtk_box_new(GtkVertical, 0); gtk_window_set_child(maskWindow, box); da := gtk_drawing_area_new(); gtk_widget_set_hexpand(da, 1); gtk_widget_set_vexpand(da, 1); gtk_drawing_area_set_draw_func(da, ADR(MaskDraw), NIL, NIL); gtk_box_append(box, da); maskScale := gtk_scale_new_with_range(GtkHorizontal, 0.0, 1.0, 0.1); gtk_range_set_value(maskScale, 0.5); maskProgress := 0.5; Connect2(maskScale, "value-changed", OnMaskChanged, NIL); gtk_box_append(box, maskScale) END; IF gtk_widget_get_visible(maskWindow) = 0 THEN gtk_widget_set_visible(maskWindow, 1) ELSE gtk_window_destroy(maskWindow); maskWindow := NIL END; RETURN maskWindow END DoMask; (* ------------------------------------------------------------------ *) (* snapping *) (* ------------------------------------------------------------------ *) PROCEDURE QrOn (x, y: INTEGER) : BOOLEAN; BEGIN RETURN qrData[y][x] = '1' END QrOn; PROCEDURE SetOnColor (cr: ADDRESS; on: BOOLEAN); BEGIN IF on THEN cairo_set_source_rgb(cr, 0.2, 0.6, 1.0) (* ON *) ELSE cairo_set_source_rgb(cr, 1.0, 0.95, 0.7) (* OFF *) END END SetOnColor; (* A 1x1 tile with a horizontal gradient between differing neighbours. *) PROCEDURE HBetween (cr: ADDRESS; x, y: REAL; a, b: BOOLEAN); VAR pat: ADDRESS; BEGIN IF a = b THEN SetOnColor(cr, a) ELSE pat := cairo_pattern_create_linear(x, y, x + 1.0, y); IF a THEN cairo_pattern_add_color_stop_rgb(pat, 0.3, 0.2, 0.6, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.7, 1.0, 0.95, 0.7) ELSE cairo_pattern_add_color_stop_rgb(pat, 0.3, 1.0, 0.95, 0.7); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.7, 0.2, 0.6, 1.0) END; cairo_set_source(cr, pat) END; cairo_rectangle(cr, x, y, 1.0, 1.0); cairo_fill(cr); IF a # b THEN cairo_pattern_destroy(pat) END END HBetween; (* A 1x1 tile with a vertical gradient between differing neighbours. *) PROCEDURE VBetween (cr: ADDRESS; x, y: REAL; a, b: BOOLEAN); VAR pat: ADDRESS; BEGIN IF a = b THEN SetOnColor(cr, a) ELSE pat := cairo_pattern_create_linear(x, y, x, y + 1.0); IF a THEN cairo_pattern_add_color_stop_rgb(pat, 0.3, 0.2, 0.6, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.7, 1.0, 0.95, 0.7) ELSE cairo_pattern_add_color_stop_rgb(pat, 0.3, 1.0, 0.95, 0.7); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1.0, 1.0, 1.0); cairo_pattern_add_color_stop_rgb(pat, 0.7, 0.2, 0.6, 1.0) END; cairo_set_source(cr, pat) END; cairo_rectangle(cr, x, y, 1.0, 1.0); cairo_fill(cr); IF a # b THEN cairo_pattern_destroy(pat) END END VBetween; PROCEDURE SnappingDraw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER; data: ADDRESS); VAR cell, ox, oy, x, y: INTEGER; BEGIN (* 21 tiles plus 21 gaps make 43 units; snap to whole device pixels. *) cell := IMin(width, height) DIV 43; IF cell < 1 THEN cell := 1 END; ox := (width - 43 * cell) DIV 2; oy := (height - 43 * cell) DIV 2; cairo_save(cr); cairo_translate(cr, VAL(REAL, ox), VAL(REAL, oy)); cairo_scale(cr, VAL(REAL, cell), VAL(REAL, cell)); cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); cairo_rectangle(cr, 0.0, 0.0, 43.0, 43.0); cairo_fill(cr); (* tile centres at odd coordinates 1, 3, ..., 41 *) FOR y := 0 TO 20 DO FOR x := 0 TO 20 DO SetOnColor(cr, QrOn(x, y)); cairo_rectangle(cr, VAL(REAL, 2 * x + 1), VAL(REAL, 2 * y + 1), 1.0, 1.0); cairo_fill(cr) END END; (* horizontal gaps between columns *) FOR y := 0 TO 20 DO FOR x := 0 TO 19 DO HBetween(cr, VAL(REAL, 2 * x + 2), VAL(REAL, 2 * y + 1), QrOn(x, y), QrOn(x + 1, y)) END END; (* vertical gaps between rows *) FOR y := 0 TO 19 DO FOR x := 0 TO 20 DO VBetween(cr, VAL(REAL, 2 * x + 1), VAL(REAL, 2 * y + 2), QrOn(x, y), QrOn(x, y + 1)) END END; cairo_restore(cr) END SnappingDraw; PROCEDURE DoSnapping (doWidget: ADDRESS) : ADDRESS; VAR da: ADDRESS; BEGIN IF snappingWindow = NIL THEN snappingWindow := gtk_window_new(); gtk_window_set_title(snappingWindow, "Snapping"); gtk_window_set_default_size(snappingWindow, 600, 400); da := gtk_drawing_area_new(); gtk_window_set_child(snappingWindow, da); gtk_drawing_area_set_draw_func(da, ADR(SnappingDraw), NIL, NIL) END; IF gtk_widget_get_visible(snappingWindow) = 0 THEN gtk_widget_set_visible(snappingWindow, 1) ELSE gtk_window_destroy(snappingWindow); snappingWindow := NIL END; RETURN snappingWindow END DoSnapping; BEGIN drawingWindow := NIL; drawSurface := NIL; dragStartX := 0.0; dragStartY := 0.0; gesturesWindow := NIL; swipeX := 0.0; swipeY := 0.0; rotateDelta := 0.0; zoomScale := 1.0; longPressed := FALSE; hasRotate := FALSE; hasZoom := FALSE; maskWindow := NIL; maskScale := NIL; maskProgress := 0.5; snappingWindow := NIL; InitQrData END DemosCanvas.