1/*
2 * Copyright © 2018 Benjamin Otte
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.1 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 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkwidgetpaintableprivate.h"
23
24#include "gtkintl.h"
25#include "gtksnapshot.h"
26#include "gtkrendernodepaintableprivate.h"
27#include "gtkwidgetprivate.h"
28
29/**
30 * GtkWidgetPaintable:
31 *
32 * `GtkWidgetPaintable` is a `GdkPaintable` that displays the contents
33 * of a widget.
34 *
35 * `GtkWidgetPaintable` will also take care of the widget not being in a
36 * state where it can be drawn (like when it isn't shown) and just draw
37 * nothing or where it does not have a size (like when it is hidden) and
38 * report no size in that case.
39 *
40 * Of course, `GtkWidgetPaintable` allows you to monitor widgets for size
41 * changes by emitting the [signal@Gdk.Paintable::invalidate-size] signal
42 * whenever the size of the widget changes as well as for visual changes by
43 * emitting the [signal@Gdk.Paintable::invalidate-contents] signal whenever
44 * the widget changes.
45 *
46 * You can use a `GtkWidgetPaintable` everywhere a `GdkPaintable` is allowed,
47 * including using it on a `GtkPicture` (or one of its parents) that it was
48 * set on itself via gtk_picture_set_paintable(). The paintable will take care
49 * of recursion when this happens. If you do this however, ensure that the
50 * [property@Gtk.Picture:can-shrink] property is set to %TRUE or you might
51 * end up with an infinitely growing widget.
52 */
53struct _GtkWidgetPaintable
54{
55 GObject parent_instance;
56
57 GtkWidget *widget;
58 guint snapshot_count;
59
60 guint pending_update_cb; /* the idle source that updates the valid image to be the new current image */
61
62 GdkPaintable *current_image; /* the image that we are presenting */
63 GdkPaintable *pending_image; /* the image that we should be presenting */
64};
65
66struct _GtkWidgetPaintableClass
67{
68 GObjectClass parent_class;
69};
70
71enum {
72 PROP_0,
73 PROP_WIDGET,
74
75 N_PROPS,
76};
77
78static GParamSpec *properties[N_PROPS] = { NULL, };
79
80static void
81gtk_widget_paintable_paintable_snapshot (GdkPaintable *paintable,
82 GdkSnapshot *snapshot,
83 double width,
84 double height)
85{
86 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: paintable);
87
88 if (self->snapshot_count > 4)
89 return;
90 else if (self->snapshot_count > 0)
91 {
92 graphene_rect_t bounds;
93
94 gtk_snapshot_push_clip (snapshot,
95 bounds: &GRAPHENE_RECT_INIT(0, 0, width, height));
96
97 if (gtk_widget_compute_bounds (widget: self->widget, target: self->widget, out_bounds: &bounds))
98 {
99 gtk_snapshot_scale (snapshot, factor_x: width / bounds.size.width, factor_y: height / bounds.size.height);
100 gtk_snapshot_translate (snapshot, point: &bounds.origin);
101 }
102
103 gtk_widget_snapshot (widget: self->widget, snapshot);
104
105 gtk_snapshot_pop (snapshot);
106 }
107 else
108 {
109 gdk_paintable_snapshot (paintable: self->current_image, snapshot, width, height);
110 }
111}
112
113static GdkPaintable *
114gtk_widget_paintable_paintable_get_current_image (GdkPaintable *paintable)
115{
116 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: paintable);
117
118 return g_object_ref (self->current_image);
119}
120
121static int
122gtk_widget_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable)
123{
124 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: paintable);
125
126 return gdk_paintable_get_intrinsic_width (paintable: self->current_image);
127}
128
129static int
130gtk_widget_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable)
131{
132 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: paintable);
133
134 return gdk_paintable_get_intrinsic_height (paintable: self->current_image);
135}
136
137static void
138gtk_widget_paintable_paintable_init (GdkPaintableInterface *iface)
139{
140 iface->snapshot = gtk_widget_paintable_paintable_snapshot;
141 iface->get_current_image = gtk_widget_paintable_paintable_get_current_image;
142 iface->get_intrinsic_width = gtk_widget_paintable_paintable_get_intrinsic_width;
143 iface->get_intrinsic_height = gtk_widget_paintable_paintable_get_intrinsic_height;
144}
145
146G_DEFINE_TYPE_EXTENDED (GtkWidgetPaintable, gtk_widget_paintable, G_TYPE_OBJECT, 0,
147 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
148 gtk_widget_paintable_paintable_init))
149
150static void
151gtk_widget_paintable_set_property (GObject *object,
152 guint prop_id,
153 const GValue *value,
154 GParamSpec *pspec)
155
156{
157 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: object);
158
159 switch (prop_id)
160 {
161 case PROP_WIDGET:
162 gtk_widget_paintable_set_widget (self, widget: g_value_get_object (value));
163 break;
164
165 default:
166 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167 break;
168 }
169}
170
171static void
172gtk_widget_paintable_get_property (GObject *object,
173 guint prop_id,
174 GValue *value,
175 GParamSpec *pspec)
176{
177 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: object);
178
179 switch (prop_id)
180 {
181 case PROP_WIDGET:
182 g_value_set_object (value, v_object: self->widget);
183 break;
184
185 default:
186 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
187 break;
188 }
189}
190
191static void
192gtk_widget_paintable_unset_widget (GtkWidgetPaintable *self)
193{
194 if (self->widget == NULL)
195 return;
196
197 self->widget->priv->paintables = g_slist_remove (list: self->widget->priv->paintables,
198 data: self);
199
200 self->widget = NULL;
201
202 g_clear_object (&self->pending_image);
203 if (self->pending_update_cb)
204 {
205 g_source_remove (tag: self->pending_update_cb);
206 self->pending_update_cb = 0;
207 }
208}
209
210static void
211gtk_widget_paintable_dispose (GObject *object)
212{
213 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: object);
214
215 gtk_widget_paintable_unset_widget (self);
216
217 G_OBJECT_CLASS (gtk_widget_paintable_parent_class)->dispose (object);
218}
219
220static void
221gtk_widget_paintable_finalize (GObject *object)
222{
223 GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (ptr: object);
224
225 g_object_unref (object: self->current_image);
226
227 G_OBJECT_CLASS (gtk_widget_paintable_parent_class)->finalize (object);
228}
229
230static void
231gtk_widget_paintable_class_init (GtkWidgetPaintableClass *klass)
232{
233 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
234
235 gobject_class->get_property = gtk_widget_paintable_get_property;
236 gobject_class->set_property = gtk_widget_paintable_set_property;
237 gobject_class->dispose = gtk_widget_paintable_dispose;
238 gobject_class->finalize = gtk_widget_paintable_finalize;
239
240 /**
241 * GtkWidgetPaintable:widget: (attributes org.gtk.Property.get=gtk_widget_paintable_get_widget org.gtk.Property.set=gtk_widget_paintable_set_widget)
242 *
243 * The observed widget or %NULL if none.
244 */
245 properties[PROP_WIDGET] =
246 g_param_spec_object (name: "widget",
247 P_("Widget"),
248 P_("Observed widget"),
249 GTK_TYPE_WIDGET,
250 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
251
252 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
253}
254
255static void
256gtk_widget_paintable_init (GtkWidgetPaintable *self)
257{
258 self->current_image = gdk_paintable_new_empty (intrinsic_width: 0, intrinsic_height: 0);
259}
260
261/**
262 * gtk_widget_paintable_new:
263 * @widget: (nullable) (transfer none): a `GtkWidget`
264 *
265 * Creates a new widget paintable observing the given widget.
266 *
267 * Returns: (transfer full) (type GtkWidgetPaintable): a new `GtkWidgetPaintable`
268 */
269GdkPaintable *
270gtk_widget_paintable_new (GtkWidget *widget)
271{
272 g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
273
274 return g_object_new (GTK_TYPE_WIDGET_PAINTABLE,
275 first_property_name: "widget", widget,
276 NULL);
277}
278
279static GdkPaintable *
280gtk_widget_paintable_snapshot_widget (GtkWidgetPaintable *self)
281{
282 graphene_rect_t bounds;
283
284 if (self->widget == NULL)
285 return gdk_paintable_new_empty (intrinsic_width: 0, intrinsic_height: 0);
286
287 if (!gtk_widget_compute_bounds (widget: self->widget, target: self->widget, out_bounds: &bounds))
288 return gdk_paintable_new_empty (intrinsic_width: 0, intrinsic_height: 0);
289
290 if (self->widget->priv->render_node == NULL)
291 return gdk_paintable_new_empty (intrinsic_width: bounds.size.width, intrinsic_height: bounds.size.height);
292
293 return gtk_render_node_paintable_new (node: self->widget->priv->render_node, bounds: &bounds);
294}
295
296/**
297 * gtk_widget_paintable_get_widget: (attributes org.gtk.Method.get_property=widget)
298 * @self: a `GtkWidgetPaintable`
299 *
300 * Returns the widget that is observed or %NULL if none.
301 *
302 * Returns: (transfer none) (nullable): the observed widget.
303 */
304GtkWidget *
305gtk_widget_paintable_get_widget (GtkWidgetPaintable *self)
306{
307 g_return_val_if_fail (GTK_IS_WIDGET_PAINTABLE (self), NULL);
308
309 return self->widget;
310}
311
312/**
313 * gtk_widget_paintable_set_widget: (attributes org.gtk.Method.set_property=widget)
314 * @self: a `GtkWidgetPaintable`
315 * @widget: (nullable): the widget to observe
316 *
317 * Sets the widget that should be observed.
318 */
319void
320gtk_widget_paintable_set_widget (GtkWidgetPaintable *self,
321 GtkWidget *widget)
322{
323
324 g_return_if_fail (GTK_IS_WIDGET_PAINTABLE (self));
325 g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
326
327 if (self->widget == widget)
328 return;
329
330 gtk_widget_paintable_unset_widget (self);
331
332 /* We do not ref the widget to not cause ref cycles when a widget
333 * is told to observe itself or one of its parent.
334 */
335 self->widget = widget;
336
337 if (widget)
338 widget->priv->paintables = g_slist_prepend (list: widget->priv->paintables, data: self);
339
340 g_object_unref (object: self->current_image);
341 self->current_image = gtk_widget_paintable_snapshot_widget (self);
342
343 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_WIDGET]);
344 gdk_paintable_invalidate_size (paintable: GDK_PAINTABLE (ptr: self));
345 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: self));
346}
347
348static gboolean
349gtk_widget_paintable_update_func (gpointer data)
350{
351 GtkWidgetPaintable *self = data;
352 GdkPaintable *old_image;
353
354 if (self->current_image != self->pending_image)
355 {
356 old_image = self->current_image;
357 self->current_image = self->pending_image;
358 self->pending_image = NULL;
359 self->pending_update_cb = 0;
360
361 if (gdk_paintable_get_intrinsic_width (paintable: self->current_image) != gdk_paintable_get_intrinsic_width (paintable: old_image) ||
362 gdk_paintable_get_intrinsic_height (paintable: self->current_image) != gdk_paintable_get_intrinsic_height (paintable: old_image))
363 gdk_paintable_invalidate_size (paintable: GDK_PAINTABLE (ptr: self));
364
365 g_object_unref (object: old_image);
366
367 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: self));
368 }
369 else
370 {
371 g_clear_object (&self->pending_image);
372 self->pending_update_cb = 0;
373 }
374
375 return G_SOURCE_REMOVE;
376}
377
378void
379gtk_widget_paintable_update_image (GtkWidgetPaintable *self)
380{
381 GdkPaintable *pending_image;
382
383 if (self->pending_update_cb == 0)
384 {
385 self->pending_update_cb = g_idle_add_full (G_PRIORITY_HIGH,
386 function: gtk_widget_paintable_update_func,
387 data: self,
388 NULL);
389 }
390
391 pending_image = gtk_widget_paintable_snapshot_widget (self);
392 g_set_object (&self->pending_image, pending_image);
393 g_object_unref (object: pending_image);
394}
395
396void
397gtk_widget_paintable_push_snapshot_count (GtkWidgetPaintable *self)
398{
399 self->snapshot_count++;
400}
401
402void
403gtk_widget_paintable_pop_snapshot_count (GtkWidgetPaintable *self)
404{
405 self->snapshot_count--;
406}
407

source code of gtk/gtk/gtkwidgetpaintable.c