[gtk/wip/matthiasc/popup4] widget: Add surface relative position changed callback
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/matthiasc/popup4] widget: Add surface relative position changed callback
- Date: Mon, 22 Apr 2019 19:08:29 +0000 (UTC)
commit 898dacfb77807c00476bd24a93aa2e0c56965a74
Author: Jonas Ådahl <jadahl gmail com>
Date: Thu Apr 18 18:55:13 2019 +0200
widget: Add surface relative position changed callback
Added two new GtkWidget API:
* gtk_widget_add_position_changed_callback()
* gtk_widget_remove_position_changed_callback()
The intention is to let the user know when a widget position relative to
the surface changes. It works by calculating the surface relative
position during allocation, and notifying the callbacks if the upper
left corner of the widget bounds changed since last time. Each widget
adds itself as a listener to its parent widget, thus will be triggered
if a parent widget moves.
gtk/gtkwidget.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++
gtk/gtkwidget.h | 13 +++
gtk/gtkwidgetprivate.h | 8 ++
3 files changed, 276 insertions(+)
---
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 037146ee6b..9ae0c722a7 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -3587,6 +3587,257 @@ gtk_widget_disconnect_frame_clock (GtkWidget *widget)
}
}
+typedef struct _GtkPositionChangedCallbackInfo GtkPositionChangedCallbackInfo;
+
+struct _GtkPositionChangedCallbackInfo
+{
+ guint id;
+ GtkPositionChangedCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+static void
+position_changed_callback_info_destroy (GtkPositionChangedCallbackInfo *info)
+{
+ if (info->notify)
+ info->notify (info->user_data);
+
+ g_slice_free (GtkPositionChangedCallbackInfo, info);
+}
+
+static void
+notify_position_changed (GtkWidget *widget)
+{
+ GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+ GList *l;
+
+ for (l = priv->position_changed_callbacks; l;)
+ {
+ GtkPositionChangedCallbackInfo *info = l->data;
+ GList *l_next = l->next;
+
+ if (info->callback (widget, info->user_data) == G_SOURCE_REMOVE)
+ {
+ priv->position_changed_callbacks =
+ g_list_delete_link (priv->position_changed_callbacks, l);
+ position_changed_callback_info_destroy (info);
+ }
+
+ l = l_next;
+ }
+}
+
+static void
+destroy_position_changed_callbacks (GtkWidget *widget)
+{
+ GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+ GList *l;
+
+ for (l = priv->position_changed_callbacks; l;)
+ {
+ GtkPositionChangedCallbackInfo *info = l->data;
+ GList *l_next = l->next;
+
+ priv->position_changed_callbacks =
+ g_list_delete_link (priv->position_changed_callbacks, l);
+ position_changed_callback_info_destroy (info);
+
+ l = l_next;
+ }
+}
+
+static void
+sync_widget_surface_position (GtkWidget *widget)
+{
+ GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+ gboolean was_valid;
+ graphene_point_t prev_position;
+ GtkWidget *parent;
+ graphene_rect_t bounds;
+
+ parent = _gtk_widget_get_parent (widget);
+ while (parent && !_gtk_widget_get_has_surface (parent))
+ parent = _gtk_widget_get_parent (parent);
+
+ was_valid = priv->surface_relative_position_valid;
+ prev_position = priv->surface_relative_position;
+
+ if (gtk_widget_compute_bounds (widget, parent ? parent : widget, &bounds))
+ {
+ priv->surface_relative_position = bounds.origin;
+ priv->surface_relative_position_valid = TRUE;
+
+ if (!was_valid ||
+ !graphene_point_equal (&priv->surface_relative_position,
+ &prev_position))
+ notify_position_changed (widget);
+ }
+ else
+ {
+ priv->surface_relative_position_valid = FALSE;
+
+ if (was_valid)
+ notify_position_changed (widget);
+ }
+}
+
+static guint position_changed_callback_id;
+
+static gboolean
+parent_position_changed_cb (GtkWidget *parent,
+ gpointer user_data)
+{
+ GtkWidget *widget = GTK_WIDGET (user_data);
+
+ sync_widget_surface_position (widget);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+remove_parent_position_changed_listener (GtkWidget *widget)
+{
+ GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+
+ if (!priv->parent_position_changed_parent)
+ return;
+
+ gtk_widget_remove_position_changed_callback (priv->parent_position_changed_parent,
+ priv->parent_position_changed_id);
+ priv->parent_position_changed_id = 0;
+ g_clear_object (&priv->parent_position_changed_parent);
+}
+
+static void
+on_parent_changed (GtkWidget *widget)
+{
+ GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+ GtkWidget *parent;
+
+ if (priv->parent_position_changed_parent)
+ remove_parent_position_changed_listener (widget);
+
+ parent = _gtk_widget_get_parent (widget);
+ if (parent)
+ {
+ priv->parent_position_changed_id =
+ gtk_widget_add_position_changed_callback (parent,
+ parent_position_changed_cb,
+ widget,
+ NULL);
+ priv->parent_position_changed_parent = g_object_ref (parent);
+ }
+}
+
+/**
+ * gtk_widget_add_position_changed_callback:
+ * @widget: a #GtkWidget
+ * @callback: a function to call when the position changes
+ * @user_data: data to pass to @callback
+ * @notify: function to call to free @user_data when the callback is removed
+ *
+ * Invokes the callback whenever the surface relative position of the widget
+ * changes.
+ *
+ * Returns: an id for the connection of this callback. Remove the callback by
+ * passing the id returned from this funcction to
+ * gtk_widget_remove_position_changed_callback()
+ */
+guint
+gtk_widget_add_position_changed_callback (GtkWidget *widget,
+ GtkPositionChangedCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ GtkWidgetPrivate *priv;
+ GtkWidget *parent;
+ GtkPositionChangedCallbackInfo *info;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+ g_return_val_if_fail (callback, 0);
+
+ priv = gtk_widget_get_instance_private (widget);
+
+ parent = _gtk_widget_get_parent (widget);
+ if (parent && !priv->parent_position_changed_id)
+ {
+ priv->parent_position_changed_id =
+ gtk_widget_add_position_changed_callback (parent,
+ parent_position_changed_cb,
+ widget,
+ NULL);
+ priv->parent_position_changed_parent = g_object_ref (parent);
+ }
+
+ if (!priv->parent_changed_handler_id)
+ {
+ priv->parent_changed_handler_id =
+ g_signal_connect (widget, "notify::parent",
+ G_CALLBACK (on_parent_changed),
+ NULL);
+ }
+
+ if (!priv->position_changed_callbacks)
+ sync_widget_surface_position (widget);
+
+ info = g_slice_new0 (GtkPositionChangedCallbackInfo);
+
+ info->id = ++position_changed_callback_id;
+ info->callback = callback;
+ info->user_data = user_data;
+ info->notify = notify;
+
+ priv->position_changed_callbacks =
+ g_list_prepend (priv->position_changed_callbacks, info);
+
+ return info->id;
+}
+
+/**
+ * gtk_widget_remove_position_changed_callback:
+ * @widget: a #GtkWidget
+ * @id: an id returned by gtk_widget_add_position_changed_callback()
+ *
+ * Removes a position changed callback previously registered with
+ * gtk_widget_add_position_changed_callback().
+ */
+void
+gtk_widget_remove_position_changed_callback (GtkWidget *widget,
+ guint id)
+{
+ GtkWidgetPrivate *priv;
+ GList *l;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (id);
+
+ priv = gtk_widget_get_instance_private (widget);
+
+ for (l = priv->position_changed_callbacks; l; l = l->next)
+ {
+ GtkPositionChangedCallbackInfo *info = l->data;
+
+ if (info->id == id)
+ {
+ priv->position_changed_callbacks =
+ g_list_delete_link (priv->position_changed_callbacks, l);
+
+ position_changed_callback_info_destroy (info);
+ break;
+ }
+ }
+
+ if (!priv->position_changed_callbacks)
+ {
+ if (priv->parent_position_changed_parent)
+ remove_parent_position_changed_listener (widget);
+
+ g_signal_handler_disconnect (widget, priv->parent_changed_handler_id);
+ priv->parent_changed_handler_id = 0;
+ }
+}
+
/**
* gtk_widget_realize:
* @widget: a #GtkWidget
@@ -4201,6 +4452,9 @@ gtk_widget_allocate (GtkWidget *widget,
priv->transform = transform;
+ if (priv->position_changed_callbacks)
+ sync_widget_surface_position (widget);
+
if (!alloc_needed && !size_changed && !baseline_changed)
{
/* Still have to move the window... */
@@ -8119,6 +8373,7 @@ gtk_widget_real_destroy (GtkWidget *object)
gtk_grab_remove (widget);
destroy_tick_callbacks (widget);
+ destroy_position_changed_callbacks (widget);
}
static void
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index 151dde2fd0..9d29330f42 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -95,6 +95,9 @@ typedef gboolean (*GtkTickCallback) (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data);
+typedef gboolean (*GtkPositionChangedCallback) (GtkWidget *widget,
+ gpointer user_data);
+
/**
* GtkRequisition:
* @width: the widget’s desired width
@@ -877,6 +880,16 @@ GDK_AVAILABLE_IN_ALL
void gtk_widget_remove_tick_callback (GtkWidget *widget,
guint id);
+GDK_AVAILABLE_IN_ALL
+guint gtk_widget_add_position_changed_callback (GtkWidget *widget,
+ GtkPositionChangedCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_widget_remove_position_changed_callback (GtkWidget *widget,
+ guint id);
+
/**
* gtk_widget_class_bind_template_callback:
* @widget_class: a #GtkWidgetClass
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index 7409ee047b..aa5cf3ad81 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -116,6 +116,14 @@ struct _GtkWidgetPrivate
guint clock_tick_id;
GList *tick_callbacks;
+ /* Surface relative position updates callbacks */
+ guint parent_position_changed_id;
+ GtkWidget *parent_position_changed_parent;
+ gulong parent_changed_handler_id;
+ GList *position_changed_callbacks;
+ gboolean surface_relative_position_valid;
+ graphene_point_t surface_relative_position;
+
/* The widget's name. If the widget does not have a name
* (the name is NULL), then its name (as returned by
* "gtk_widget_get_name") is its class's name.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]