1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "gtkdrawingarea.h" |
27 | #include "gtkintl.h" |
28 | #include "gtkmarshalers.h" |
29 | #include "gtkprivate.h" |
30 | #include "gtksnapshot.h" |
31 | #include "gtkwidgetprivate.h" |
32 | |
33 | typedef struct _GtkDrawingAreaPrivate GtkDrawingAreaPrivate; |
34 | |
35 | struct _GtkDrawingAreaPrivate { |
36 | int content_width; |
37 | int content_height; |
38 | |
39 | GtkDrawingAreaDrawFunc draw_func; |
40 | gpointer draw_func_target; |
41 | GDestroyNotify draw_func_target_destroy_notify; |
42 | }; |
43 | |
44 | enum { |
45 | PROP_0, |
46 | PROP_CONTENT_WIDTH, |
47 | PROP_CONTENT_HEIGHT, |
48 | LAST_PROP |
49 | }; |
50 | |
51 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
52 | |
53 | enum { |
54 | RESIZE, |
55 | LAST_SIGNAL |
56 | }; |
57 | |
58 | static guint signals[LAST_SIGNAL] = { 0, }; |
59 | |
60 | /** |
61 | * GtkDrawingArea: |
62 | * |
63 | * `GtkDrawingArea` is a widget that allows drawing with cairo. |
64 | * |
65 | * ![An example GtkDrawingArea](drawingarea.png) |
66 | * |
67 | * It’s essentially a blank widget; you can draw on it. After |
68 | * creating a drawing area, the application may want to connect to: |
69 | * |
70 | * - The [signal@Gtk.Widget::realize] signal to take any necessary actions |
71 | * when the widget is instantiated on a particular display. |
72 | * (Create GDK resources in response to this signal.) |
73 | * |
74 | * - The [signal@Gtk.DrawingArea::resize] signal to take any necessary |
75 | * actions when the widget changes size. |
76 | * |
77 | * - Call [method@Gtk.DrawingArea.set_draw_func] to handle redrawing the |
78 | * contents of the widget. |
79 | * |
80 | * The following code portion demonstrates using a drawing |
81 | * area to display a circle in the normal widget foreground |
82 | * color. |
83 | * |
84 | * ## Simple GtkDrawingArea usage |
85 | * |
86 | * ```c |
87 | * static void |
88 | * draw_function (GtkDrawingArea *area, |
89 | * cairo_t *cr, |
90 | * int width, |
91 | * int height, |
92 | * gpointer data) |
93 | * { |
94 | * GdkRGBA color; |
95 | * GtkStyleContext *context; |
96 | * |
97 | * context = gtk_widget_get_style_context (GTK_WIDGET (area)); |
98 | * |
99 | * cairo_arc (cr, |
100 | * width / 2.0, height / 2.0, |
101 | * MIN (width, height) / 2.0, |
102 | * 0, 2 * G_PI); |
103 | * |
104 | * gtk_style_context_get_color (context, |
105 | * &color); |
106 | * gdk_cairo_set_source_rgba (cr, &color); |
107 | * |
108 | * cairo_fill (cr); |
109 | * } |
110 | * |
111 | * int |
112 | * main (int argc, char **argv) |
113 | * { |
114 | * gtk_init (); |
115 | * |
116 | * GtkWidget *area = gtk_drawing_area_new (); |
117 | * gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100); |
118 | * gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100); |
119 | * gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), |
120 | * draw_function, |
121 | * NULL, NULL); |
122 | * return 0; |
123 | * } |
124 | * ``` |
125 | * |
126 | * The draw function is normally called when a drawing area first comes |
127 | * onscreen, or when it’s covered by another window and then uncovered. |
128 | * You can also force a redraw by adding to the “damage region” of the |
129 | * drawing area’s window using [method@Gtk.Widget.queue_draw]. |
130 | * This will cause the drawing area to call the draw function again. |
131 | * |
132 | * The available routines for drawing are documented in the |
133 | * [Cairo documentation](https://www.cairographics.org/manual/); GDK |
134 | * offers additional API to integrate with Cairo, like [func@Gdk.cairo_set_source_rgba] |
135 | * or [func@Gdk.cairo_set_source_pixbuf]. |
136 | * |
137 | * To receive mouse events on a drawing area, you will need to use |
138 | * event controllers. To receive keyboard events, you will need to set |
139 | * the “can-focus” property on the drawing area, and you should probably |
140 | * draw some user-visible indication that the drawing area is focused. |
141 | * |
142 | * If you need more complex control over your widget, you should consider |
143 | * creating your own `GtkWidget` subclass. |
144 | */ |
145 | |
146 | G_DEFINE_TYPE_WITH_PRIVATE (GtkDrawingArea, gtk_drawing_area, GTK_TYPE_WIDGET) |
147 | |
148 | static void |
149 | gtk_drawing_area_set_property (GObject *gobject, |
150 | guint prop_id, |
151 | const GValue *value, |
152 | GParamSpec *pspec) |
153 | { |
154 | GtkDrawingArea *self = GTK_DRAWING_AREA (gobject); |
155 | |
156 | switch (prop_id) |
157 | { |
158 | case PROP_CONTENT_WIDTH: |
159 | gtk_drawing_area_set_content_width (self, width: g_value_get_int (value)); |
160 | break; |
161 | |
162 | case PROP_CONTENT_HEIGHT: |
163 | gtk_drawing_area_set_content_height (self, height: g_value_get_int (value)); |
164 | break; |
165 | |
166 | default: |
167 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
168 | } |
169 | } |
170 | |
171 | static void |
172 | gtk_drawing_area_get_property (GObject *gobject, |
173 | guint prop_id, |
174 | GValue *value, |
175 | GParamSpec *pspec) |
176 | { |
177 | GtkDrawingArea *self = GTK_DRAWING_AREA (gobject); |
178 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
179 | |
180 | switch (prop_id) |
181 | { |
182 | case PROP_CONTENT_WIDTH: |
183 | g_value_set_int (value, v_int: priv->content_width); |
184 | break; |
185 | |
186 | case PROP_CONTENT_HEIGHT: |
187 | g_value_set_int (value, v_int: priv->content_height); |
188 | break; |
189 | |
190 | default: |
191 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
192 | } |
193 | } |
194 | |
195 | static void |
196 | gtk_drawing_area_dispose (GObject *object) |
197 | { |
198 | GtkDrawingArea *self = GTK_DRAWING_AREA (object); |
199 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
200 | |
201 | if (priv->draw_func_target_destroy_notify != NULL) |
202 | priv->draw_func_target_destroy_notify (priv->draw_func_target); |
203 | |
204 | priv->draw_func = NULL; |
205 | priv->draw_func_target = NULL; |
206 | priv->draw_func_target_destroy_notify = NULL; |
207 | |
208 | G_OBJECT_CLASS (gtk_drawing_area_parent_class)->dispose (object); |
209 | } |
210 | |
211 | static void |
212 | gtk_drawing_area_measure (GtkWidget *widget, |
213 | GtkOrientation orientation, |
214 | int for_size, |
215 | int *minimum, |
216 | int *natural, |
217 | int *minimum_baseline, |
218 | int *natural_baseline) |
219 | { |
220 | GtkDrawingArea *self = GTK_DRAWING_AREA (widget); |
221 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
222 | |
223 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
224 | { |
225 | *minimum = *natural = priv->content_width; |
226 | } |
227 | else |
228 | { |
229 | *minimum = *natural = priv->content_height; |
230 | } |
231 | } |
232 | |
233 | static void |
234 | gtk_drawing_area_size_allocate (GtkWidget *widget, |
235 | int width, |
236 | int height, |
237 | int baseline) |
238 | { |
239 | g_signal_emit (instance: widget, signal_id: signals[RESIZE], detail: 0, width, height); |
240 | } |
241 | |
242 | static void |
243 | gtk_drawing_area_snapshot (GtkWidget *widget, |
244 | GtkSnapshot *snapshot) |
245 | { |
246 | GtkDrawingArea *self = GTK_DRAWING_AREA (widget); |
247 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
248 | cairo_t *cr; |
249 | int width, height; |
250 | |
251 | if (!priv->draw_func) |
252 | return; |
253 | |
254 | width = gtk_widget_get_width (widget); |
255 | height = gtk_widget_get_height (widget); |
256 | |
257 | |
258 | cr = gtk_snapshot_append_cairo (snapshot, |
259 | bounds: &GRAPHENE_RECT_INIT ( |
260 | 0, 0, |
261 | width, height |
262 | )); |
263 | priv->draw_func (self, |
264 | cr, |
265 | width, height, |
266 | priv->draw_func_target); |
267 | cairo_destroy (cr); |
268 | } |
269 | |
270 | static void |
271 | gtk_drawing_area_class_init (GtkDrawingAreaClass *class) |
272 | { |
273 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
274 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
275 | |
276 | gobject_class->set_property = gtk_drawing_area_set_property; |
277 | gobject_class->get_property = gtk_drawing_area_get_property; |
278 | gobject_class->dispose = gtk_drawing_area_dispose; |
279 | |
280 | widget_class->measure = gtk_drawing_area_measure; |
281 | widget_class->size_allocate = gtk_drawing_area_size_allocate; |
282 | widget_class->snapshot = gtk_drawing_area_snapshot; |
283 | |
284 | /** |
285 | * GtkDrawingArea:content-width: (attributes org.gtk.Property.get=gtk_drawing_area_get_content_width org.gtk.Property.set=gtk_drawing_area_set_content_width) |
286 | * |
287 | * The content width. |
288 | */ |
289 | props[PROP_CONTENT_WIDTH] = |
290 | g_param_spec_int (name: "content-width" , |
291 | P_("Content Width" ), |
292 | P_("Desired width for displayed content" ), |
293 | minimum: 0, G_MAXINT, default_value: 0, |
294 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
295 | |
296 | /** |
297 | * GtkDrawingArea:content-height: (attributes org.gtk.Property.get=gtk_drawing_area_get_content_height org.gtk.Property.set=gtk_drawing_area_set_content_height) |
298 | * |
299 | * The content height. |
300 | */ |
301 | props[PROP_CONTENT_HEIGHT] = |
302 | g_param_spec_int (name: "content-height" , |
303 | P_("Content Height" ), |
304 | P_("Desired height for displayed content" ), |
305 | minimum: 0, G_MAXINT, default_value: 0, |
306 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
307 | |
308 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: props); |
309 | |
310 | /** |
311 | * GtkDrawingArea::resize: |
312 | * @area: the `GtkDrawingArea` that emitted the signal |
313 | * @width: the width of the viewport |
314 | * @height: the height of the viewport |
315 | * |
316 | * Emitted once when the widget is realized, and then each time the widget |
317 | * is changed while realized. |
318 | * |
319 | * This is useful in order to keep state up to date with the widget size, |
320 | * like for instance a backing surface. |
321 | */ |
322 | signals[RESIZE] = |
323 | g_signal_new (I_("resize" ), |
324 | G_TYPE_FROM_CLASS (class), |
325 | signal_flags: G_SIGNAL_RUN_LAST, |
326 | G_STRUCT_OFFSET (GtkDrawingAreaClass, resize), |
327 | NULL, NULL, |
328 | c_marshaller: _gtk_marshal_VOID__INT_INT, |
329 | G_TYPE_NONE, n_params: 2, G_TYPE_INT, G_TYPE_INT); |
330 | g_signal_set_va_marshaller (signal_id: signals[RESIZE], |
331 | G_TYPE_FROM_CLASS (class), |
332 | va_marshaller: _gtk_marshal_VOID__INT_INTv); |
333 | } |
334 | |
335 | static void |
336 | gtk_drawing_area_init (GtkDrawingArea *darea) |
337 | { |
338 | gtk_widget_set_focusable (GTK_WIDGET (darea), FALSE); |
339 | } |
340 | |
341 | /** |
342 | * gtk_drawing_area_new: |
343 | * |
344 | * Creates a new drawing area. |
345 | * |
346 | * Returns: a new `GtkDrawingArea` |
347 | */ |
348 | GtkWidget* |
349 | gtk_drawing_area_new (void) |
350 | { |
351 | return g_object_new (GTK_TYPE_DRAWING_AREA, NULL); |
352 | } |
353 | |
354 | /** |
355 | * gtk_drawing_area_set_content_width: (attributes org.gtk.Method.set_property=content-width) |
356 | * @self: a `GtkDrawingArea` |
357 | * @width: the width of contents |
358 | * |
359 | * Sets the desired width of the contents of the drawing area. |
360 | * |
361 | * Note that because widgets may be allocated larger sizes than they |
362 | * requested, it is possible that the actual width passed to your draw |
363 | * function is larger than the width set here. You can use |
364 | * [method@Gtk.Widget.set_halign] to avoid that. |
365 | * |
366 | * If the width is set to 0 (the default), the drawing area may disappear. |
367 | */ |
368 | void |
369 | gtk_drawing_area_set_content_width (GtkDrawingArea *self, |
370 | int width) |
371 | { |
372 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
373 | |
374 | g_return_if_fail (GTK_IS_DRAWING_AREA (self)); |
375 | g_return_if_fail (width >= 0); |
376 | |
377 | if (priv->content_width == width) |
378 | return; |
379 | |
380 | priv->content_width = width; |
381 | |
382 | gtk_widget_queue_resize (GTK_WIDGET (self)); |
383 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_CONTENT_WIDTH]); |
384 | } |
385 | |
386 | /** |
387 | * gtk_drawing_area_get_content_width: (attributes org.gtk.Method.get_property=content-width) |
388 | * @self: a `GtkDrawingArea` |
389 | * |
390 | * Retrieves the content width of the `GtkDrawingArea`. |
391 | * |
392 | * Returns: The width requested for content of the drawing area |
393 | */ |
394 | int |
395 | gtk_drawing_area_get_content_width (GtkDrawingArea *self) |
396 | { |
397 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
398 | |
399 | g_return_val_if_fail (GTK_IS_DRAWING_AREA (self), 0); |
400 | |
401 | return priv->content_width; |
402 | } |
403 | |
404 | /** |
405 | * gtk_drawing_area_set_content_height: (attributes org.gtk.Method.set_property=content-height) |
406 | * @self: a `GtkDrawingArea` |
407 | * @height: the height of contents |
408 | * |
409 | * Sets the desired height of the contents of the drawing area. |
410 | * |
411 | * Note that because widgets may be allocated larger sizes than they |
412 | * requested, it is possible that the actual height passed to your draw |
413 | * function is larger than the height set here. You can use |
414 | * [method@Gtk.Widget.set_valign] to avoid that. |
415 | * |
416 | * If the height is set to 0 (the default), the drawing area may disappear. |
417 | */ |
418 | void |
419 | gtk_drawing_area_set_content_height (GtkDrawingArea *self, |
420 | int height) |
421 | { |
422 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
423 | |
424 | g_return_if_fail (GTK_IS_DRAWING_AREA (self)); |
425 | g_return_if_fail (height >= 0); |
426 | |
427 | if (priv->content_height == height) |
428 | return; |
429 | |
430 | priv->content_height = height; |
431 | |
432 | gtk_widget_queue_resize (GTK_WIDGET (self)); |
433 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_CONTENT_HEIGHT]); |
434 | } |
435 | |
436 | /** |
437 | * gtk_drawing_area_get_content_height: (attributes org.gtk.Method.get_property=content-height) |
438 | * @self: a `GtkDrawingArea` |
439 | * |
440 | * Retrieves the content height of the `GtkDrawingArea`. |
441 | * |
442 | * Returns: The height requested for content of the drawing area |
443 | */ |
444 | int |
445 | gtk_drawing_area_get_content_height (GtkDrawingArea *self) |
446 | { |
447 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
448 | |
449 | g_return_val_if_fail (GTK_IS_DRAWING_AREA (self), 0); |
450 | |
451 | return priv->content_height; |
452 | } |
453 | |
454 | /** |
455 | * gtk_drawing_area_set_draw_func: |
456 | * @self: a `GtkDrawingArea` |
457 | * @draw_func: (nullable): callback that lets you draw |
458 | * the drawing area's contents |
459 | * @user_data: (closure): user data passed to @draw_func |
460 | * @destroy: destroy notifier for @user_data |
461 | * |
462 | * Setting a draw function is the main thing you want to do when using |
463 | * a drawing area. |
464 | * |
465 | * The draw function is called whenever GTK needs to draw the contents |
466 | * of the drawing area to the screen. |
467 | * |
468 | * The draw function will be called during the drawing stage of GTK. |
469 | * In the drawing stage it is not allowed to change properties of any |
470 | * GTK widgets or call any functions that would cause any properties |
471 | * to be changed. You should restrict yourself exclusively to drawing |
472 | * your contents in the draw function. |
473 | * |
474 | * If what you are drawing does change, call [method@Gtk.Widget.queue_draw] |
475 | * on the drawing area. This will cause a redraw and will call @draw_func again. |
476 | */ |
477 | void |
478 | gtk_drawing_area_set_draw_func (GtkDrawingArea *self, |
479 | GtkDrawingAreaDrawFunc draw_func, |
480 | gpointer user_data, |
481 | GDestroyNotify destroy) |
482 | { |
483 | GtkDrawingAreaPrivate *priv = gtk_drawing_area_get_instance_private (self); |
484 | |
485 | g_return_if_fail (GTK_IS_DRAWING_AREA (self)); |
486 | |
487 | if (priv->draw_func_target_destroy_notify != NULL) |
488 | priv->draw_func_target_destroy_notify (priv->draw_func_target); |
489 | |
490 | priv->draw_func = draw_func; |
491 | priv->draw_func_target = user_data; |
492 | priv->draw_func_target_destroy_notify = destroy; |
493 | |
494 | gtk_widget_queue_draw (GTK_WIDGET (self)); |
495 | } |
496 | |