[gtk+/gtk-style-context: 192/251] GtkStyleContext: Add animation machinery.
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/gtk-style-context: 192/251] GtkStyleContext: Add animation machinery.
- Date: Tue, 12 Oct 2010 02:11:15 +0000 (UTC)
commit faf503f0f345f3862f4e057f1f6678b52c6bd348
Author: Carlos Garnacho <carlosg gnome org>
Date: Thu Aug 19 22:35:42 2010 +0200
GtkStyleContext: Add animation machinery.
Animation regions are confined through gtk_style_context_[push|pop]_animatable_region,
anything that's painted between these calls will be invalidated if an animation starts
for any of the regions in the stack.
gtk_style_context_notify_state_change() may be called from widgets to indicate a
change for a given GtkStateType, in that case an animation will be started if there
is an animation description for the widget/state.
gtk/gtkstylecontext.c | 439 +++++++++++++++++++++++++++++++++++++++++++++++-
gtk/gtkstylecontext.h | 29 +++-
gtk/gtkthemingengine.c | 2 +-
3 files changed, 459 insertions(+), 11 deletions(-)
---
diff --git a/gtk/gtkstylecontext.c b/gtk/gtkstylecontext.c
index cd3b2c0..d34bc41 100644
--- a/gtk/gtkstylecontext.c
+++ b/gtk/gtkstylecontext.c
@@ -29,6 +29,8 @@
#include "gtkintl.h"
#include "gtkwidget.h"
#include "gtkprivate.h"
+#include "gtkanimationdescription.h"
+#include "gtktimeline.h"
#include "gtkalias.h"
@@ -37,6 +39,7 @@ typedef struct GtkStyleProviderData GtkStyleProviderData;
typedef struct GtkStyleInfo GtkStyleInfo;
typedef struct GtkRegion GtkRegion;
typedef struct PropertyValue PropertyValue;
+typedef struct AnimationInfo AnimationInfo;
struct GtkRegion
{
@@ -64,6 +67,19 @@ struct GtkStyleInfo
GtkJunctionSides junction_sides;
};
+struct AnimationInfo
+{
+ GtkTimeline *timeline;
+
+ gpointer region_id;
+ GdkWindow *window;
+ GtkStateType state;
+ gboolean target_value;
+
+ GdkRegion *invalidation_region;
+ GArray *rectangles;
+};
+
struct GtkStyleContextPrivate
{
GdkScreen *screen;
@@ -81,6 +97,10 @@ struct GtkStyleContextPrivate
GtkStateFlags state_flags;
GSList *info_stack;
+ GSList *animation_regions;
+ GSList *animations;
+ gboolean animations_invalidated;
+
GtkThemingEngine *theming_engine;
GtkTextDirection direction;
@@ -554,11 +574,37 @@ gtk_style_context_get_state (GtkStyleContext *context)
return priv->state_flags;
}
+static gboolean
+context_has_animatable_region (GtkStyleContext *context,
+ gpointer region_id)
+{
+ GtkStyleContextPrivate *priv;
+ GSList *r;
+
+ /* NULL region_id means everything
+ * rendered through the style context
+ */
+ if (!region_id)
+ return TRUE;
+
+ priv = context->priv;
+
+ for (r = priv->animation_regions; r; r = r->next)
+ {
+ if (r->data == region_id)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
gboolean
gtk_style_context_is_state_set (GtkStyleContext *context,
- GtkStateType state)
+ GtkStateType state,
+ gdouble *progress)
{
GtkStyleContextPrivate *priv;
+ gboolean state_set;
g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), FALSE);
@@ -567,22 +613,51 @@ gtk_style_context_is_state_set (GtkStyleContext *context,
switch (state)
{
case GTK_STATE_NORMAL:
- return priv->state_flags == 0;
+ state_set = (priv->state_flags == 0);
+ break;
case GTK_STATE_ACTIVE:
- return priv->state_flags & GTK_STATE_FLAG_ACTIVE;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_ACTIVE);
+ break;
case GTK_STATE_PRELIGHT:
- return priv->state_flags & GTK_STATE_FLAG_PRELIGHT;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_PRELIGHT);
+ break;
case GTK_STATE_SELECTED:
- return priv->state_flags & GTK_STATE_FLAG_SELECTED;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_SELECTED);
+ break;
case GTK_STATE_INSENSITIVE:
- return priv->state_flags & GTK_STATE_FLAG_INSENSITIVE;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_INSENSITIVE);
+ break;
case GTK_STATE_INCONSISTENT:
- return priv->state_flags & GTK_STATE_FLAG_INCONSISTENT;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_INCONSISTENT);
+ break;
case GTK_STATE_FOCUSED:
- return priv->state_flags & GTK_STATE_FLAG_FOCUSED;
+ state_set = (priv->state_flags & GTK_STATE_FLAG_FOCUSED);
+ break;
default:
- return FALSE;
+ g_assert_not_reached ();
}
+
+ if (progress)
+ {
+ AnimationInfo *info;
+ GSList *l;
+
+ *progress = (state_set) ? 1 : 0;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ info = l->data;
+
+ if (info->state == state &&
+ context_has_animatable_region (context, info->region_id))
+ {
+ *progress = gtk_timeline_get_progress (info->timeline);
+ break;
+ }
+ }
+ }
+
+ return state_set;
}
void
@@ -1362,6 +1437,350 @@ gtk_style_context_lookup_color (GtkStyleContext *context,
return gtk_symbolic_color_resolve (sym_color, priv->store, color);
}
+static void
+timeline_frame_cb (GtkTimeline *timeline,
+ gdouble progress,
+ gpointer user_data)
+{
+ AnimationInfo *info;
+
+ info = user_data;
+
+ if (info->invalidation_region &&
+ !gdk_region_empty (info->invalidation_region))
+ gdk_window_invalidate_region (info->window, info->invalidation_region, TRUE);
+}
+
+static void
+animation_info_free (AnimationInfo *info)
+{
+ g_object_unref (info->timeline);
+ g_object_unref (info->window);
+
+ if (info->invalidation_region)
+ gdk_region_destroy (info->invalidation_region);
+
+ g_array_free (info->rectangles, TRUE);
+ g_slice_free (AnimationInfo, info);
+}
+
+static void
+timeline_finished_cb (GtkTimeline *timeline,
+ gpointer user_data)
+{
+ GtkStyleContextPrivate *priv;
+ GtkStyleContext *context;
+ AnimationInfo *info;
+ GSList *l;
+
+ context = user_data;
+ priv = context->priv;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ info = l->data;
+
+ if (info->timeline == timeline)
+ {
+ priv->animations = g_slist_delete_link (priv->animations, l);
+
+ /* Invalidate one last time the area, so the final content is painted */
+ if (info->invalidation_region &&
+ !gdk_region_empty (info->invalidation_region))
+ gdk_window_invalidate_region (info->window, info->invalidation_region, TRUE);
+
+ animation_info_free (info);
+ break;
+ }
+ }
+}
+
+static AnimationInfo *
+animation_info_new (GtkStyleContext *context,
+ gdouble duration,
+ GtkTimelineProgressType progress_type,
+ GtkStateType state,
+ gboolean target_value,
+ GdkWindow *window)
+{
+ AnimationInfo *info;
+
+ info = g_slice_new0 (AnimationInfo);
+
+ info->rectangles = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+ info->timeline = gtk_timeline_new (duration);
+ info->window = g_object_ref (window);
+ info->state = state;
+ info->target_value = target_value;
+
+ gtk_timeline_set_progress_type (info->timeline, progress_type);
+
+ if (!target_value)
+ {
+ gtk_timeline_set_direction (info->timeline, GTK_TIMELINE_DIRECTION_BACKWARD);
+ gtk_timeline_rewind (info->timeline);
+ }
+
+ g_signal_connect (info->timeline, "frame",
+ G_CALLBACK (timeline_frame_cb), info);
+ g_signal_connect (info->timeline, "finished",
+ G_CALLBACK (timeline_finished_cb), context);
+
+ gtk_timeline_start (info->timeline);
+
+ return info;
+}
+
+static AnimationInfo *
+animation_info_lookup (GtkStyleContext *context,
+ gpointer region_id,
+ GtkStateType state)
+{
+ GtkStyleContextPrivate *priv;
+ GSList *l;
+
+ priv = context->priv;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ AnimationInfo *info;
+
+ info = l->data;
+
+ if (info->state == state &&
+ info->region_id == region_id)
+ return info;
+ }
+
+ return NULL;
+}
+
+void
+gtk_style_context_notify_state_change (GtkStyleContext *context,
+ GdkWindow *window,
+ gpointer region_id,
+ GtkStateType state,
+ gboolean state_value)
+{
+ GtkStyleContextPrivate *priv;
+ GtkAnimationDescription *desc;
+ AnimationInfo *info;
+ GtkStateFlags flags;
+
+ g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
+ g_return_if_fail (GDK_IS_WINDOW (window));
+ g_return_if_fail (state < GTK_STATE_LAST);
+
+ state_value = (state_value == TRUE);
+ priv = context->priv;
+
+ switch (state)
+ {
+ case GTK_STATE_ACTIVE:
+ flags = GTK_STATE_FLAG_ACTIVE;
+ break;
+ case GTK_STATE_PRELIGHT:
+ flags = GTK_STATE_FLAG_PRELIGHT;
+ break;
+ case GTK_STATE_SELECTED:
+ flags = GTK_STATE_FLAG_SELECTED;
+ break;
+ case GTK_STATE_INSENSITIVE:
+ flags = GTK_STATE_FLAG_INSENSITIVE;
+ break;
+ case GTK_STATE_INCONSISTENT:
+ flags = GTK_STATE_FLAG_INCONSISTENT;
+ break;
+ case GTK_STATE_FOCUSED:
+ flags = GTK_STATE_FLAG_FOCUSED;
+ break;
+ case GTK_STATE_NORMAL:
+ default:
+ flags = 0;
+ break;
+ }
+
+ /* Find out if there is any animation description for the given
+ * state, it will fallback to the normal state as well if necessary.
+ */
+ gtk_style_set_get (priv->store, flags,
+ "transition", &desc,
+ NULL);
+
+ if (!desc)
+ return;
+
+ if (gtk_animation_description_get_duration (desc) == 0)
+ {
+ gtk_animation_description_unref (desc);
+ return;
+ }
+
+ info = animation_info_lookup (context, region_id, state);
+
+ if (info)
+ {
+ /* Reverse the animation if target values are the opposite */
+ if (info->target_value != state_value)
+ {
+ if (gtk_timeline_get_direction (info->timeline) == GTK_TIMELINE_DIRECTION_FORWARD)
+ gtk_timeline_set_direction (info->timeline, GTK_TIMELINE_DIRECTION_BACKWARD);
+ else
+ gtk_timeline_set_direction (info->timeline, GTK_TIMELINE_DIRECTION_FORWARD);
+
+ info->target_value = state_value;
+ }
+ }
+ else
+ {
+ info = animation_info_new (context,
+ gtk_animation_description_get_duration (desc),
+ gtk_animation_description_get_progress_type (desc),
+ state, state_value, window);
+
+ priv->animations = g_slist_prepend (priv->animations, info);
+ priv->animations_invalidated = TRUE;
+ }
+
+ gtk_animation_description_unref (desc);
+}
+
+void
+gtk_style_context_push_animatable_region (GtkStyleContext *context,
+ gpointer region_id)
+{
+ GtkStyleContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
+ g_return_if_fail (region_id != NULL);
+
+ priv = context->priv;
+ priv->animation_regions = g_slist_prepend (priv->animation_regions, region_id);
+}
+
+void
+gtk_style_context_pop_animatable_region (GtkStyleContext *context)
+{
+ GtkStyleContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
+
+ priv = context->priv;
+ priv->animation_regions = g_slist_delete_link (priv->animation_regions,
+ priv->animation_regions);
+}
+
+void
+_gtk_style_context_invalidate_animation_areas (GtkStyleContext *context)
+{
+ GtkStyleContextPrivate *priv;
+ GSList *l;
+
+ priv = context->priv;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ AnimationInfo *info;
+
+ info = l->data;
+
+ /* A NULL invalidation region means it has to be recreated on
+ * the next expose event, this happens usually after a widget
+ * allocation change, so the next expose after it will update
+ * the invalidation region.
+ */
+ if (info->invalidation_region)
+ {
+ gdk_region_destroy (info->invalidation_region);
+ info->invalidation_region = NULL;
+ }
+ }
+
+ priv->animations_invalidated = TRUE;
+}
+
+void
+_gtk_style_context_coalesce_animation_areas (GtkStyleContext *context)
+{
+ GtkStyleContextPrivate *priv;
+ GSList *l;
+
+ priv = context->priv;
+
+ if (!priv->animations_invalidated)
+ return;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ AnimationInfo *info;
+ guint i;
+
+ info = l->data;
+
+ if (info->invalidation_region)
+ continue;
+
+ /* FIXME: If this happens there's not much
+ * point in keeping the animation running.
+ */
+ if (info->rectangles->len == 0)
+ continue;
+
+ info->invalidation_region = gdk_region_new ();
+
+ for (i = 0; i <info->rectangles->len; i++)
+ {
+ GdkRectangle *rect;
+
+ rect = &g_array_index (info->rectangles, GdkRectangle, i);
+ gdk_region_union_with_rect (info->invalidation_region, rect);
+ }
+
+ g_array_remove_range (info->rectangles, 0, info->rectangles->len);
+ }
+}
+
+static void
+store_animation_region (GtkStyleContext *context,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height)
+{
+ GtkStyleContextPrivate *priv;
+ GSList *l;
+
+ priv = context->priv;
+
+ if (!priv->animations_invalidated)
+ return;
+
+ for (l = priv->animations; l; l = l->next)
+ {
+ AnimationInfo *info;
+
+ info = l->data;
+
+ /* The animation doesn't need updatring
+ * the invalidation area, bail out.
+ */
+ if (info->invalidation_region)
+ continue;
+
+ if (context_has_animatable_region (context, info->region_id))
+ {
+ GdkRectangle rect;
+
+ rect.x = (gint) x;
+ rect.y = (gint) y;
+ rect.width = (gint) width;
+ rect.height = (gint) height;
+
+ g_array_append_val (info->rectangles, rect);
+ }
+ }
+}
+
/* Paint methods */
void
gtk_render_check (GtkStyleContext *context,
@@ -1380,6 +1799,8 @@ gtk_render_check (GtkStyleContext *context,
priv = context->priv;
engine_class = GTK_THEMING_ENGINE_GET_CLASS (priv->theming_engine);
+ store_animation_region (context, x, y, width, height);
+
_gtk_theming_engine_set_context (priv->theming_engine, context);
engine_class->render_check (priv->theming_engine, cr,
x, y, width, height);
diff --git a/gtk/gtkstylecontext.h b/gtk/gtkstylecontext.h
index eecb7b3..020368c 100644
--- a/gtk/gtkstylecontext.h
+++ b/gtk/gtkstylecontext.h
@@ -75,7 +75,8 @@ void gtk_style_context_set_state (GtkStyleContext *context,
GtkStateFlags gtk_style_context_get_state (GtkStyleContext *context);
gboolean gtk_style_context_is_state_set (GtkStyleContext *context,
- GtkStateType state);
+ GtkStateType state,
+ gdouble *progress);
void gtk_style_context_set_path (GtkStyleContext *context,
GtkWidgetPath *path);
@@ -128,10 +129,36 @@ gboolean gtk_style_context_lookup_color (GtkStyleContext *context,
const gchar *color_name,
GdkColor *color);
+void gtk_style_context_notify_state_change (GtkStyleContext *context,
+ GdkWindow *window,
+ gpointer region_id,
+ GtkStateType state,
+ gboolean state_value);
+void gtk_style_context_push_animatable_region (GtkStyleContext *context,
+ gpointer region_id);
+void gtk_style_context_pop_animatable_region (GtkStyleContext *context);
+
+
/* Semi-private API */
const GValue * _gtk_style_context_peek_style_property (GtkStyleContext *context,
GType widget_type,
GParamSpec *pspec);
+void _gtk_style_context_invalidate_animation_areas (GtkStyleContext *context);
+void _gtk_style_context_coalesce_animation_areas (GtkStyleContext *context);
+
+/* Animation for state changes */
+void gtk_style_context_state_transition_start (GtkStyleContext *context,
+ gpointer identifier,
+ GtkWidget *widget,
+ GtkStateType state,
+ gboolean value,
+ GdkRectangle *rect);
+void gtk_style_context_state_transition_update (GtkStyleContext *context,
+ gpointer identifier,
+ GdkRectangle *rect,
+ GtkStateType state);
+void gtk_style_context_state_transition_stop (GtkStyleContext *context,
+ gpointer identifier);
/* Paint methods */
void gtk_render_check (GtkStyleContext *context,
diff --git a/gtk/gtkthemingengine.c b/gtk/gtkthemingengine.c
index a7a740b..b29c73d 100644
--- a/gtk/gtkthemingengine.c
+++ b/gtk/gtkthemingengine.c
@@ -321,7 +321,7 @@ gtk_theming_engine_is_state_set (GtkThemingEngine *engine,
g_return_val_if_fail (GTK_IS_THEMING_ENGINE (engine), 0);
priv = engine->priv;
- return gtk_style_context_is_state_set (priv->context, state);
+ return gtk_style_context_is_state_set (priv->context, state, NULL);
}
G_CONST_RETURN GtkWidgetPath *
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]