/* GTK - The GIMP Toolkit * Copyright (C) 2007 Carlos Garnacho * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #define MSECS_PER_SEC 1000 #define FRAME_INTERVAL(nframes) (MSECS_PER_SEC / nframes) #define DEFAULT_FPS 30 typedef struct GtkTimelinePriv GtkTimelinePriv; struct GtkTimelinePriv { guint duration; guint fps; guint source_id; GTimer *timer; gdouble elapsed_time; gdouble progress; gdouble last_progress; GdkScreen *screen; GtkTimelineProgressType progress_type; guint animations_enabled : 1; guint loop : 1; guint direction : 1; }; enum { PROP_0, PROP_FPS, PROP_DURATION, PROP_LOOP, PROP_DIRECTION, PROP_SCREEN }; enum { STARTED, PAUSED, FINISHED, FRAME, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0, }; static void gtk_timeline_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_timeline_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _gtk_timeline_finalize (GObject *object); G_DEFINE_TYPE (GtkTimeline, _gtk_timeline, G_TYPE_OBJECT) static void _gtk_timeline_class_init (GtkTimelineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = gtk_timeline_set_property; object_class->get_property = gtk_timeline_get_property; object_class->finalize = _gtk_timeline_finalize; g_object_class_install_property (object_class, PROP_FPS, g_param_spec_uint ("fps", "FPS", "Frames per second for the timeline", 1, G_MAXUINT, DEFAULT_FPS, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DURATION, g_param_spec_uint ("duration", "Animation Duration", "Animation Duration", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_LOOP, g_param_spec_boolean ("loop", "Loop", "Whether the timeline loops or not", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SCREEN, g_param_spec_object ("screen", "Screen", "Screen to get the settings from", GDK_TYPE_SCREEN, G_PARAM_READWRITE)); signals[STARTED] = g_signal_new ("started", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkTimelineClass, started), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[PAUSED] = g_signal_new ("paused", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkTimelineClass, paused), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[FINISHED] = g_signal_new ("finished", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkTimelineClass, finished), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[FRAME] = g_signal_new ("frame", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkTimelineClass, frame), NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); g_type_class_add_private (klass, sizeof (GtkTimelinePriv)); } static void _gtk_timeline_init (GtkTimeline *timeline) { GtkTimelinePriv *priv; priv = timeline->priv = G_TYPE_INSTANCE_GET_PRIVATE (timeline, GTK_TYPE_TIMELINE, GtkTimelinePriv); priv->fps = DEFAULT_FPS; priv->duration = 0.0; priv->direction = GTK_TIMELINE_DIRECTION_FORWARD; priv->screen = gdk_screen_get_default (); priv->last_progress = 0; } static void gtk_timeline_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkTimeline *timeline; timeline = GTK_TIMELINE (object); switch (prop_id) { case PROP_FPS: _gtk_timeline_set_fps (timeline, g_value_get_uint (value)); break; case PROP_DURATION: _gtk_timeline_set_duration (timeline, g_value_get_uint (value)); break; case PROP_LOOP: _gtk_timeline_set_loop (timeline, g_value_get_boolean (value)); break; case PROP_DIRECTION: _gtk_timeline_set_direction (timeline, g_value_get_enum (value)); break; case PROP_SCREEN: _gtk_timeline_set_screen (timeline, GDK_SCREEN (g_value_get_object (value))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_timeline_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkTimeline *timeline; GtkTimelinePriv *priv; timeline = GTK_TIMELINE (object); priv = timeline->priv; switch (prop_id) { case PROP_FPS: g_value_set_uint (value, priv->fps); break; case PROP_DURATION: g_value_set_uint (value, priv->duration); break; case PROP_LOOP: g_value_set_boolean (value, priv->loop); break; case PROP_DIRECTION: g_value_set_enum (value, priv->direction); break; case PROP_SCREEN: g_value_set_object (value, priv->screen); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void _gtk_timeline_finalize (GObject *object) { GtkTimelinePriv *priv; GtkTimeline *timeline; timeline = (GtkTimeline *) object; priv = timeline->priv; if (priv->source_id) { g_source_remove (priv->source_id); priv->source_id = 0; } if (priv->timer) g_timer_destroy (priv->timer); G_OBJECT_CLASS (_gtk_timeline_parent_class)->finalize (object); } static gdouble calculate_progress (gdouble linear_progress, GtkTimelineProgressType progress_type) { gdouble progress; progress = linear_progress; switch (progress_type) { case GTK_TIMELINE_PROGRESS_LINEAR: break; case GTK_TIMELINE_PROGRESS_EASE_IN_OUT: progress *= 2; if (progress < 1) progress = pow (progress, 3) / 2; else progress = (pow (progress - 2, 3) + 2) / 2; break; case GTK_TIMELINE_PROGRESS_EASE: progress = (sin ((progress - 0.5) * G_PI) + 1) / 2; break; case GTK_TIMELINE_PROGRESS_EASE_IN: progress = pow (progress, 3); break; case GTK_TIMELINE_PROGRESS_EASE_OUT: progress = pow (progress - 1, 3) + 1; break; default: g_warning ("Timeline progress type not implemented"); } return progress; } static gboolean gtk_timeline_run_frame (GtkTimeline *timeline) { GtkTimelinePriv *priv; gdouble delta_progress, progress; /* the user may unref us during the signals, so save ourselves */ g_object_ref (timeline); priv = timeline->priv; priv->elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000); g_timer_start (priv->timer); if (priv->animations_enabled) { delta_progress = (gdouble) priv->elapsed_time / priv->duration; progress = priv->last_progress; if (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD) progress -= delta_progress; else progress += delta_progress; priv->last_progress = progress; progress = CLAMP (progress, 0., 1.); } else progress = (priv->direction == GTK_TIMELINE_DIRECTION_FORWARD) ? 1.0 : 0.0; priv->progress = progress; g_signal_emit (timeline, signals [FRAME], 0, calculate_progress (progress, priv->progress_type)); if ((priv->direction == GTK_TIMELINE_DIRECTION_FORWARD && progress == 1.0) || (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD && progress == 0.0)) { gboolean loop; loop = priv->loop && priv->animations_enabled; if (loop) _gtk_timeline_rewind (timeline); else { if (priv->source_id) { g_source_remove (priv->source_id); priv->source_id = 0; } g_timer_stop (priv->timer); g_signal_emit (timeline, signals [FINISHED], 0); g_object_unref (timeline); return FALSE; } } g_object_unref (timeline); return TRUE; } /* * _gtk_timeline_new: * @duration: duration in milliseconds for the timeline * * Creates a new #GtkTimeline with the specified number of frames. * * Return Value: the newly created #GtkTimeline */ GtkTimeline * _gtk_timeline_new (guint duration) { return g_object_new (GTK_TYPE_TIMELINE, "duration", duration, NULL); } GtkTimeline * _gtk_timeline_new_for_screen (guint duration, GdkScreen *screen) { return g_object_new (GTK_TYPE_TIMELINE, "duration", duration, "screen", screen, NULL); } /* * _gtk_timeline_start: * @timeline: A #GtkTimeline * * Runs the timeline from the current frame. */ void _gtk_timeline_start (GtkTimeline *timeline) { GtkTimelinePriv *priv; GtkSettings *settings; gboolean enable_animations = FALSE; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; if (!priv->source_id) { if (priv->timer) g_timer_continue (priv->timer); else priv->timer = g_timer_new (); /* sanity check */ g_assert (priv->fps > 0); if (priv->screen) { settings = gtk_settings_get_for_screen (priv->screen); g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL); } priv->animations_enabled = enable_animations; g_signal_emit (timeline, signals [STARTED], 0); if (enable_animations) priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps), (GSourceFunc) gtk_timeline_run_frame, timeline); else priv->source_id = gdk_threads_add_idle ((GSourceFunc) gtk_timeline_run_frame, timeline); } } /* * _gtk_timeline_pause: * @timeline: A #GtkTimeline * * Pauses the timeline. */ void _gtk_timeline_pause (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; if (priv->source_id) { g_timer_stop (priv->timer); g_source_remove (priv->source_id); priv->source_id = 0; g_signal_emit (timeline, signals [PAUSED], 0); } } /* * _gtk_timeline_rewind: * @timeline: A #GtkTimeline * * Rewinds the timeline. */ void _gtk_timeline_rewind (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; if (_gtk_timeline_get_direction (timeline) != GTK_TIMELINE_DIRECTION_FORWARD) priv->progress = priv->last_progress = 1.; else priv->progress = priv->last_progress = 0.; /* reset timer */ if (priv->timer) { g_timer_start (priv->timer); if (!priv->source_id) g_timer_stop (priv->timer); } } /* * _gtk_timeline_is_running: * @timeline: A #GtkTimeline * * Returns whether the timeline is running or not. * * Return Value: %TRUE if the timeline is running */ gboolean _gtk_timeline_is_running (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE); priv = timeline->priv; return (priv->source_id != 0); } /* * _gtk_timeline_get_elapsed_time: * @timeline: A #GtkTimeline * * Returns the elapsed time since the last GtkTimeline::frame signal * * Return Value: elapsed time in milliseconds since the last frame */ guint _gtk_timeline_get_elapsed_time (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0); priv = timeline->priv; return priv->elapsed_time; } /* * _gtk_timeline_get_fps: * @timeline: A #GtkTimeline * * Returns the number of frames per second. * * Return Value: frames per second */ guint _gtk_timeline_get_fps (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 1); priv = timeline->priv; return priv->fps; } /* * _gtk_timeline_set_fps: * @timeline: A #GtkTimeline * @fps: frames per second * * Sets the number of frames per second that * the timeline will play. */ void _gtk_timeline_set_fps (GtkTimeline *timeline, guint fps) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); g_return_if_fail (fps > 0); priv = timeline->priv; priv->fps = fps; if (_gtk_timeline_is_running (timeline)) { g_source_remove (priv->source_id); priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps), (GSourceFunc) gtk_timeline_run_frame, timeline); } g_object_notify (G_OBJECT (timeline), "fps"); } /* * _gtk_timeline_get_loop: * @timeline: A #GtkTimeline * * Returns whether the timeline loops to the * beginning when it has reached the end. * * Return Value: %TRUE if the timeline loops */ gboolean _gtk_timeline_get_loop (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE); priv = timeline->priv; return priv->loop; } /* * _gtk_timeline_set_loop: * @timeline: A #GtkTimeline * @loop: %TRUE to make the timeline loop * * Sets whether the timeline loops to the beginning * when it has reached the end. */ void _gtk_timeline_set_loop (GtkTimeline *timeline, gboolean loop) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; if (loop != priv->loop) { priv->loop = loop; g_object_notify (G_OBJECT (timeline), "loop"); } } void _gtk_timeline_set_duration (GtkTimeline *timeline, guint duration) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; if (duration != priv->duration) { priv->duration = duration; g_object_notify (G_OBJECT (timeline), "duration"); } } guint _gtk_timeline_get_duration (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0); priv = timeline->priv; return priv->duration; } /* * _gtk_timeline_set_direction: * @timeline: A #GtkTimeline * @direction: direction * * Sets the direction of the timeline. */ void _gtk_timeline_set_direction (GtkTimeline *timeline, GtkTimelineDirection direction) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; priv->direction = direction; } /* * _gtk_timeline_get_direction: * @timeline: A #GtkTimeline * * Returns the direction of the timeline. * * Return Value: direction */ GtkTimelineDirection _gtk_timeline_get_direction (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_DIRECTION_FORWARD); priv = timeline->priv; return priv->direction; } void _gtk_timeline_set_screen (GtkTimeline *timeline, GdkScreen *screen) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); g_return_if_fail (GDK_IS_SCREEN (screen)); priv = timeline->priv; if (priv->screen) g_object_unref (priv->screen); priv->screen = g_object_ref (screen); g_object_notify (G_OBJECT (timeline), "screen"); } GdkScreen * _gtk_timeline_get_screen (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), NULL); priv = timeline->priv; return priv->screen; } gdouble _gtk_timeline_get_progress (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0.); priv = timeline->priv; return calculate_progress (priv->progress, priv->progress_type); } GtkTimelineProgressType _gtk_timeline_get_progress_type (GtkTimeline *timeline) { GtkTimelinePriv *priv; g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_PROGRESS_LINEAR); priv = timeline->priv; return priv->progress_type; } void _gtk_timeline_set_progress_type (GtkTimeline *timeline, GtkTimelineProgressType progress_type) { GtkTimelinePriv *priv; g_return_if_fail (GTK_IS_TIMELINE (timeline)); priv = timeline->priv; priv->progress_type = progress_type; }