aboutsummaryrefslogtreecommitdiff
path: root/src/idotimeline.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/idotimeline.c')
-rw-r--r--src/idotimeline.c721
1 files changed, 721 insertions, 0 deletions
diff --git a/src/idotimeline.c b/src/idotimeline.c
new file mode 100644
index 0000000..f86f1f2
--- /dev/null
+++ b/src/idotimeline.c
@@ -0,0 +1,721 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* gtktimeline.c
+ *
+ * Copyright (C) 2007 Carlos Garnacho <carlos@imendio.com>
+ *
+ * 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 "idotimeline.h"
+#include "idotypebuiltins.h"
+
+#include <gtk/gtksettings.h>
+#include <math.h>
+
+#define IDO_TIMELINE_GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), IDO_TYPE_TIMELINE, IdoTimelinePriv))
+#define MSECS_PER_SEC 1000
+#define FRAME_INTERVAL(nframes) (MSECS_PER_SEC / nframes)
+#define DEFAULT_FPS 30
+
+typedef struct IdoTimelinePriv IdoTimelinePriv;
+
+struct IdoTimelinePriv
+{
+ guint duration;
+ guint fps;
+ guint source_id;
+
+ GTimer *timer;
+
+ gdouble progress;
+ gdouble last_progress;
+
+ GdkScreen *screen;
+
+ 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 ido_timeline_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ido_timeline_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void ido_timeline_finalize (GObject *object);
+
+
+G_DEFINE_TYPE (IdoTimeline, ido_timeline, G_TYPE_OBJECT)
+
+
+static void
+ido_timeline_class_init (IdoTimelineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = ido_timeline_set_property;
+ object_class->get_property = ido_timeline_get_property;
+ object_class->finalize = ido_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_DIRECTION,
+ g_param_spec_enum ("direction",
+ "Direction",
+ "Whether the timeline moves forward or backward in time",
+ IDO_TYPE_TIMELINE_DIRECTION,
+ IDO_TIMELINE_DIRECTION_FORWARD,
+ 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 (IdoTimelineClass, 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 (IdoTimelineClass, 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 (IdoTimelineClass, 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 (IdoTimelineClass, frame),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__DOUBLE,
+ G_TYPE_NONE, 1,
+ G_TYPE_DOUBLE);
+
+ g_type_class_add_private (klass, sizeof (IdoTimelinePriv));
+}
+
+static void
+ido_timeline_init (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ priv->fps = DEFAULT_FPS;
+ priv->duration = 0.0;
+ priv->direction = IDO_TIMELINE_DIRECTION_FORWARD;
+ priv->screen = gdk_screen_get_default ();
+
+ priv->last_progress = 0;
+}
+
+static void
+ido_timeline_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdoTimeline *timeline;
+ IdoTimelinePriv *priv;
+
+ timeline = IDO_TIMELINE (object);
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ switch (prop_id)
+ {
+ case PROP_FPS:
+ ido_timeline_set_fps (timeline, g_value_get_uint (value));
+ break;
+ case PROP_DURATION:
+ ido_timeline_set_duration (timeline, g_value_get_uint (value));
+ break;
+ case PROP_LOOP:
+ ido_timeline_set_loop (timeline, g_value_get_boolean (value));
+ break;
+ case PROP_DIRECTION:
+ ido_timeline_set_direction (timeline, g_value_get_enum (value));
+ break;
+ case PROP_SCREEN:
+ ido_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
+ido_timeline_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdoTimeline *timeline;
+ IdoTimelinePriv *priv;
+
+ timeline = IDO_TIMELINE (object);
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ 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
+ido_timeline_finalize (GObject *object)
+{
+ IdoTimelinePriv *priv;
+
+ priv = IDO_TIMELINE_GET_PRIV (object);
+
+ 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 (ido_timeline_parent_class)->finalize (object);
+}
+
+static gboolean
+ido_timeline_run_frame (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+ gdouble delta_progress, progress;
+ guint elapsed_time;
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000);
+ g_timer_start (priv->timer);
+
+ if (priv->animations_enabled)
+ {
+ delta_progress = (gdouble) elapsed_time / priv->duration;
+ progress = priv->last_progress;
+
+ if (priv->direction == IDO_TIMELINE_DIRECTION_BACKWARD)
+ progress -= delta_progress;
+ else
+ progress += delta_progress;
+
+ priv->last_progress = progress;
+
+ progress = CLAMP (progress, 0., 1.);
+ }
+ else
+ progress = (priv->direction == IDO_TIMELINE_DIRECTION_FORWARD) ? 1.0 : 0.0;
+
+ priv->progress = progress;
+ g_signal_emit (timeline, signals [FRAME], 0, progress);
+
+ if ((priv->direction == IDO_TIMELINE_DIRECTION_FORWARD && progress == 1.0) ||
+ (priv->direction == IDO_TIMELINE_DIRECTION_BACKWARD && progress == 0.0))
+ {
+ if (!priv->loop)
+ {
+ 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);
+ return FALSE;
+ }
+ else
+ ido_timeline_rewind (timeline);
+ }
+
+ return TRUE;
+}
+
+/**
+ * ido_timeline_new:
+ * @duration: duration in milliseconds for the timeline
+ *
+ * Creates a new #IdoTimeline with the specified number of frames.
+ *
+ * Return Value: the newly created #IdoTimeline
+ **/
+IdoTimeline *
+ido_timeline_new (guint duration)
+{
+ return g_object_new (IDO_TYPE_TIMELINE,
+ "duration", duration,
+ NULL);
+}
+
+IdoTimeline *
+ido_timeline_new_for_screen (guint duration,
+ GdkScreen *screen)
+{
+ return g_object_new (IDO_TYPE_TIMELINE,
+ "duration", duration,
+ "screen", screen,
+ NULL);
+}
+
+/**
+ * ido_timeline_start:
+ * @timeline: A #IdoTimeline
+ *
+ * Runs the timeline from the current frame.
+ **/
+void
+ido_timeline_start (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+ gboolean enable_animations = FALSE;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ 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)
+ {
+#if 0
+ GtkSettings *settings = gtk_settings_get_for_screen (priv->screen);
+ g_object_get (settings, "ido-enable-animations", &enable_animations, NULL);
+#else
+ // XXX
+ enable_animations = TRUE;
+#endif
+ }
+
+ priv->animations_enabled = (enable_animations == TRUE);
+
+ g_signal_emit (timeline, signals [STARTED], 0);
+
+ if (enable_animations)
+ priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
+ (GSourceFunc) ido_timeline_run_frame,
+ timeline);
+ else
+ priv->source_id = gdk_threads_add_idle ((GSourceFunc) ido_timeline_run_frame,
+ timeline);
+ }
+}
+
+/**
+ * ido_timeline_pause:
+ * @timeline: A #IdoTimeline
+ *
+ * Pauses the timeline.
+ **/
+void
+ido_timeline_pause (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ 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);
+ }
+}
+
+/**
+ * ido_timeline_rewind:
+ * @timeline: A #IdoTimeline
+ *
+ * Rewinds the timeline.
+ **/
+void
+ido_timeline_rewind (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ if (ido_timeline_get_direction(timeline) != IDO_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);
+ }
+}
+
+/**
+ * ido_timeline_is_running:
+ * @timeline: A #IdoTimeline
+ *
+ * Returns whether the timeline is running or not.
+ *
+ * Return Value: %TRUE if the timeline is running
+ **/
+gboolean
+ido_timeline_is_running (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), FALSE);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ return (priv->source_id != 0);
+}
+
+/**
+ * ido_timeline_get_fps:
+ * @timeline: A #IdoTimeline
+ *
+ * Returns the number of frames per second.
+ *
+ * Return Value: frames per second
+ **/
+guint
+ido_timeline_get_fps (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), 1);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+ return priv->fps;
+}
+
+/**
+ * ido_timeline_set_fps:
+ * @timeline: A #IdoTimeline
+ * @fps: frames per second
+ *
+ * Sets the number of frames per second that
+ * the timeline will play.
+ **/
+void
+ido_timeline_set_fps (IdoTimeline *timeline,
+ guint fps)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+ g_return_if_fail (fps > 0);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ priv->fps = fps;
+
+ if (ido_timeline_is_running (timeline))
+ {
+ g_source_remove (priv->source_id);
+ priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
+ (GSourceFunc) ido_timeline_run_frame,
+ timeline);
+ }
+
+ g_object_notify (G_OBJECT (timeline), "fps");
+}
+
+/**
+ * ido_timeline_get_loop:
+ * @timeline: A #IdoTimeline
+ *
+ * Returns whether the timeline loops to the
+ * beginning when it has reached the end.
+ *
+ * Return Value: %TRUE if the timeline loops
+ **/
+gboolean
+ido_timeline_get_loop (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), FALSE);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+ return priv->loop;
+}
+
+/**
+ * ido_timeline_set_loop:
+ * @timeline: A #IdoTimeline
+ * @loop: %TRUE to make the timeline loop
+ *
+ * Sets whether the timeline loops to the beginning
+ * when it has reached the end.
+ **/
+void
+ido_timeline_set_loop (IdoTimeline *timeline,
+ gboolean loop)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ if (loop != priv->loop)
+ {
+ priv->loop = loop;
+ g_object_notify (G_OBJECT (timeline), "loop");
+ }
+}
+
+void
+ido_timeline_set_duration (IdoTimeline *timeline,
+ guint duration)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ if (duration != priv->duration)
+ {
+ priv->duration = duration;
+ g_object_notify (G_OBJECT (timeline), "duration");
+ }
+}
+
+guint
+ido_timeline_get_duration (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), 0);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ return priv->duration;
+}
+
+/**
+ * ido_timeline_set_direction:
+ * @timeline: A #IdoTimeline
+ * @direction: direction
+ *
+ * Sets the direction of the timeline.
+ **/
+void
+ido_timeline_set_direction (IdoTimeline *timeline,
+ IdoTimelineDirection direction)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ if (direction != priv->direction)
+ {
+ priv->direction = direction;
+ g_object_notify (G_OBJECT (timeline), "direction");
+ }
+}
+
+/**
+ * ido_timeline_get_direction:
+ * @timeline: A #IdoTimeline
+ *
+ * Returns the direction of the timeline.
+ *
+ * Return Value: direction
+ **/
+IdoTimelineDirection
+ido_timeline_get_direction (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), IDO_TIMELINE_DIRECTION_FORWARD);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+ return priv->direction;
+}
+
+void
+ido_timeline_set_screen (IdoTimeline *timeline,
+ GdkScreen *screen)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_if_fail (IDO_IS_TIMELINE (timeline));
+ g_return_if_fail (GDK_IS_SCREEN (screen));
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+
+ if (priv->screen)
+ g_object_unref (priv->screen);
+
+ priv->screen = g_object_ref (screen);
+
+ g_object_notify (G_OBJECT (timeline), "screen");
+}
+
+GdkScreen *
+ido_timeline_get_screen (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), NULL);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+ return priv->screen;
+}
+
+gdouble
+ido_timeline_get_progress (IdoTimeline *timeline)
+{
+ IdoTimelinePriv *priv;
+
+ g_return_val_if_fail (IDO_IS_TIMELINE (timeline), 0.);
+
+ priv = IDO_TIMELINE_GET_PRIV (timeline);
+ return priv->progress;
+}
+
+gdouble
+ido_timeline_calculate_progress (gdouble linear_progress,
+ IdoTimelineProgressType progress_type)
+{
+ gdouble progress;
+
+ progress = linear_progress;
+
+ switch (progress_type)
+ {
+ case IDO_TIMELINE_PROGRESS_LINEAR:
+ break;
+ case IDO_TIMELINE_PROGRESS_SINUSOIDAL:
+ progress = sinf ((progress * G_PI) / 2);
+ break;
+ case IDO_TIMELINE_PROGRESS_EXPONENTIAL:
+ progress *= progress;
+ break;
+ case IDO_TIMELINE_PROGRESS_EASE_IN_EASE_OUT:
+ {
+ progress *= 2;
+
+ if (progress < 1)
+ progress = pow (progress, 3) / 2;
+ else
+ progress = (pow (progress - 2, 3) + 2) / 2;
+ }
+ }
+
+ return progress;
+}