/* egg-animation.c * * Copyright (C) 2010-2016 Christian Hergert * * This file 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 file 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 General Public License along * with this program. If not, see . */ #include #include #include #include #include #include #include "animation/egg-animation.h" #include "animation/egg-frame-source.h" #define FALLBACK_FRAME_RATE 60 typedef gdouble (*AlphaFunc) (gdouble offset); typedef void (*TweenFunc) (const GValue *begin, const GValue *end, GValue *value, gdouble offset); typedef struct { gboolean is_child; /* Does GParamSpec belong to parent widget */ GParamSpec *pspec; /* GParamSpec of target property */ GValue begin; /* Begin value in animation */ GValue end; /* End value in animation */ } Tween; struct _EggAnimation { GInitiallyUnowned parent_instance; gpointer target; /* Target object to animate */ guint64 begin_msec; /* Time in which animation started */ guint duration_msec; /* Duration of animation */ guint mode; /* Tween mode */ gulong tween_handler; /* GSource or signal handler */ gulong after_paint_handler; /* signal handler */ gdouble last_offset; /* Track our last offset */ GArray *tweens; /* Array of tweens to perform */ GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */ GDestroyNotify notify; /* Notify callback */ gpointer notify_data; /* Data for notify */ }; G_DEFINE_TYPE (EggAnimation, egg_animation, G_TYPE_INITIALLY_UNOWNED) enum { PROP_0, PROP_DURATION, PROP_FRAME_CLOCK, PROP_MODE, PROP_TARGET, LAST_PROP }; enum { TICK, LAST_SIGNAL }; /* * Helper macros. */ #define LAST_FUNDAMENTAL 64 #define TWEEN(type) \ static void \ tween_ ## type (const GValue * begin, \ const GValue * end, \ GValue * value, \ gdouble offset) \ { \ g ## type x = g_value_get_ ## type (begin); \ g ## type y = g_value_get_ ## type (end); \ g_value_set_ ## type (value, x + ((y - x) * offset)); \ } /* * Globals. */ static AlphaFunc alpha_funcs[EGG_ANIMATION_LAST]; static gboolean debug; static GParamSpec *properties[LAST_PROP]; static guint signals[LAST_SIGNAL]; static TweenFunc tween_funcs[LAST_FUNDAMENTAL]; static guint slow_down_factor = 1; /* * Tweeners for basic types. */ TWEEN (int); TWEEN (uint); TWEEN (long); TWEEN (ulong); TWEEN (float); TWEEN (double); /** * egg_animation_alpha_ease_in_cubic: * @offset: (in): The position within the animation; 0.0 to 1.0. * * An alpha function to transform the offset within the animation. * @EGG_ANIMATION_CUBIC means the valu ewill be transformed into * cubic acceleration (x * x * x). */ static gdouble egg_animation_alpha_ease_in_cubic (gdouble offset) { return offset * offset * offset; } static gdouble egg_animation_alpha_ease_out_cubic (gdouble offset) { gdouble p = offset - 1.0; return p * p * p + 1.0; } static gdouble egg_animation_alpha_ease_in_out_cubic (gdouble offset) { if (offset < .5) return egg_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0; else return .5 + egg_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0; } /** * egg_animation_alpha_linear: * @offset: (in): The position within the animation; 0.0 to 1.0. * * An alpha function to transform the offset within the animation. * @EGG_ANIMATION_LINEAR means no tranformation will be made. * * Returns: @offset. * Side effects: None. */ static gdouble egg_animation_alpha_linear (gdouble offset) { return offset; } /** * egg_animation_alpha_ease_in_quad: * @offset: (in): The position within the animation; 0.0 to 1.0. * * An alpha function to transform the offset within the animation. * @EGG_ANIMATION_EASE_IN_QUAD means that the value will be transformed * into a quadratic acceleration. * * Returns: A tranformation of @offset. * Side effects: None. */ static gdouble egg_animation_alpha_ease_in_quad (gdouble offset) { return offset * offset; } /** * egg_animation_alpha_ease_out_quad: * @offset: (in): The position within the animation; 0.0 to 1.0. * * An alpha function to transform the offset within the animation. * @EGG_ANIMATION_EASE_OUT_QUAD means that the value will be transformed * into a quadratic deceleration. * * Returns: A tranformation of @offset. * Side effects: None. */ static gdouble egg_animation_alpha_ease_out_quad (gdouble offset) { return -1.0 * offset * (offset - 2.0); } /** * egg_animation_alpha_ease_in_out_quad: * @offset: (in): The position within the animation; 0.0 to 1.0. * * An alpha function to transform the offset within the animation. * @EGG_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed * into a quadratic acceleration for the first half, and quadratic * deceleration the second half. * * Returns: A tranformation of @offset. * Side effects: None. */ static gdouble egg_animation_alpha_ease_in_out_quad (gdouble offset) { offset *= 2.0; if (offset < 1.0) return 0.5 * offset * offset; offset -= 1.0; return -0.5 * (offset * (offset - 2.0) - 1.0); } /** * egg_animation_load_begin_values: * @animation: (in): A #EggAnimation. * * Load the begin values for all the properties we are about to * animate. * * Side effects: None. */ static void egg_animation_load_begin_values (EggAnimation *animation) { GtkContainer *container; Tween *tween; guint i; g_return_if_fail (EGG_IS_ANIMATION (animation)); for (i = 0; i < animation->tweens->len; i++) { tween = &g_array_index (animation->tweens, Tween, i); g_value_reset (&tween->begin); if (tween->is_child) { container = GTK_CONTAINER (gtk_widget_get_parent (animation->target)); gtk_container_child_get_property (container, animation->target, tween->pspec->name, &tween->begin); } else { g_object_get_property (animation->target, tween->pspec->name, &tween->begin); } } } /** * egg_animation_unload_begin_values: * @animation: (in): A #EggAnimation. * * Unloads the begin values for the animation. This might be particularly * useful once we support pointer types. * * Side effects: None. */ static void egg_animation_unload_begin_values (EggAnimation *animation) { Tween *tween; guint i; g_return_if_fail (EGG_IS_ANIMATION (animation)); for (i = 0; i < animation->tweens->len; i++) { tween = &g_array_index (animation->tweens, Tween, i); g_value_reset (&tween->begin); } } /** * egg_animation_get_offset: * @animation: A #EggAnimation. * @frame_time: the time to present the frame, or 0 for current timing. * * Retrieves the position within the animation from 0.0 to 1.0. This * value is calculated using the msec of the beginning of the animation * and the current time. * * Returns: The offset of the animation from 0.0 to 1.0. */ static gdouble egg_animation_get_offset (EggAnimation *animation, gint64 frame_time) { gdouble offset; gint64 frame_msec; g_return_val_if_fail (EGG_IS_ANIMATION (animation), 0.0); if (frame_time == 0) { if (animation->frame_clock != NULL) frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock); else frame_time = g_get_monotonic_time (); } frame_msec = frame_time / 1000L; offset = (gdouble) (frame_msec - animation->begin_msec) / (gdouble) MAX (animation->duration_msec, 1); return CLAMP (offset, 0.0, 1.0); } /** * egg_animation_update_property: * @animation: (in): A #EggAnimation. * @target: (in): A #GObject. * @tween: (in): a #Tween containing the property. * @value: (in): The new value for the property. * * Updates the value of a property on an object using @value. * * Side effects: The property of @target is updated. */ static void egg_animation_update_property (EggAnimation *animation, gpointer target, Tween *tween, const GValue *value) { g_assert (EGG_IS_ANIMATION (animation)); g_assert (G_IS_OBJECT (target)); g_assert (tween); g_assert (value); g_object_set_property (target, tween->pspec->name, value); } /** * egg_animation_update_child_property: * @animation: (in): A #EggAnimation. * @target: (in): A #GObject. * @tween: (in): A #Tween containing the property. * @value: (in): The new value for the property. * * Updates the value of the parent widget of the target to @value. * * Side effects: The property of @target's parent widget is updated. */ static void egg_animation_update_child_property (EggAnimation *animation, gpointer target, Tween *tween, const GValue *value) { GtkWidget *parent; g_assert (EGG_IS_ANIMATION (animation)); g_assert (G_IS_OBJECT (target)); g_assert (tween); g_assert (value); parent = gtk_widget_get_parent (GTK_WIDGET (target)); gtk_container_child_set_property (GTK_CONTAINER (parent), target, tween->pspec->name, value); } /** * egg_animation_get_value_at_offset: * @animation: (in): A #EggAnimation. * @offset: (in): The offset in the animation from 0.0 to 1.0. * @tween: (in): A #Tween containing the property. * @value: (out): A #GValue in which to store the property. * * Retrieves a value for a particular position within the animation. * * Side effects: None. */ static void egg_animation_get_value_at_offset (EggAnimation *animation, gdouble offset, Tween *tween, GValue *value) { g_return_if_fail (EGG_IS_ANIMATION (animation)); g_return_if_fail (tween != NULL); g_return_if_fail (value != NULL); g_return_if_fail (value->g_type == tween->pspec->value_type); if (value->g_type < LAST_FUNDAMENTAL) { /* * If you hit the following assertion, you need to add a function * to create the new value at the given offset. */ g_assert (tween_funcs[value->g_type]); tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset); } else { /* * TODO: Support complex transitions. */ if (offset >= 1.0) g_value_copy (&tween->end, value); } } static void egg_animation_set_frame_clock (EggAnimation *animation, GdkFrameClock *frame_clock) { if (animation->frame_clock != frame_clock) { g_clear_object (&animation->frame_clock); animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL; } } static void egg_animation_set_target (EggAnimation *animation, gpointer target) { g_assert (!animation->target); animation->target = g_object_ref (target); if (GTK_IS_WIDGET (animation->target)) egg_animation_set_frame_clock (animation, gtk_widget_get_frame_clock (animation->target)); } /** * egg_animation_tick: * @animation: (in): A #EggAnimation. * * Moves the object properties to the next position in the animation. * * Returns: %TRUE if the animation has not completed; otherwise %FALSE. * Side effects: None. */ static gboolean egg_animation_tick (EggAnimation *animation, gdouble offset) { gdouble alpha; GValue value = { 0 }; Tween *tween; guint i; g_return_val_if_fail (EGG_IS_ANIMATION (animation), FALSE); if (offset == animation->last_offset) return offset < 1.0; alpha = alpha_funcs[animation->mode](offset); /* * Update property values. */ for (i = 0; i < animation->tweens->len; i++) { tween = &g_array_index (animation->tweens, Tween, i); g_value_init (&value, tween->pspec->value_type); egg_animation_get_value_at_offset (animation, alpha, tween, &value); if (!tween->is_child) { egg_animation_update_property (animation, animation->target, tween, &value); } else { egg_animation_update_child_property (animation, animation->target, tween, &value); } g_value_unset (&value); } /* * Notify anyone interested in the tick signal. */ g_signal_emit (animation, signals[TICK], 0); /* * Flush any outstanding events to the graphics server (in the case of X). */ #if !GTK_CHECK_VERSION (3, 13, 0) if (GTK_IS_WIDGET (animation->target)) { GdkWindow *window; if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target)))) gdk_window_flush (window); } #endif animation->last_offset = offset; return offset < 1.0; } /** * egg_animation_timeout_cb: * @user_data: (in): A #EggAnimation. * * Timeout from the main loop to move to the next step of the animation. * * Returns: %TRUE until the animation has completed; otherwise %FALSE. * Side effects: None. */ static gboolean egg_animation_timeout_cb (gpointer user_data) { EggAnimation *animation = user_data; gboolean ret; gdouble offset; offset = egg_animation_get_offset (animation, 0); if (!(ret = egg_animation_tick (animation, offset))) egg_animation_stop (animation); return ret; } static gboolean egg_animation_widget_tick_cb (GdkFrameClock *frame_clock, EggAnimation *animation) { gboolean ret = G_SOURCE_REMOVE; g_assert (GDK_IS_FRAME_CLOCK (frame_clock)); g_assert (EGG_IS_ANIMATION (animation)); if (animation->tween_handler) { gdouble offset; offset = egg_animation_get_offset (animation, 0); if (!(ret = egg_animation_tick (animation, offset))) egg_animation_stop (animation); } return ret; } static void egg_animation_widget_after_paint_cb (GdkFrameClock *frame_clock, EggAnimation *animation) { gint64 base_time; gint64 interval; gint64 next_frame_time; gdouble offset; g_assert (GDK_IS_FRAME_CLOCK (frame_clock)); g_assert (EGG_IS_ANIMATION (animation)); base_time = gdk_frame_clock_get_frame_time (frame_clock); gdk_frame_clock_get_refresh_info (frame_clock, base_time, &interval, &next_frame_time); offset = egg_animation_get_offset (animation, next_frame_time); egg_animation_tick (animation, offset); } /** * egg_animation_start: * @animation: (in): A #EggAnimation. * * Start the animation. When the animation stops, the internal reference will * be dropped and the animation may be finalized. * * Side effects: None. */ void egg_animation_start (EggAnimation *animation) { g_return_if_fail (EGG_IS_ANIMATION (animation)); g_return_if_fail (!animation->tween_handler); g_object_ref_sink (animation); egg_animation_load_begin_values (animation); if (animation->frame_clock) { animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL; animation->tween_handler = g_signal_connect (animation->frame_clock, "update", G_CALLBACK (egg_animation_widget_tick_cb), animation); animation->after_paint_handler = g_signal_connect (animation->frame_clock, "after-paint", G_CALLBACK (egg_animation_widget_after_paint_cb), animation); gdk_frame_clock_begin_updating (animation->frame_clock); } else { animation->begin_msec = g_get_monotonic_time () / 1000UL; animation->tween_handler = egg_frame_source_add (FALLBACK_FRAME_RATE, egg_animation_timeout_cb, animation); } } static void egg_animation_notify (EggAnimation *self) { g_assert (EGG_IS_ANIMATION (self)); if (self->notify != NULL) { GDestroyNotify notify = self->notify; gpointer data = self->notify_data; self->notify = NULL; self->notify_data = NULL; notify (data); } } /** * egg_animation_stop: * @animation: (in): A #EggAnimation. * * Stops a running animation. The internal reference to the animation is * dropped and therefore may cause the object to finalize. * * Side effects: None. */ void egg_animation_stop (EggAnimation *animation) { g_return_if_fail (EGG_IS_ANIMATION (animation)); if (animation->tween_handler) { if (animation->frame_clock) { gdk_frame_clock_end_updating (animation->frame_clock); g_clear_signal_handler (&animation->tween_handler, animation->frame_clock); g_clear_signal_handler (&animation->after_paint_handler, animation->frame_clock); } else { g_source_remove (animation->tween_handler); animation->tween_handler = 0; } egg_animation_unload_begin_values (animation); egg_animation_notify (animation); g_object_unref (animation); } } /** * egg_animation_add_property: * @animation: (in): A #EggAnimation. * @pspec: (in): A #ParamSpec of @target or a #GtkWidget's parent. * @value: (in): The new value for the property at the end of the animation. * * Adds a new property to the set of properties to be animated during the * lifetime of the animation. * * Side effects: None. */ void egg_animation_add_property (EggAnimation *animation, GParamSpec *pspec, const GValue *value) { Tween tween = { 0 }; GType type; g_return_if_fail (EGG_IS_ANIMATION (animation)); g_return_if_fail (pspec != NULL); g_return_if_fail (value != NULL); g_return_if_fail (value->g_type); g_return_if_fail (animation->target); g_return_if_fail (!animation->tween_handler); type = G_TYPE_FROM_INSTANCE (animation->target); tween.is_child = !g_type_is_a (type, pspec->owner_type); if (tween.is_child) { if (!GTK_IS_WIDGET (animation->target)) { g_critical (_("Cannot locate property %s in class %s"), pspec->name, g_type_name (type)); return; } } tween.pspec = g_param_spec_ref (pspec); g_value_init (&tween.begin, pspec->value_type); g_value_init (&tween.end, pspec->value_type); g_value_copy (value, &tween.end); g_array_append_val (animation->tweens, tween); } /** * egg_animation_dispose: * @object: (in): A #EggAnimation. * * Releases any object references the animation contains. * * Side effects: None. */ static void egg_animation_dispose (GObject *object) { EggAnimation *self = EGG_ANIMATION (object); g_clear_object (&self->target); g_clear_object (&self->frame_clock); G_OBJECT_CLASS (egg_animation_parent_class)->dispose (object); } /** * egg_animation_finalize: * @object: (in): A #EggAnimation. * * Finalizes the object and releases any resources allocated. * * Side effects: None. */ static void egg_animation_finalize (GObject *object) { EggAnimation *self = EGG_ANIMATION (object); Tween *tween; guint i; for (i = 0; i < self->tweens->len; i++) { tween = &g_array_index (self->tweens, Tween, i); g_value_unset (&tween->begin); g_value_unset (&tween->end); g_param_spec_unref (tween->pspec); } g_array_unref (self->tweens); G_OBJECT_CLASS (egg_animation_parent_class)->finalize (object); } /** * egg_animation_set_property: * @object: (in): A #GObject. * @prop_id: (in): The property identifier. * @value: (in): The given property. * @pspec: (in): A #ParamSpec. * * Set a given #GObject property. */ static void egg_animation_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EggAnimation *animation = EGG_ANIMATION (object); switch (prop_id) { case PROP_DURATION: animation->duration_msec = g_value_get_uint (value) * slow_down_factor; break; case PROP_FRAME_CLOCK: egg_animation_set_frame_clock (animation, g_value_get_object (value)); break; case PROP_MODE: animation->mode = g_value_get_enum (value); break; case PROP_TARGET: egg_animation_set_target (animation, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } /** * egg_animation_class_init: * @klass: (in): A #EggAnimationClass. * * Initializes the GObjectClass. * * Side effects: Properties, signals, and vtables are initialized. */ static void egg_animation_class_init (EggAnimationClass *klass) { GObjectClass *object_class; const gchar *slow_down_factor_env; debug = !!g_getenv ("EGG_ANIMATION_DEBUG"); slow_down_factor_env = g_getenv ("EGG_ANIMATION_SLOW_DOWN_FACTOR"); if (slow_down_factor_env) slow_down_factor = MAX (1, atoi (slow_down_factor_env)); object_class = G_OBJECT_CLASS (klass); object_class->dispose = egg_animation_dispose; object_class->finalize = egg_animation_finalize; object_class->set_property = egg_animation_set_property; /** * EggAnimation:duration: * * The "duration" property is the total number of milliseconds that the * animation should run before being completed. */ properties[PROP_DURATION] = g_param_spec_uint ("duration", "Duration", "The duration of the animation", 0, G_MAXUINT, 250, (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); properties[PROP_FRAME_CLOCK] = g_param_spec_object ("frame-clock", "Frame Clock", "An optional frame-clock to synchronize with.", GDK_TYPE_FRAME_CLOCK, (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * EggAnimation:mode: * * The "mode" property is the Alpha function that should be used to * determine the offset within the animation based on the current * offset in the animations duration. */ properties[PROP_MODE] = g_param_spec_enum ("mode", "Mode", "The animation mode", EGG_TYPE_ANIMATION_MODE, EGG_ANIMATION_LINEAR, (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * EggAnimation:target: * * The "target" property is the #GObject that should have its properties * animated. */ properties[PROP_TARGET] = g_param_spec_object ("target", "Target", "The target of the animation", G_TYPE_OBJECT, (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, LAST_PROP, properties); /** * EggAnimation::tick: * * The "tick" signal is emitted on each frame in the animation. */ signals[TICK] = g_signal_new ("tick", EGG_TYPE_ANIMATION, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); #define SET_ALPHA(_T, _t) \ alpha_funcs[EGG_ANIMATION_ ## _T] = egg_animation_alpha_ ## _t SET_ALPHA (LINEAR, linear); SET_ALPHA (EASE_IN_QUAD, ease_in_quad); SET_ALPHA (EASE_OUT_QUAD, ease_out_quad); SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad); SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic); SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic); SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic); #define SET_TWEEN(_T, _t) \ G_STMT_START { \ guint idx = G_TYPE_ ## _T; \ tween_funcs[idx] = tween_ ## _t; \ } G_STMT_END SET_TWEEN (INT, int); SET_TWEEN (UINT, uint); SET_TWEEN (LONG, long); SET_TWEEN (ULONG, ulong); SET_TWEEN (FLOAT, float); SET_TWEEN (DOUBLE, double); } /** * egg_animation_init: * @animation: (in): A #EggAnimation. * * Initializes the #EggAnimation instance. * * Side effects: Everything. */ static void egg_animation_init (EggAnimation *animation) { animation->duration_msec = 250; animation->mode = EGG_ANIMATION_EASE_IN_OUT_QUAD; animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween)); animation->last_offset = -G_MINDOUBLE; } /** * egg_animation_mode_get_type: * * Retrieves the GType for #EggAnimationMode. * * Returns: A GType. * Side effects: GType registered on first call. */ GType egg_animation_mode_get_type (void) { static GType type_id = 0; static const GEnumValue values[] = { { EGG_ANIMATION_LINEAR, "EGG_ANIMATION_LINEAR", "linear" }, { EGG_ANIMATION_EASE_IN_QUAD, "EGG_ANIMATION_EASE_IN_QUAD", "ease-in-quad" }, { EGG_ANIMATION_EASE_IN_OUT_QUAD, "EGG_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" }, { EGG_ANIMATION_EASE_OUT_QUAD, "EGG_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" }, { EGG_ANIMATION_EASE_IN_CUBIC, "EGG_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" }, { EGG_ANIMATION_EASE_OUT_CUBIC, "EGG_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" }, { EGG_ANIMATION_EASE_IN_OUT_CUBIC, "EGG_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" }, { 0 } }; if (G_UNLIKELY (!type_id)) type_id = g_enum_register_static ("EggAnimationMode", values); return type_id; } /** * egg_object_animatev: * @object: A #GObject. * @mode: The animation mode. * @duration_msec: The duration in milliseconds. * @frame_clock: (nullable): The #GdkFrameClock to synchronize to. * @first_property: The first property to animate. * @args: A variadac list of arguments * * Returns: (transfer none): A #EggAnimation. */ EggAnimation * egg_object_animatev (gpointer object, EggAnimationMode mode, guint duration_msec, GdkFrameClock *frame_clock, const gchar *first_property, va_list args) { EggAnimation *animation; GObjectClass *klass; GObjectClass *pklass; const gchar *name; GParamSpec *pspec; GtkWidget *parent; GValue value = { 0 }; gchar *error = NULL; GType type; GType ptype; gboolean enable_animations; g_return_val_if_fail (first_property != NULL, NULL); g_return_val_if_fail (mode < EGG_ANIMATION_LAST, NULL); if ((frame_clock == NULL) && GTK_IS_WIDGET (object)) frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object)); /* * If we have a frame clock, then we must be in the gtk thread and we * should check GtkSettings for disabled animations. If we are disabled, * we will just make the timeout immediate. */ if (frame_clock != NULL) { g_object_get (gtk_settings_get_default (), "gtk-enable-animations", &enable_animations, NULL); if (enable_animations == FALSE) duration_msec = 0; } name = first_property; type = G_TYPE_FROM_INSTANCE (object); klass = G_OBJECT_GET_CLASS (object); animation = g_object_new (EGG_TYPE_ANIMATION, "duration", duration_msec, "frame-clock", frame_clock, "mode", mode, "target", object, NULL); do { /* * First check for the property on the object. If that does not exist * then check if the object has a parent and look at its child * properties (if it's a GtkWidget). */ if (!(pspec = g_object_class_find_property (klass, name))) { if (!g_type_is_a (type, GTK_TYPE_WIDGET)) { g_critical (_("Failed to find property %s in %s"), name, g_type_name (type)); goto failure; } if (!(parent = gtk_widget_get_parent (object))) { g_critical (_("Failed to find property %s in %s"), name, g_type_name (type)); goto failure; } pklass = G_OBJECT_GET_CLASS (parent); ptype = G_TYPE_FROM_INSTANCE (parent); if (!(pspec = gtk_container_class_find_child_property (pklass, name))) { g_critical (_("Failed to find property %s in %s or parent %s"), name, g_type_name (type), g_type_name (ptype)); goto failure; } } g_value_init (&value, pspec->value_type); G_VALUE_COLLECT (&value, args, 0, &error); if (error != NULL) { g_critical (_("Failed to retrieve va_list value: %s"), error); g_free (error); goto failure; } egg_animation_add_property (animation, pspec, &value); g_value_unset (&value); } while ((name = va_arg (args, const gchar *))); egg_animation_start (animation); return animation; failure: g_object_ref_sink (animation); g_object_unref (animation); return NULL; } /** * egg_object_animate: * @object: (in): A #GObject. * @mode: (in): The animation mode. * @duration_msec: (in): The duration in milliseconds. * @first_property: (in): The first property to animate. * * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They * will be animated from their current value to the target value over the time period. * * Return value: (transfer none): A #EggAnimation. * Side effects: None. */ EggAnimation* egg_object_animate (gpointer object, EggAnimationMode mode, guint duration_msec, GdkFrameClock *frame_clock, const gchar *first_property, ...) { EggAnimation *animation; va_list args; va_start (args, first_property); animation = egg_object_animatev (object, mode, duration_msec, frame_clock, first_property, args); va_end (args); return animation; } /** * egg_object_animate_full: * * Return value: (transfer none): A #EggAnimation. */ EggAnimation* egg_object_animate_full (gpointer object, EggAnimationMode mode, guint duration_msec, GdkFrameClock *frame_clock, GDestroyNotify notify, gpointer notify_data, const gchar *first_property, ...) { EggAnimation *animation; va_list args; va_start (args, first_property); animation = egg_object_animatev (object, mode, duration_msec, frame_clock, first_property, args); va_end (args); animation->notify = notify; animation->notify_data = notify_data; return animation; }