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 "updatesoverlay.h"
23
24#include "gtkintl.h"
25#include "gtkwidget.h"
26#include "gtknative.h"
27
28#include "gsk/gskrendernodeprivate.h"
29
30/* duration before we start fading in us */
31#define GDK_DRAW_REGION_MIN_DURATION 50 * 1000
32/* duration when fade is finished in us */
33#define GDK_DRAW_REGION_MAX_DURATION 200 * 1000
34
35typedef struct {
36 gint64 timestamp;
37 cairo_region_t *region;
38} GtkUpdate;
39
40typedef struct {
41 GQueue *updates;
42 GskRenderNode *last;
43 GtkWidget *widget;
44 guint tick_callback;
45 gulong unmap_callback;
46} GtkWidgetUpdates;
47
48struct _GtkUpdatesOverlay
49{
50 GtkInspectorOverlay parent_instance;
51
52 GHashTable *toplevels; /* widget => GtkWidgetUpdates */
53};
54
55struct _GtkUpdatesOverlayClass
56{
57 GtkInspectorOverlayClass parent_class;
58};
59
60G_DEFINE_TYPE (GtkUpdatesOverlay, gtk_updates_overlay, GTK_TYPE_INSPECTOR_OVERLAY)
61
62static void
63gtk_update_free (gpointer data)
64{
65 GtkUpdate *region = data;
66
67 cairo_region_destroy (region: region->region);
68 g_slice_free (GtkUpdate, region);
69}
70
71static void
72gtk_widget_updates_free (gpointer data)
73{
74 GtkWidgetUpdates *updates = data;
75
76 g_queue_free_full (queue: updates->updates, free_func: gtk_update_free);
77 g_clear_pointer (&updates->last, gsk_render_node_unref);
78 g_signal_handler_disconnect (instance: updates->widget, handler_id: updates->unmap_callback);
79 if (updates->tick_callback)
80 gtk_widget_remove_tick_callback (widget: updates->widget, id: updates->tick_callback);
81 updates->tick_callback = 0;
82
83 g_slice_free (GtkWidgetUpdates, updates);
84}
85
86static void
87gtk_widget_updates_unmap_widget (GtkWidget *widget,
88 GtkUpdatesOverlay *self)
89{
90 g_hash_table_remove (hash_table: self->toplevels, key: widget);
91}
92
93static gboolean
94gtk_widget_updates_tick (GtkWidget *widget,
95 GdkFrameClock *clock,
96 gpointer data)
97{
98 GtkWidgetUpdates *updates = data;
99 GtkUpdate *draw;
100 gint64 now;
101
102 now = gdk_frame_clock_get_frame_time (frame_clock: clock);
103
104 for (draw = g_queue_pop_tail (queue: updates->updates);
105 draw != NULL && (now - draw->timestamp >= GDK_DRAW_REGION_MAX_DURATION);
106 draw = g_queue_pop_tail (queue: updates->updates))
107 {
108 gtk_update_free (data: draw);
109 }
110
111 gdk_surface_queue_render (surface: gtk_native_get_surface (self: gtk_widget_get_native (widget)));
112 if (draw)
113 {
114 g_queue_push_tail (queue: updates->updates, data: draw);
115 return G_SOURCE_CONTINUE;
116 }
117 else
118 {
119 updates->tick_callback = 0;
120 return G_SOURCE_REMOVE;
121 }
122}
123
124static GtkWidgetUpdates *
125gtk_update_overlay_lookup_for_widget (GtkUpdatesOverlay *self,
126 GtkWidget *widget,
127 gboolean create)
128{
129 GtkWidgetUpdates *updates = g_hash_table_lookup (hash_table: self->toplevels, key: widget);
130
131 if (updates || !create)
132 return updates;
133
134 updates = g_slice_new0 (GtkWidgetUpdates);
135 updates->updates = g_queue_new ();
136 updates->widget = widget;
137 updates->unmap_callback = g_signal_connect (widget, "unmap", G_CALLBACK (gtk_widget_updates_unmap_widget), self);
138
139 g_hash_table_insert (hash_table: self->toplevels, g_object_ref (widget), value: updates);
140 return updates;
141}
142
143static void
144gtk_widget_updates_add (GtkWidgetUpdates *updates,
145 gint64 timestamp,
146 cairo_region_t *region)
147{
148 GtkUpdate *update;
149 GList *l;
150
151 update = g_slice_new0 (GtkUpdate);
152 update->timestamp = timestamp;
153 update->region = region;
154 for (l = g_queue_peek_head_link (queue: updates->updates); l != NULL; l = l->next)
155 {
156 GtkUpdate *u = l->data;
157 cairo_region_subtract (dst: u->region, other: region);
158 }
159 g_queue_push_head (queue: updates->updates, data: update);
160 if (updates->tick_callback == 0)
161 updates->tick_callback = gtk_widget_add_tick_callback (widget: updates->widget, callback: gtk_widget_updates_tick, user_data: updates, NULL);
162}
163
164static void
165gtk_updates_overlay_snapshot (GtkInspectorOverlay *overlay,
166 GtkSnapshot *snapshot,
167 GskRenderNode *node,
168 GtkWidget *widget)
169{
170 GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (ptr: overlay);
171 GtkWidgetUpdates *updates;
172 GtkUpdate *draw;
173 gint64 now;
174 GList *l;
175
176 if (!GTK_IS_NATIVE (ptr: widget))
177 return;
178
179 updates = gtk_update_overlay_lookup_for_widget (self, widget, TRUE);
180 now = gdk_frame_clock_get_frame_time (frame_clock: gtk_widget_get_frame_clock (widget));
181
182 if (updates->last)
183 {
184 cairo_region_t *diff;
185
186 diff = cairo_region_create ();
187 gsk_render_node_diff (node1: updates->last, node2: node, region: diff);
188 if (cairo_region_is_empty (region: diff))
189 cairo_region_destroy (region: diff);
190 else
191 gtk_widget_updates_add (updates, timestamp: now, region: diff);
192 }
193 else
194 {
195 cairo_region_t *region;
196 graphene_rect_t bounds;
197
198 gsk_render_node_get_bounds (node, bounds: &bounds);
199 region = cairo_region_create_rectangle (rectangle: &(cairo_rectangle_int_t) {
200 floor (x: bounds.origin.x),
201 floor (x: bounds.origin.y),
202 ceil (x: bounds.origin.x + bounds.size.width) - floor (x: bounds.origin.x),
203 ceil (x: bounds.origin.y + bounds.size.height) - floor (x: bounds.origin.y)
204 });
205 gtk_widget_updates_add (updates, timestamp: now, region);
206 }
207 g_clear_pointer (&updates->last, gsk_render_node_unref);
208 updates->last = gsk_render_node_ref (node);
209
210 for (l = g_queue_peek_head_link (queue: updates->updates); l != NULL; l = l->next)
211 {
212 double progress;
213 guint i;
214
215 draw = l->data;
216
217 if (now - draw->timestamp < GDK_DRAW_REGION_MIN_DURATION)
218 progress = 0.0;
219 else if (now - draw->timestamp < GDK_DRAW_REGION_MAX_DURATION)
220 progress = (double) (now - draw->timestamp - GDK_DRAW_REGION_MIN_DURATION)
221 / (GDK_DRAW_REGION_MAX_DURATION - GDK_DRAW_REGION_MIN_DURATION);
222 else
223 break;
224
225 for (i = 0; i < cairo_region_num_rectangles (region: draw->region); i++)
226 {
227 GdkRectangle rect;
228
229 cairo_region_get_rectangle (region: draw->region, nth: i, rectangle: &rect);
230 gtk_snapshot_append_color (snapshot,
231 color: &(GdkRGBA) { 1, 0, 0, 0.4 * (1 - progress) },
232 bounds: &GRAPHENE_RECT_INIT(rect.x, rect.y,
233 rect.width, rect.height));
234 }
235 }
236}
237
238static void
239gtk_updates_overlay_queue_draw (GtkInspectorOverlay *overlay)
240{
241 GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (ptr: overlay);
242 GHashTableIter iter;
243 gpointer widget;
244
245 g_hash_table_iter_init (iter: &iter, hash_table: self->toplevels);
246 while (g_hash_table_iter_next (iter: &iter, key: &widget, NULL))
247 gdk_surface_queue_render (surface: gtk_native_get_surface (self: gtk_widget_get_native (widget)));
248}
249
250static void
251gtk_updates_overlay_dispose (GObject *object)
252{
253 GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (ptr: object);
254
255 g_hash_table_unref (hash_table: self->toplevels);
256
257 G_OBJECT_CLASS (gtk_updates_overlay_parent_class)->dispose (object);
258}
259
260static void
261gtk_updates_overlay_class_init (GtkUpdatesOverlayClass *klass)
262{
263 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
264 GtkInspectorOverlayClass *overlay_class = GTK_INSPECTOR_OVERLAY_CLASS (ptr: klass);
265
266 overlay_class->snapshot = gtk_updates_overlay_snapshot;
267 overlay_class->queue_draw = gtk_updates_overlay_queue_draw;
268
269 gobject_class->dispose = gtk_updates_overlay_dispose;
270}
271
272static void
273gtk_updates_overlay_init (GtkUpdatesOverlay *self)
274{
275 self->toplevels = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, key_destroy_func: g_object_unref, value_destroy_func: gtk_widget_updates_free);
276}
277
278GtkInspectorOverlay *
279gtk_updates_overlay_new (void)
280{
281 return g_object_new (GTK_TYPE_UPDATES_OVERLAY, NULL);
282}
283
284

source code of gtk/gtk/inspector/updatesoverlay.c