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 | |
35 | typedef struct { |
36 | gint64 timestamp; |
37 | cairo_region_t *region; |
38 | } GtkUpdate; |
39 | |
40 | typedef struct { |
41 | GQueue *updates; |
42 | GskRenderNode *last; |
43 | GtkWidget *widget; |
44 | guint tick_callback; |
45 | gulong unmap_callback; |
46 | } GtkWidgetUpdates; |
47 | |
48 | struct _GtkUpdatesOverlay |
49 | { |
50 | GtkInspectorOverlay parent_instance; |
51 | |
52 | GHashTable *toplevels; /* widget => GtkWidgetUpdates */ |
53 | }; |
54 | |
55 | struct _GtkUpdatesOverlayClass |
56 | { |
57 | GtkInspectorOverlayClass parent_class; |
58 | }; |
59 | |
60 | G_DEFINE_TYPE (GtkUpdatesOverlay, gtk_updates_overlay, GTK_TYPE_INSPECTOR_OVERLAY) |
61 | |
62 | static void |
63 | gtk_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 | |
71 | static void |
72 | gtk_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 | |
86 | static void |
87 | gtk_widget_updates_unmap_widget (GtkWidget *widget, |
88 | GtkUpdatesOverlay *self) |
89 | { |
90 | g_hash_table_remove (hash_table: self->toplevels, key: widget); |
91 | } |
92 | |
93 | static gboolean |
94 | gtk_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 | |
124 | static GtkWidgetUpdates * |
125 | gtk_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 | |
143 | static void |
144 | gtk_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 | |
164 | static void |
165 | gtk_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 | |
238 | static void |
239 | gtk_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 | |
250 | static void |
251 | gtk_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 | |
260 | static void |
261 | gtk_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 | |
272 | static void |
273 | gtk_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 | |
278 | GtkInspectorOverlay * |
279 | gtk_updates_overlay_new (void) |
280 | { |
281 | return g_object_new (GTK_TYPE_UPDATES_OVERLAY, NULL); |
282 | } |
283 | |
284 | |