From 2d10c2568c0e2b6bdf4de4a77ea8f47b4f6c213a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 22 Mar 2018 20:47:28 +0100 Subject: [PATCH] gtk: Add GtkWidgetPaintable A GtkWidgetPaintable is a paintable that observes a given GtkWidget and renders that widget into a paintable. --- docs/reference/gtk/gtk4-sections.txt | 18 ++ gtk/gtk.h | 1 + gtk/gtkwidget.c | 40 +++- gtk/gtkwidgetpaintable.c | 317 +++++++++++++++++++++++++++ gtk/gtkwidgetpaintable.h | 43 ++++ gtk/gtkwidgetprivate.h | 2 + gtk/meson.build | 2 + 7 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 gtk/gtkwidgetpaintable.c create mode 100644 gtk/gtkwidgetpaintable.h diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 83d091b693..f855e16ff2 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4201,6 +4201,24 @@ gtk_snapshot_render_layout gtk_snapshot_render_insertion_cursor +
+gtkwidgetpaintable +GtkWidgetPaintable +gtk_widget_paintable_new +gtk_widget_paintable_get_widget +gtk_widget_paintable_set_widget + + +GTK_WIDGET_PAINTABLE +GTK_IS_WIDGET_PAINTABLE +GTK_TYPE_WIDGET_PAINTABLE +GTK_WIDGET_PAINTABLE_CLASS +GTK_IS_WIDGET_PAINTABLE_CLASS +GTK_WIDGET_PAINTABLE_GET_CLASS + +gtk_widget_paintable_get_type +
+
gtkwidget GtkWidget diff --git a/gtk/gtk.h b/gtk/gtk.h index b6f73e6108..0775fb04d8 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -232,6 +232,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index c6f895c64c..8b99176ee1 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -65,6 +65,7 @@ #include "gtktooltipprivate.h" #include "gtktypebuiltins.h" #include "gtkversion.h" +#include "gtkwidgetpaintable.h" #include "gtkwidgetpathprivate.h" #include "gtkwindowgroup.h" #include "gtkwindowprivate.h" @@ -4020,6 +4021,29 @@ gtk_widget_queue_draw_area (GtkWidget *widget, cairo_region_destroy (region); } +static void +gtk_widget_invalidate_paintable_contents (GtkWidget *widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GSList *l; + + if (!_gtk_widget_is_drawable (widget)) + return; + + for (l = priv->paintables; l; l = l->next) + gdk_paintable_invalidate_contents (l->data); +} + +static void +gtk_widget_invalidate_paintable_size (GtkWidget *widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GSList *l; + + for (l = priv->paintables; l; l = l->next) + gdk_paintable_invalidate_size (l->data); +} + static void gtk_widget_real_queue_draw (GtkWidget *widget) { @@ -4032,6 +4056,7 @@ gtk_widget_real_queue_draw (GtkWidget *widget) priv->draw_needed = TRUE; g_clear_pointer (&priv->render_node, gsk_render_node_unref); + gtk_widget_invalidate_paintable_contents (widget); } } @@ -4122,6 +4147,8 @@ gtk_widget_queue_resize_internal (GtkWidget *widget) } } + gtk_widget_invalidate_paintable_size (widget); + if (_gtk_widget_is_toplevel (widget) && GTK_IS_CONTAINER (widget)) { gtk_container_queue_resize_handler (GTK_CONTAINER (widget)); @@ -6937,6 +6964,7 @@ _gtk_widget_set_visible_flag (GtkWidget *widget, memset (&priv->clip, 0, sizeof (priv->clip)); memset (&priv->allocated_size, 0, sizeof (priv->allocated_size)); priv->allocated_size_baseline = 0; + gtk_widget_invalidate_paintable_size (widget); } } @@ -8883,6 +8911,9 @@ gtk_widget_dispose (GObject *object) else if (_gtk_widget_get_visible (widget)) gtk_widget_hide (widget); + while (priv->paintables) + gtk_widget_paintable_set_widget (priv->paintables->data, NULL); + priv->visible = FALSE; if (_gtk_widget_get_realized (widget)) gtk_widget_unrealize (widget); @@ -9196,6 +9227,8 @@ gtk_widget_real_unmap (GtkWidget *widget) gtk_widget_unmap (child); } + gtk_widget_invalidate_paintable_contents (widget); + gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE); @@ -13988,8 +14021,11 @@ gtk_widget_snapshot (GtkWidget *widget, if (priv->draw_needed) { - g_assert (priv->render_node == NULL); - priv->render_node = gtk_widget_create_render_node (widget, snapshot); + GskRenderNode *render_node; + render_node = gtk_widget_create_render_node (widget, snapshot); + /* This can happen when nested drawing happens and a widget contains itself */ + g_clear_pointer (&priv->render_node, gsk_render_node_unref); + priv->render_node = render_node; priv->draw_needed = FALSE; } diff --git a/gtk/gtkwidgetpaintable.c b/gtk/gtkwidgetpaintable.c new file mode 100644 index 0000000000..be167d6005 --- /dev/null +++ b/gtk/gtkwidgetpaintable.c @@ -0,0 +1,317 @@ +/* + * 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 "gtkwidgetpaintable.h" + +#include "gtkintl.h" +#include "gtksnapshot.h" +#include "gtkwidgetprivate.h" + +/** + * SECTION:gtkwidgetpaintable + * @Short_description: Drawing a widget elsewhere + * @Title: GtkWidgetPaintable + * @see_also: #GtkWidget, #GdkPaintable + * + * #GtkWidgetPaintable is an implementation of the #GdkPaintable interface + * that allows displaying the contents of a #GtkWidget. + * + * #GtkWidgetPaintable will also take care of the widget not being in a + * state where it can be drawn (like when it isn't shown) and just draw + * nothing or where it does not have a size (like when it is hidden) and + * report no size in that case. + * + * Of course, #GtkWidgetPaintable allows you to monitor widgets for size + * changes by emitting the GdkPaintable::invalidate-size signal whenever + * the size of the widget changes as well as for visual changes by + * emitting the GdkPaintable::invalidate-contents signal whenever the + * widget changes. + * + * You can of course use a #GtkWidgetPaintable everywhere a + * #GdkPaintable is allowed, including using it on a #GtkImage (or one + * of its parents) that it was set on itself via + * gtk_image_set_from_paintable(). The paintable will take care of recursion + * when this happens. + * If you do this however, make sure to set the GtkImage:can-shrink property + * to %TRUE or you might end up with an infinitely growing image. + */ +struct _GtkWidgetPaintable +{ + GObject parent_instance; + + GtkWidget *widget; + guint loop_tracker; +}; + +struct _GtkWidgetPaintableClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_WIDGET, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_widget_paintable_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (paintable); + graphene_matrix_t transform; + + if (self->widget == NULL || + !_gtk_widget_is_drawable (self->widget) || + _gtk_widget_get_alloc_needed (self->widget)) + return; + + if (self->loop_tracker >= 5) + return; + self->loop_tracker++; + + /* need to clip because widgets may draw out of bounds */ + gtk_snapshot_push_clip (snapshot, + &GRAPHENE_RECT_INIT(0, 0, width, height), + "WidgetPaintableClip<%g,%g>", + width, height); + graphene_matrix_init_scale (&transform, + width / gtk_widget_get_allocated_width (self->widget), + height / gtk_widget_get_allocated_height (self->widget), + 1.0); + gtk_snapshot_push_transform (snapshot, + &transform, + "WidgetPaintableScale<%g,%g>", + width / gtk_widget_get_allocated_width (self->widget), + height / gtk_widget_get_allocated_height (self->widget)); + + gtk_widget_snapshot (self->widget, snapshot); + + gtk_snapshot_pop (snapshot); + gtk_snapshot_pop (snapshot); + + self->loop_tracker--; +} + +static GdkPaintable * +gtk_widget_paintable_paintable_get_current_image (GdkPaintable *paintable) +{ + g_warning ("FIXME: Implement once we can create paintables from render nodes"); + + return NULL; +} + +static int +gtk_widget_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (paintable); + + if (self->widget == NULL) + return 0; + + return gtk_widget_get_allocated_width (self->widget); +} + +static int +gtk_widget_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (paintable); + + if (self->widget == NULL) + return 0; + + return gtk_widget_get_allocated_height (self->widget); +} + +static void +gtk_widget_paintable_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gtk_widget_paintable_paintable_snapshot; + iface->get_current_image = gtk_widget_paintable_paintable_get_current_image; + iface->get_intrinsic_width = gtk_widget_paintable_paintable_get_intrinsic_width; + iface->get_intrinsic_height = gtk_widget_paintable_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_EXTENDED (GtkWidgetPaintable, gtk_widget_paintable, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gtk_widget_paintable_paintable_init)) + +static void +gtk_widget_paintable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (object); + + switch (prop_id) + { + case PROP_WIDGET: + gtk_widget_paintable_set_widget (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_widget_paintable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (object); + + switch (prop_id) + { + case PROP_WIDGET: + g_value_set_object (value, self->widget); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_widget_paintable_dispose (GObject *object) +{ + GtkWidgetPaintable *self = GTK_WIDGET_PAINTABLE (object); + + gtk_widget_paintable_set_widget (self, NULL); + + G_OBJECT_CLASS (gtk_widget_paintable_parent_class)->dispose (object); +} + +static void +gtk_widget_paintable_class_init (GtkWidgetPaintableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gtk_widget_paintable_get_property; + gobject_class->set_property = gtk_widget_paintable_set_property; + gobject_class->dispose = gtk_widget_paintable_dispose; + + /** + * GtkWidgetPaintable:widget + * + * The observed widget or %NULL if none. + */ + properties[PROP_WIDGET] = + g_param_spec_object ("widget", + P_("Widget"), + P_("Observed widget"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_widget_paintable_init (GtkWidgetPaintable *self) +{ +} + +/** + * gtk_widget_paintable_new: + * @widget: (allow-none) (transfer none): a #GtkWidget or %NULL + * + * Creates a new widget paintable observing the given widget. + * + * Returns: a new #GtkWidgetPaintable + **/ +GdkPaintable * +gtk_widget_paintable_new (GtkWidget *widget) +{ + g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL); + + return g_object_new (GTK_TYPE_WIDGET_PAINTABLE, + "widget", widget, + NULL); +} + +/** + * gtk_widget_paintable_get_widget: + * @self: a #GtkWidgetPaintable + * + * Returns the widget that is observed or %NULL + * if none. + * + * Returns: (transfer none) (nullable): the observed widget. + **/ +GtkWidget * +gtk_widget_paintable_get_widget (GtkWidgetPaintable *self) +{ + g_return_val_if_fail (GTK_IS_WIDGET_PAINTABLE (self), NULL); + + return self->widget; +} + +/** + * gtk_widget_paintable_set_widget: + * @self: a #GtkWidgetPaintable + * @widget: (allow-none): the widget to observe or %NULL + * + * Sets the widget that should be observed. + **/ +void +gtk_widget_paintable_set_widget (GtkWidgetPaintable *self, + GtkWidget *widget) +{ + + g_return_if_fail (GTK_IS_WIDGET_PAINTABLE (self)); + g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget)); + + if (self->widget == widget) + return; + + if (self->widget) + { + self->widget->priv->paintables = g_slist_remove (self->widget->priv->paintables, + self); + } + + /* We do not ref the widget to not cause ref cycles when a widget + * is told to observe itself or one of its parent. + */ + self->widget = widget; + + if (widget) + { + widget->priv->paintables = g_slist_prepend (widget->priv->paintables, + self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDGET]); + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); +} + + diff --git a/gtk/gtkwidgetpaintable.h b/gtk/gtkwidgetpaintable.h new file mode 100644 index 0000000000..3af7ddf137 --- /dev/null +++ b/gtk/gtkwidgetpaintable.h @@ -0,0 +1,43 @@ +/* + * 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 + */ + +#ifndef __GTK_WIDGET_PAINTABLE_H__ +#define __GTK_WIDGET_PAINTABLE_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_WIDGET_PAINTABLE (gtk_widget_paintable_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkWidgetPaintable, gtk_widget_paintable, GTK, WIDGET_PAINTABLE, GObject) + +GDK_AVAILABLE_IN_ALL +GdkPaintable * gtk_widget_paintable_new (GtkWidget *widget); + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_widget_paintable_get_widget (GtkWidgetPaintable *self); +GDK_AVAILABLE_IN_ALL +void gtk_widget_paintable_set_widget (GtkWidgetPaintable *self, + GtkWidget *widget); + +G_END_DECLS + +#endif /* __GTK_WIDGET_PAINTABLE_H__ */ diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 2c9286b12c..ed65646ee2 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -148,6 +148,8 @@ struct _GtkWidgetPrivate /* The render node we draw or %NULL if not yet created. */ GskRenderNode *render_node; + GSList *paintables; + /* The widget's surface or its parent surface if it does * not have a surface. (Which will be indicated by the * no_surface field being set). diff --git a/gtk/meson.build b/gtk/meson.build index 5dea256920..3cfbf38e0c 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -362,6 +362,7 @@ gtk_public_sources = files([ 'gtkvolumebutton.c', 'gtkwidget.c', 'gtkwidgetfocus.c', + 'gtkwidgetpaintable.c', 'gtkwidgetpath.c', 'gtkwindow.c', 'gtkwindowgroup.c', @@ -577,6 +578,7 @@ gtk_public_headers = files([ 'gtkviewport.h', 'gtkvolumebutton.h', 'gtkwidget.h', + 'gtkwidgetpaintable.h', 'gtkwidgetpath.h', 'gtkwindow.h', 'gtkwindowgroup.h', -- 2.30.2