widget: Add surface relative transform changed callback

Added two new private GtkWidget API:

 * gtk_widget_add_surface_transform_changed_callback()
 * gtk_widget_remove_surface_transform_changed_callback()

The intention is to let the user know when a widget transform relative
to the surface changes. It works by calculating the surface relative
transform during allocation, and notifying the callbacks if it changed
since last time. Each widget adds itself as a listener to its parent
widget, thus will be triggered if a parents surface relative transform
changes.
This commit is contained in:
Jonas Ådahl 2019-04-18 18:55:13 +02:00
parent 91bbe6ef95
commit 477ad2505b
3 changed files with 278 additions and 1 deletions

View File

@ -713,6 +713,9 @@ static void gtk_widget_update_input_shape (GtkWidget *widget);
static gboolean gtk_widget_class_get_visible_by_default (GtkWidgetClass *widget_class);
static void remove_parent_surface_transform_changed_listener (GtkWidget *widget);
static void add_parent_surface_transform_changed_listener (GtkWidget *widget);
/* --- variables --- */
static gint GtkWidget_private_offset = 0;
@ -2905,6 +2908,9 @@ gtk_widget_root (GtkWidget *widget)
if (priv->context)
gtk_style_context_set_display (priv->context, gtk_root_get_display (priv->root));
if (priv->surface_transform_changed_callbacks)
add_parent_surface_transform_changed_listener (widget);
GTK_WIDGET_GET_CLASS (widget)->root (widget);
g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_ROOT]);
@ -2922,6 +2928,9 @@ gtk_widget_unroot (GtkWidget *widget)
g_assert (priv->root);
g_assert (!priv->realized);
if (priv->parent_surface_transform_changed_parent)
remove_parent_surface_transform_changed_listener (widget);
GTK_WIDGET_GET_CLASS (widget)->unroot (widget);
if (priv->context)
@ -3586,6 +3595,249 @@ gtk_widget_disconnect_frame_clock (GtkWidget *widget)
}
}
typedef struct _GtkSurfaceTransformChangedCallbackInfo GtkSurfaceTransformChangedCallbackInfo;
struct _GtkSurfaceTransformChangedCallbackInfo
{
guint id;
GtkSurfaceTransformChangedCallback callback;
gpointer user_data;
GDestroyNotify notify;
};
static void
surface_transform_changed_callback_info_destroy (GtkSurfaceTransformChangedCallbackInfo *info)
{
if (info->notify)
info->notify (info->user_data);
g_slice_free (GtkSurfaceTransformChangedCallbackInfo, info);
}
static void
notify_surface_transform_changed (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
graphene_matrix_t *surface_transform;
GList *l;
if (priv->cached_surface_transform_valid)
surface_transform = &priv->cached_surface_transform;
else
surface_transform = NULL;
for (l = priv->surface_transform_changed_callbacks; l;)
{
GtkSurfaceTransformChangedCallbackInfo *info = l->data;
GList *l_next = l->next;
if (info->callback (widget,
surface_transform,
info->user_data) == G_SOURCE_REMOVE)
{
priv->surface_transform_changed_callbacks =
g_list_delete_link (priv->surface_transform_changed_callbacks, l);
surface_transform_changed_callback_info_destroy (info);
}
l = l_next;
}
}
static void
destroy_surface_transform_changed_callbacks (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
GList *l;
for (l = priv->surface_transform_changed_callbacks; l;)
{
GtkSurfaceTransformChangedCallbackInfo *info = l->data;
GList *l_next = l->next;
priv->surface_transform_changed_callbacks =
g_list_delete_link (priv->surface_transform_changed_callbacks, l);
surface_transform_changed_callback_info_destroy (info);
l = l_next;
}
}
static void
sync_widget_surface_transform (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
gboolean was_valid;
graphene_matrix_t prev_transform;
was_valid = priv->cached_surface_transform_valid;
prev_transform = priv->cached_surface_transform;
if (GTK_IS_ROOT (widget))
{
gsk_transform_to_matrix (priv->transform,
&priv->cached_surface_transform);
priv->cached_surface_transform_valid = TRUE;
}
else if (!priv->root)
{
priv->cached_surface_transform_valid = FALSE;
}
else if (gtk_widget_compute_transform (widget, GTK_WIDGET (priv->root),
&priv->cached_surface_transform))
{
priv->cached_surface_transform_valid = TRUE;
}
else
{
g_warning ("Could not compute surface transform");
priv->cached_surface_transform_valid = FALSE;
}
if (was_valid != priv->cached_surface_transform_valid ||
(was_valid && priv->cached_surface_transform_valid &&
!graphene_matrix_equal (&priv->cached_surface_transform,
&prev_transform)))
notify_surface_transform_changed (widget);
}
static guint surface_transform_changed_callback_id;
static gboolean
parent_surface_transform_changed_cb (GtkWidget *parent,
const graphene_matrix_t *transform,
gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET (user_data);
sync_widget_surface_transform (widget);
return G_SOURCE_CONTINUE;
}
static void
remove_parent_surface_transform_changed_listener (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
g_assert (priv->parent_surface_transform_changed_parent);
gtk_widget_remove_surface_transform_changed_callback (
priv->parent_surface_transform_changed_parent,
priv->parent_surface_transform_changed_id);
priv->parent_surface_transform_changed_id = 0;
g_clear_object (&priv->parent_surface_transform_changed_parent);
}
static void
add_parent_surface_transform_changed_listener (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
GtkWidget *parent;
g_assert (!priv->parent_surface_transform_changed_parent);
parent = priv->parent;
priv->parent_surface_transform_changed_id =
gtk_widget_add_surface_transform_changed_callback (
parent,
parent_surface_transform_changed_cb,
widget,
NULL);
priv->parent_surface_transform_changed_parent = g_object_ref (parent);
}
/**
* gtk_widget_add_surface_transform_changed_callback:
* @widget: a #GtkWidget
* @callback: a function to call when the surface transform 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 transform 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_surface_transform_changed_callback()
*/
guint
gtk_widget_add_surface_transform_changed_callback (GtkWidget *widget,
GtkSurfaceTransformChangedCallback callback,
gpointer user_data,
GDestroyNotify notify)
{
GtkWidgetPrivate *priv;
GtkSurfaceTransformChangedCallbackInfo *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);
if (priv->parent && !priv->parent_surface_transform_changed_id)
add_parent_surface_transform_changed_listener (widget);
if (!priv->surface_transform_changed_callbacks)
sync_widget_surface_transform (widget);
info = g_slice_new0 (GtkSurfaceTransformChangedCallbackInfo);
info->id = ++surface_transform_changed_callback_id;
info->callback = callback;
info->user_data = user_data;
info->notify = notify;
priv->surface_transform_changed_callbacks =
g_list_prepend (priv->surface_transform_changed_callbacks, info);
return info->id;
}
/**
* gtk_widget_remove_surface_transform_changed_callback:
* @widget: a #GtkWidget
* @id: an id returned by gtk_widget_add_surface_transform_changed_callback()
*
* Removes a surface transform changed callback previously registered with
* gtk_widget_add_surface_transform_changed_callback().
*/
void
gtk_widget_remove_surface_transform_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->surface_transform_changed_callbacks; l; l = l->next)
{
GtkSurfaceTransformChangedCallbackInfo *info = l->data;
if (info->id == id)
{
priv->surface_transform_changed_callbacks =
g_list_delete_link (priv->surface_transform_changed_callbacks, l);
surface_transform_changed_callback_info_destroy (info);
break;
}
}
if (!priv->surface_transform_changed_callbacks)
{
if (priv->parent_surface_transform_changed_parent)
remove_parent_surface_transform_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
@ -4200,6 +4452,9 @@ gtk_widget_allocate (GtkWidget *widget,
priv->transform = transform;
if (priv->surface_transform_changed_callbacks)
sync_widget_surface_transform (widget);
if (!alloc_needed && !size_changed && !baseline_changed)
{
/* Still have to move the window... */
@ -8125,6 +8380,7 @@ gtk_widget_real_destroy (GtkWidget *object)
gtk_grab_remove (widget);
destroy_tick_callbacks (widget);
destroy_surface_transform_changed_callbacks (widget);
}
static void

View File

@ -41,6 +41,10 @@
G_BEGIN_DECLS
typedef gboolean (*GtkSurfaceTransformChangedCallback) (GtkWidget *widget,
const graphene_matrix_t *surface_transform,
gpointer user_data);
#define GTK_STATE_FLAGS_BITS 14
struct _GtkWidgetPrivate
@ -116,6 +120,14 @@ struct _GtkWidgetPrivate
guint clock_tick_id;
GList *tick_callbacks;
/* Surface relative transform updates callbacks */
guint parent_surface_transform_changed_id;
GtkWidget *parent_surface_transform_changed_parent;
gulong parent_changed_handler_id;
GList *surface_transform_changed_callbacks;
gboolean cached_surface_transform_valid;
graphene_matrix_t cached_surface_transform;
/* 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.
@ -344,6 +356,15 @@ gboolean gtk_widget_run_controllers (GtkWidget
GtkPropagationPhase phase);
guint gtk_widget_add_surface_transform_changed_callback (GtkWidget *widget,
GtkSurfaceTransformChangedCallback callback,
gpointer user_data,
GDestroyNotify notify);
void gtk_widget_remove_surface_transform_changed_callback (GtkWidget *widget,
guint id);
/* inline getters */
static inline GtkWidget *

View File

@ -35,7 +35,7 @@ gdk_pixbuf_req = '>= 2.30.0'
introspection_req = '>= 1.39.0'
wayland_proto_req = '>= 1.12'
wayland_req = '>= 1.14.91'
graphene_req = '>= 1.8.5'
graphene_req = '>= 1.8.7'
epoxy_req = '>= 1.4'
cloudproviders_req = '>= 0.2.5'
xkbcommon_req = '>= 0.2.0'