1/* Paint
2 * #Keywords: GdkDrawingArea, GtkGesture
3 *
4 * Demonstrates practical handling of drawing tablets in a real world
5 * usecase.
6 */
7#include <glib/gi18n.h>
8#include <gtk/gtk.h>
9
10enum {
11 COLOR_SET,
12 N_SIGNALS
13};
14
15static guint area_signals[N_SIGNALS] = { 0, };
16
17typedef struct
18{
19 GtkWidget parent_instance;
20 cairo_surface_t *surface;
21 cairo_t *cr;
22 GdkRGBA draw_color;
23 GtkPadController *pad_controller;
24 double brush_size;
25} DrawingArea;
26
27typedef struct
28{
29 GtkWidgetClass parent_class;
30} DrawingAreaClass;
31
32static GtkPadActionEntry pad_actions[] = {
33 { GTK_PAD_ACTION_BUTTON, 1, -1, N_("Black"), "pad.black" },
34 { GTK_PAD_ACTION_BUTTON, 2, -1, N_("Pink"), "pad.pink" },
35 { GTK_PAD_ACTION_BUTTON, 3, -1, N_("Green"), "pad.green" },
36 { GTK_PAD_ACTION_BUTTON, 4, -1, N_("Red"), "pad.red" },
37 { GTK_PAD_ACTION_BUTTON, 5, -1, N_("Purple"), "pad.purple" },
38 { GTK_PAD_ACTION_BUTTON, 6, -1, N_("Orange"), "pad.orange" },
39 { GTK_PAD_ACTION_STRIP, -1, -1, N_("Brush size"), "pad.brush_size" },
40};
41
42static const char *pad_colors[] = {
43 "black",
44 "pink",
45 "green",
46 "red",
47 "purple",
48 "orange"
49};
50
51static GType drawing_area_get_type (void);
52G_DEFINE_TYPE (DrawingArea, drawing_area, GTK_TYPE_WIDGET)
53
54static void drawing_area_set_color (DrawingArea *area,
55 GdkRGBA *color);
56
57static void
58drawing_area_ensure_surface (DrawingArea *area,
59 int width,
60 int height)
61{
62 if (!area->surface ||
63 cairo_image_surface_get_width (surface: area->surface) != width ||
64 cairo_image_surface_get_height (surface: area->surface) != height)
65 {
66 cairo_surface_t *surface;
67
68 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32,
69 width, height);
70 if (area->surface)
71 {
72 cairo_t *cr;
73
74 cr = cairo_create (target: surface);
75 cairo_set_source_surface (cr, surface: area->surface, x: 0, y: 0);
76 cairo_paint (cr);
77
78 cairo_surface_destroy (surface: area->surface);
79 cairo_destroy (cr: area->cr);
80 cairo_destroy (cr);
81 }
82
83 area->surface = surface;
84 area->cr = cairo_create (target: surface);
85 }
86}
87
88static void
89drawing_area_size_allocate (GtkWidget *widget,
90 int width,
91 int height,
92 int baseline)
93{
94 DrawingArea *area = (DrawingArea *) widget;
95
96 drawing_area_ensure_surface (area, width, height);
97
98 GTK_WIDGET_CLASS (drawing_area_parent_class)->size_allocate (widget, width, height, baseline);
99}
100
101static void
102drawing_area_map (GtkWidget *widget)
103{
104 GtkAllocation allocation;
105
106 GTK_WIDGET_CLASS (drawing_area_parent_class)->map (widget);
107
108 gtk_widget_get_allocation (widget, allocation: &allocation);
109 drawing_area_ensure_surface (area: (DrawingArea *) widget,
110 width: allocation.width, height: allocation.height);
111}
112
113static void
114drawing_area_unmap (GtkWidget *widget)
115{
116 DrawingArea *area = (DrawingArea *) widget;
117
118 g_clear_pointer (&area->cr, cairo_destroy);
119 g_clear_pointer (&area->surface, cairo_surface_destroy);
120
121 GTK_WIDGET_CLASS (drawing_area_parent_class)->unmap (widget);
122}
123
124static void
125drawing_area_snapshot (GtkWidget *widget,
126 GtkSnapshot *snapshot)
127{
128 DrawingArea *area = (DrawingArea *) widget;
129 GtkAllocation allocation;
130 cairo_t *cr;
131
132 gtk_widget_get_allocation (widget, allocation: &allocation);
133 cr = gtk_snapshot_append_cairo (snapshot,
134 bounds: &GRAPHENE_RECT_INIT (
135 0, 0,
136 allocation.width,
137 allocation.height
138 ));
139
140 cairo_set_source_rgb (cr, red: 1, green: 1, blue: 1);
141 cairo_paint (cr);
142
143 cairo_set_source_surface (cr, surface: area->surface, x: 0, y: 0);
144 cairo_paint (cr);
145
146 cairo_set_source_rgb (cr, red: 0.6, green: 0.6, blue: 0.6);
147 cairo_rectangle (cr, x: 0, y: 0, width: allocation.width, height: allocation.height);
148 cairo_stroke (cr);
149
150 cairo_destroy (cr);
151}
152
153static void
154on_pad_button_activate (GSimpleAction *action,
155 GVariant *parameter,
156 DrawingArea *area)
157{
158 const char *color = g_object_get_data (G_OBJECT (action), key: "color");
159 GdkRGBA rgba;
160
161 gdk_rgba_parse (rgba: &rgba, spec: color);
162 drawing_area_set_color (area, color: &rgba);
163}
164
165static void
166on_pad_knob_change (GSimpleAction *action,
167 GVariant *parameter,
168 DrawingArea *area)
169{
170 double value = g_variant_get_double (value: parameter);
171
172 area->brush_size = value;
173}
174
175static void
176drawing_area_unroot (GtkWidget *widget)
177{
178 DrawingArea *area = (DrawingArea *) widget;
179 GtkWidget *toplevel;
180
181 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
182
183 if (area->pad_controller)
184 {
185 gtk_widget_remove_controller (widget: toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
186 area->pad_controller = NULL;
187 }
188
189 GTK_WIDGET_CLASS (drawing_area_parent_class)->unroot (widget);
190}
191
192static void
193drawing_area_root (GtkWidget *widget)
194{
195 DrawingArea *area = (DrawingArea *) widget;
196 GSimpleActionGroup *action_group;
197 GSimpleAction *action;
198 GtkWidget *toplevel;
199 int i;
200
201 GTK_WIDGET_CLASS (drawing_area_parent_class)->root (widget);
202
203 toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (area)));
204
205 action_group = g_simple_action_group_new ();
206 area->pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (action_group), NULL);
207
208 for (i = 0; i < G_N_ELEMENTS (pad_actions); i++)
209 {
210 if (pad_actions[i].type == GTK_PAD_ACTION_BUTTON)
211 {
212 action = g_simple_action_new (name: pad_actions[i].action_name, NULL);
213 g_object_set_data (G_OBJECT (action), key: "color",
214 data: (gpointer) pad_colors[i]);
215 g_signal_connect (action, "activate",
216 G_CALLBACK (on_pad_button_activate), area);
217 }
218 else
219 {
220 action = g_simple_action_new_stateful (name: pad_actions[i].action_name,
221 G_VARIANT_TYPE_DOUBLE, NULL);
222 g_signal_connect (action, "activate",
223 G_CALLBACK (on_pad_knob_change), area);
224 }
225
226 g_action_map_add_action (G_ACTION_MAP (action_group), G_ACTION (action));
227 g_object_unref (object: action);
228 }
229
230 gtk_pad_controller_set_action_entries (controller: area->pad_controller, entries: pad_actions,
231 G_N_ELEMENTS (pad_actions));
232
233 gtk_widget_add_controller (widget: toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
234}
235
236static void
237drawing_area_class_init (DrawingAreaClass *klass)
238{
239 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
240
241 widget_class->size_allocate = drawing_area_size_allocate;
242 widget_class->snapshot = drawing_area_snapshot;
243 widget_class->map = drawing_area_map;
244 widget_class->unmap = drawing_area_unmap;
245 widget_class->root = drawing_area_root;
246 widget_class->unroot = drawing_area_unroot;
247
248 area_signals[COLOR_SET] =
249 g_signal_new (signal_name: "color-set",
250 G_TYPE_FROM_CLASS (widget_class),
251 signal_flags: G_SIGNAL_RUN_FIRST,
252 class_offset: 0, NULL, NULL, NULL,
253 G_TYPE_NONE, n_params: 1, GDK_TYPE_RGBA);
254}
255
256static void
257drawing_area_apply_stroke (DrawingArea *area,
258 GdkDeviceTool *tool,
259 double x,
260 double y,
261 double pressure)
262{
263 if (gdk_device_tool_get_tool_type (tool) == GDK_DEVICE_TOOL_TYPE_ERASER)
264 {
265 cairo_set_line_width (cr: area->cr, width: 10 * pressure * area->brush_size);
266 cairo_set_operator (cr: area->cr, op: CAIRO_OPERATOR_DEST_OUT);
267 }
268 else
269 {
270 cairo_set_line_width (cr: area->cr, width: 4 * pressure * area->brush_size);
271 cairo_set_operator (cr: area->cr, op: CAIRO_OPERATOR_SATURATE);
272 }
273
274 cairo_set_source_rgba (cr: area->cr, red: area->draw_color.red,
275 green: area->draw_color.green, blue: area->draw_color.blue,
276 alpha: area->draw_color.alpha * pressure);
277
278 cairo_line_to (cr: area->cr, x, y);
279 cairo_stroke (cr: area->cr);
280 cairo_move_to (cr: area->cr, x, y);
281}
282
283static void
284stylus_gesture_down (GtkGestureStylus *gesture,
285 double x,
286 double y,
287 DrawingArea *area)
288{
289 cairo_new_path (cr: area->cr);
290}
291
292static void
293stylus_gesture_motion (GtkGestureStylus *gesture,
294 double x,
295 double y,
296 DrawingArea *area)
297{
298 GdkTimeCoord *backlog;
299 GdkDeviceTool *tool;
300 double pressure;
301 guint n_items;
302
303 tool = gtk_gesture_stylus_get_device_tool (gesture);
304
305 if (gtk_gesture_stylus_get_backlog (gesture, backlog: &backlog, n_elems: &n_items))
306 {
307 guint i;
308
309 for (i = 0; i < n_items; i++)
310 {
311 drawing_area_apply_stroke (area, tool,
312 x: backlog[i].axes[GDK_AXIS_X],
313 y: backlog[i].axes[GDK_AXIS_Y],
314 pressure: backlog[i].axes[GDK_AXIS_PRESSURE]);
315 }
316
317 g_free (mem: backlog);
318 }
319 else
320 {
321 if (!gtk_gesture_stylus_get_axis (gesture, axis: GDK_AXIS_PRESSURE, value: &pressure))
322 pressure = 1;
323
324 drawing_area_apply_stroke (area, tool, x, y, pressure);
325 }
326
327 gtk_widget_queue_draw (GTK_WIDGET (area));
328}
329
330static void
331drawing_area_init (DrawingArea *area)
332{
333 GtkGesture *gesture;
334
335 gesture = gtk_gesture_stylus_new ();
336 g_signal_connect (gesture, "down",
337 G_CALLBACK (stylus_gesture_down), area);
338 g_signal_connect (gesture, "motion",
339 G_CALLBACK (stylus_gesture_motion), area);
340 gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture));
341
342 area->draw_color = (GdkRGBA) { 0, 0, 0, 1 };
343 area->brush_size = 1;
344}
345
346static GtkWidget *
347drawing_area_new (void)
348{
349 return g_object_new (object_type: drawing_area_get_type (), NULL);
350}
351
352static void
353drawing_area_set_color (DrawingArea *area,
354 GdkRGBA *color)
355{
356 if (gdk_rgba_equal (p1: &area->draw_color, p2: color))
357 return;
358
359 area->draw_color = *color;
360 g_signal_emit (instance: area, signal_id: area_signals[COLOR_SET], detail: 0, &area->draw_color);
361}
362
363static void
364color_button_color_set (GtkColorButton *button,
365 DrawingArea *draw_area)
366{
367 GdkRGBA color;
368
369 gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), color: &color);
370 drawing_area_set_color (area: draw_area, color: &color);
371}
372
373static void
374drawing_area_color_set (DrawingArea *area,
375 GdkRGBA *color,
376 GtkColorButton *button)
377{
378 gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (button), color);
379}
380
381GtkWidget *
382do_paint (GtkWidget *toplevel)
383{
384 static GtkWidget *window = NULL;
385
386 if (!window)
387 {
388 GtkWidget *draw_area, *headerbar, *colorbutton;
389
390 window = gtk_window_new ();
391
392 draw_area = drawing_area_new ();
393 gtk_window_set_child (GTK_WINDOW (window), child: draw_area);
394
395 headerbar = gtk_header_bar_new ();
396
397 colorbutton = gtk_color_button_new ();
398 g_signal_connect (colorbutton, "color-set",
399 G_CALLBACK (color_button_color_set), draw_area);
400 g_signal_connect (draw_area, "color-set",
401 G_CALLBACK (drawing_area_color_set), colorbutton);
402 gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (colorbutton),
403 color: &(GdkRGBA) { 0, 0, 0, 1 });
404
405 gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), child: colorbutton);
406 gtk_window_set_titlebar (GTK_WINDOW (window), titlebar: headerbar);
407 gtk_window_set_title (GTK_WINDOW (window), title: "Paint");
408 g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window);
409 }
410
411 if (!gtk_widget_get_visible (widget: window))
412 gtk_widget_show (widget: window);
413 else
414 gtk_window_destroy (GTK_WINDOW (window));
415
416 return window;
417}
418

source code of gtk/demos/gtk-demo/paint.c