/* * Copyright © 2018 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Benjamin Otte */ #include "config.h" #include "updatesoverlay.h" #include "gtkintl.h" #include "gtkwidget.h" #include "gtknative.h" #include "gsk/gskrendernodeprivate.h" /* duration before we start fading in us */ #define GDK_DRAW_REGION_MIN_DURATION 50 * 1000 /* duration when fade is finished in us */ #define GDK_DRAW_REGION_MAX_DURATION 200 * 1000 typedef struct { gint64 timestamp; cairo_region_t *region; } GtkUpdate; typedef struct { GQueue *updates; GskRenderNode *last; GtkWidget *widget; guint tick_callback; gulong unmap_callback; } GtkWidgetUpdates; struct _GtkUpdatesOverlay { GtkInspectorOverlay parent_instance; GHashTable *toplevels; /* widget => GtkWidgetUpdates */ }; struct _GtkUpdatesOverlayClass { GtkInspectorOverlayClass parent_class; }; G_DEFINE_TYPE (GtkUpdatesOverlay, gtk_updates_overlay, GTK_TYPE_INSPECTOR_OVERLAY) static void gtk_update_free (gpointer data) { GtkUpdate *region = data; cairo_region_destroy (region->region); g_slice_free (GtkUpdate, region); } static void gtk_widget_updates_free (gpointer data) { GtkWidgetUpdates *updates = data; g_queue_free_full (updates->updates, gtk_update_free); g_clear_pointer (&updates->last, gsk_render_node_unref); g_signal_handler_disconnect (updates->widget, updates->unmap_callback); if (updates->tick_callback) gtk_widget_remove_tick_callback (updates->widget, updates->tick_callback); updates->tick_callback = 0; g_slice_free (GtkWidgetUpdates, updates); } static void gtk_widget_updates_unmap_widget (GtkWidget *widget, GtkUpdatesOverlay *self) { g_hash_table_remove (self->toplevels, widget); } static gboolean gtk_widget_updates_tick (GtkWidget *widget, GdkFrameClock *clock, gpointer data) { GtkWidgetUpdates *updates = data; GtkUpdate *draw; gint64 now; now = gdk_frame_clock_get_frame_time (clock); for (draw = g_queue_pop_tail (updates->updates); draw != NULL && (now - draw->timestamp >= GDK_DRAW_REGION_MAX_DURATION); draw = g_queue_pop_tail (updates->updates)) { gtk_update_free (draw); } gdk_surface_queue_expose (gtk_native_get_surface (gtk_widget_get_native (widget))); if (draw) { g_queue_push_tail (updates->updates, draw); return G_SOURCE_CONTINUE; } else { updates->tick_callback = 0; return G_SOURCE_REMOVE; } } static GtkWidgetUpdates * gtk_update_overlay_lookup_for_widget (GtkUpdatesOverlay *self, GtkWidget *widget, gboolean create) { GtkWidgetUpdates *updates = g_hash_table_lookup (self->toplevels, widget); if (updates || !create) return updates; updates = g_slice_new0 (GtkWidgetUpdates); updates->updates = g_queue_new (); updates->widget = widget; updates->unmap_callback = g_signal_connect (widget, "unmap", G_CALLBACK (gtk_widget_updates_unmap_widget), self); g_hash_table_insert (self->toplevels, g_object_ref (widget), updates); return updates; } static void gtk_widget_updates_add (GtkWidgetUpdates *updates, gint64 timestamp, cairo_region_t *region) { GtkUpdate *update; GList *l; update = g_slice_new0 (GtkUpdate); update->timestamp = timestamp; update->region = region; for (l = g_queue_peek_head_link (updates->updates); l != NULL; l = l->next) { GtkUpdate *u = l->data; cairo_region_subtract (u->region, region); } g_queue_push_head (updates->updates, update); if (updates->tick_callback == 0) updates->tick_callback = gtk_widget_add_tick_callback (updates->widget, gtk_widget_updates_tick, updates, NULL); } static void gtk_updates_overlay_snapshot (GtkInspectorOverlay *overlay, GtkSnapshot *snapshot, GskRenderNode *node, GtkWidget *widget) { GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (overlay); GtkWidgetUpdates *updates; GtkUpdate *draw; gint64 now; GList *l; double native_x, native_y; if (!GTK_IS_NATIVE (widget)) return; /* The coordinates we're getting from GdkSurface API are in GdkSurface coordinate spaces, * but we're snapshotting in widget space, so we need to transform */ gtk_native_get_surface_transform (GTK_NATIVE (widget), &native_x, &native_y); updates = gtk_update_overlay_lookup_for_widget (self, widget, TRUE); now = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)); if (updates->last) { cairo_region_t *diff; diff = cairo_region_create (); gsk_render_node_diff (updates->last, node, diff); if (cairo_region_is_empty (diff)) cairo_region_destroy (diff); else gtk_widget_updates_add (updates, now, diff); } else { cairo_region_t *region; graphene_rect_t bounds; gsk_render_node_get_bounds (node, &bounds); region = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { floor (bounds.origin.x), floor (bounds.origin.y), ceil (bounds.origin.x + bounds.size.width) - floor (bounds.origin.x), ceil (bounds.origin.y + bounds.size.height) - floor (bounds.origin.y) }); gtk_widget_updates_add (updates, now, region); } g_clear_pointer (&updates->last, gsk_render_node_unref); updates->last = gsk_render_node_ref (node); for (l = g_queue_peek_head_link (updates->updates); l != NULL; l = l->next) { double progress; guint i; draw = l->data; if (now - draw->timestamp < GDK_DRAW_REGION_MIN_DURATION) progress = 0.0; else if (now - draw->timestamp < GDK_DRAW_REGION_MAX_DURATION) progress = (double) (now - draw->timestamp - GDK_DRAW_REGION_MIN_DURATION) / (GDK_DRAW_REGION_MAX_DURATION - GDK_DRAW_REGION_MIN_DURATION); else break; for (i = 0; i < cairo_region_num_rectangles (draw->region); i++) { GdkRectangle rect; cairo_region_get_rectangle (draw->region, i, &rect); gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 0, 0, 0.4 * (1 - progress) }, &GRAPHENE_RECT_INIT(rect.x - native_x, rect.y - native_y, rect.width, rect.height)); } } } static void gtk_updates_overlay_queue_draw (GtkInspectorOverlay *overlay) { GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (overlay); GHashTableIter iter; gpointer widget; g_hash_table_iter_init (&iter, self->toplevels); while (g_hash_table_iter_next (&iter, &widget, NULL)) gdk_surface_queue_expose (gtk_native_get_surface (gtk_widget_get_native (widget))); } static void gtk_updates_overlay_dispose (GObject *object) { GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (object); g_hash_table_unref (self->toplevels); G_OBJECT_CLASS (gtk_updates_overlay_parent_class)->dispose (object); } static void gtk_updates_overlay_class_init (GtkUpdatesOverlayClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkInspectorOverlayClass *overlay_class = GTK_INSPECTOR_OVERLAY_CLASS (klass); overlay_class->snapshot = gtk_updates_overlay_snapshot; overlay_class->queue_draw = gtk_updates_overlay_queue_draw; gobject_class->dispose = gtk_updates_overlay_dispose; } static void gtk_updates_overlay_init (GtkUpdatesOverlay *self) { self->toplevels = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, gtk_widget_updates_free); } GtkInspectorOverlay * gtk_updates_overlay_new (void) { return g_object_new (GTK_TYPE_UPDATES_OVERLAY, NULL); }