[gnome-builder/wip/libide] libide: add theatrics helpers



commit a27b0d4dcc4675953d8726b3b29168b585e54cf2
Author: Christian Hergert <christian hergert me>
Date:   Wed Feb 25 11:39:49 2015 -0800

    libide: add theatrics helpers

 libide/Makefile.am                  |    7 +
 libide/theatrics/ide-animation.c    | 1129 +++++++++++++++++++++++++++++++++++
 libide/theatrics/ide-animation.h    |   89 +++
 libide/theatrics/ide-box-theatric.c |  336 +++++++++++
 libide/theatrics/ide-box-theatric.h |   55 ++
 libide/theatrics/ide-frame-source.c |  134 +++++
 libide/theatrics/ide-frame-source.h |   32 +
 7 files changed, 1782 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 4253676..fda3bf6 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -189,6 +189,12 @@ libide_1_0_la_SOURCES = \
        libide/ide-search-reducer.h \
        libide/tasks/ide-load-directory-task.c \
        libide/tasks/ide-load-directory-task.h \
+       libide/theatrics/ide-animation.c \
+       libide/theatrics/ide-animation.h \
+       libide/theatrics/ide-box-theatric.c \
+       libide/theatrics/ide-box-theatric.h \
+       libide/theatrics/ide-frame-source.c \
+       libide/theatrics/ide-frame-source.h \
        libide/trie/trie.c \
        libide/trie/trie.h \
        libide/util/ide-cairo.c \
@@ -214,6 +220,7 @@ libide_1_0_la_includes = \
        -I$(top_srcdir)/libide/pygobject \
        -I$(top_srcdir)/libide/python \
        -I$(top_srcdir)/libide/tasks \
+       -I$(top_srcdir)/libide/theatrics \
        -I$(top_srcdir)/libide/util \
        -I$(top_srcdir)/libide/xml \
        $(CLANG_CFLAGS) \
diff --git a/libide/theatrics/ide-animation.c b/libide/theatrics/ide-animation.c
new file mode 100644
index 0000000..fc0b192
--- /dev/null
+++ b/libide/theatrics/ide-animation.c
@@ -0,0 +1,1129 @@
+/* ide-animation.c
+ *
+ * Copyright (C) 2013 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "ide-animation.h"
+#include "ide-frame-source.h"
+
+#define FALLBACK_FRAME_RATE 60
+
+G_DEFINE_TYPE (IdeAnimation, ide_animation, G_TYPE_INITIALLY_UNOWNED)
+
+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 _IdeAnimationPrivate
+{
+  gpointer       target;         /* Target object to animate */
+  guint64        begin_msec;     /* Time in which animation started */
+  guint          duration_msec;  /* Duration of animation */
+  guint          mode;           /* Tween mode */
+  guint          tween_handler;  /* GSource performing tweens */
+  GArray        *tweens;         /* Array of tweens to perform */
+  GdkFrameClock *frame_clock;     /* An optional frame-clock for sync. */
+};
+
+
+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   gAlphaFuncs[IDE_ANIMATION_LAST];
+static gboolean    gDebug;
+static GParamSpec *gParamSpecs[LAST_PROP];
+static guint       gSignals[LAST_SIGNAL];
+static TweenFunc   gTweenFuncs[LAST_FUNDAMENTAL];
+
+
+/*
+ * Tweeners for basic types.
+ */
+TWEEN (int);
+TWEEN (uint);
+TWEEN (long);
+TWEEN (ulong);
+TWEEN (float);
+TWEEN (double);
+
+
+/**
+ * ide_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.
+ * @IDE_ANIMATION_CUBIC means the valu ewill be transformed into
+ * cubic acceleration (x * x * x).
+ */
+static gdouble
+ide_animation_alpha_ease_in_cubic (gdouble offset)
+{
+  return offset * offset * offset;
+}
+
+
+static gdouble
+ide_animation_alpha_ease_out_cubic (gdouble offset)
+{
+  gdouble p = offset - 1.0;
+
+  return p * p * p + 1.0;
+}
+
+
+/**
+ * ide_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.
+ * @IDE_ANIMATION_LINEAR means no tranformation will be made.
+ *
+ * Returns: @offset.
+ * Side effects: None.
+ */
+static gdouble
+ide_animation_alpha_linear (gdouble offset)
+{
+  return offset;
+}
+
+
+/**
+ * ide_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.
+ * @IDE_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
+ide_animation_alpha_ease_in_quad (gdouble offset)
+{
+  return offset * offset;
+}
+
+
+/**
+ * ide_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.
+ * @IDE_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
+ide_animation_alpha_ease_out_quad (gdouble offset)
+{
+  return -1.0 * offset * (offset - 2.0);
+}
+
+
+/**
+ * ide_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.
+ * @IDE_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
+ide_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);
+}
+
+
+/**
+ * ide_animation_load_begin_values:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * Load the begin values for all the properties we are about to
+ * animate.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+ide_animation_load_begin_values (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+  GtkContainer *container;
+  Tween *tween;
+  gint i;
+
+  g_return_if_fail (IDE_IS_ANIMATION (animation));
+
+  priv = animation->priv;
+
+  for (i = 0; i < priv->tweens->len; i++)
+    {
+      tween = &g_array_index (priv->tweens, Tween, i);
+      g_value_reset (&tween->begin);
+      if (tween->is_child)
+        {
+          container = GTK_CONTAINER (gtk_widget_get_parent (priv->target));
+          gtk_container_child_get_property (container,
+                                            priv->target,
+                                            tween->pspec->name,
+                                            &tween->begin);
+        }
+      else
+        {
+          g_object_get_property (priv->target,
+                                 tween->pspec->name,
+                                 &tween->begin);
+        }
+    }
+}
+
+
+/**
+ * ide_animation_unload_begin_values:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * Unloads the begin values for the animation. This might be particularly
+ * useful once we support pointer types.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+ide_animation_unload_begin_values (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+  Tween *tween;
+  gint i;
+
+  g_return_if_fail (IDE_IS_ANIMATION (animation));
+
+  priv = animation->priv;
+
+  for (i = 0; i < priv->tweens->len; i++)
+    {
+      tween = &g_array_index (priv->tweens, Tween, i);
+      g_value_reset (&tween->begin);
+    }
+}
+
+
+/**
+ * ide_animation_get_offset:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * 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.
+ * Side effects: None.
+ */
+static gdouble
+ide_animation_get_offset (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+  GdkFrameClock *frame_clock;
+  gdouble offset;
+  gint64 frame_msec;
+
+  g_return_val_if_fail (IDE_IS_ANIMATION (animation), 0.0);
+
+  priv = animation->priv;
+
+  if (GTK_IS_WIDGET (priv->target) &&
+      (frame_clock = gtk_widget_get_frame_clock (priv->target)))
+    frame_msec = gdk_frame_clock_get_frame_time (frame_clock) / 1000UL;
+  else
+    frame_msec = g_get_monotonic_time () / 1000UL;
+
+  offset = (gdouble) (frame_msec - priv->begin_msec) /
+           (gdouble) priv->duration_msec;
+
+  return CLAMP (offset, 0.0, 1.0);
+}
+
+
+/**
+ * ide_animation_update_property:
+ * @animation: (in): A #IdeAnimation.
+ * @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.
+ *
+ * Returns: None.
+ * Side effects: The property of @target is updated.
+ */
+static void
+ide_animation_update_property (IdeAnimation  *animation,
+                              gpointer      target,
+                              Tween        *tween,
+                              const GValue *value)
+{
+  g_assert (IDE_IS_ANIMATION (animation));
+  g_assert (G_IS_OBJECT (target));
+  g_assert (tween);
+  g_assert (value);
+
+  g_object_set_property (target, tween->pspec->name, value);
+}
+
+
+/**
+ * ide_animation_update_child_property:
+ * @animation: (in): A #IdeAnimation.
+ * @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.
+ *
+ * Returns: None.
+ * Side effects: The property of @target<!-- -->'s parent widget is updated.
+ */
+static void
+ide_animation_update_child_property (IdeAnimation *animation,
+                                     gpointer      target,
+                                     Tween        *tween,
+                                     const GValue *value)
+{
+  GtkWidget *parent;
+
+  g_assert (IDE_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);
+}
+
+
+/**
+ * ide_animation_get_value_at_offset:
+ * @animation: (in): A #IdeAnimation.
+ * @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.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+ide_animation_get_value_at_offset (IdeAnimation *animation,
+                                   gdouble       offset,
+                                   Tween        *tween,
+                                   GValue       *value)
+{
+  g_return_if_fail (IDE_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 (gTweenFuncs[value->g_type]);
+      gTweenFuncs[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
+ide_animation_set_frame_clock (IdeAnimation  *animation,
+                               GdkFrameClock *frame_clock)
+{
+  IdeAnimationPrivate *priv = animation->priv;
+
+  if (priv->frame_clock != frame_clock)
+    {
+      g_clear_object (&priv->frame_clock);
+      priv->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
+    }
+}
+
+static void
+ide_animation_set_target (IdeAnimation *animation,
+                          gpointer      target)
+{
+  IdeAnimationPrivate *priv = animation->priv;
+
+  g_assert (!priv->target);
+
+  priv->target = g_object_ref (target);
+
+  if (GTK_IS_WIDGET (priv->target))
+    ide_animation_set_frame_clock (animation,
+                                  gtk_widget_get_frame_clock (priv->target));
+}
+
+
+/**
+ * ide_animation_tick:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * 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
+ide_animation_tick (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+  gdouble offset;
+  gdouble alpha;
+  GValue value = { 0 };
+  Tween *tween;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_ANIMATION (animation), FALSE);
+
+  priv = animation->priv;
+
+  offset = ide_animation_get_offset (animation);
+  alpha = gAlphaFuncs[priv->mode](offset);
+
+  /*
+   * Update property values.
+   */
+  for (i = 0; i < priv->tweens->len; i++)
+    {
+      tween = &g_array_index (priv->tweens, Tween, i);
+      g_value_init (&value, tween->pspec->value_type);
+      ide_animation_get_value_at_offset (animation, alpha, tween, &value);
+      if (!tween->is_child)
+        {
+          ide_animation_update_property (animation,
+                                        priv->target,
+                                        tween,
+                                        &value);
+        }
+      else
+        {
+          ide_animation_update_child_property (animation,
+                                              priv->target,
+                                              tween,
+                                              &value);
+        }
+      g_value_unset (&value);
+    }
+
+  /*
+   * Notify anyone interested in the tick signal.
+   */
+  g_signal_emit (animation, gSignals[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 (priv->target))
+    {
+      GdkWindow *window;
+
+      if ((window = gtk_widget_get_window (GTK_WIDGET (priv->target))))
+        gdk_window_flush (window);
+    }
+#endif
+
+  return offset < 1.0;
+}
+
+
+/**
+ * ide_animation_timeout_cb:
+ * @user_data: (in): A #IdeAnimation.
+ *
+ * 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
+ide_animation_timeout_cb (gpointer user_data)
+{
+  IdeAnimation *animation = user_data;
+  gboolean ret;
+
+  if (!(ret = ide_animation_tick (animation)))
+    ide_animation_stop (animation);
+
+  return ret;
+}
+
+
+static gboolean
+ide_animation_widget_tick_cb (GdkFrameClock *frame_clock,
+                              IdeAnimation  *animation)
+{
+  gboolean ret = G_SOURCE_REMOVE;
+
+  g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+  g_assert (IDE_IS_ANIMATION (animation));
+
+  if (animation->priv->tween_handler)
+    if (!(ret = ide_animation_tick (animation)))
+      ide_animation_stop (animation);
+
+  return ret;
+}
+
+
+/**
+ * ide_animation_start:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * Start the animation. When the animation stops, the internal reference will
+ * be dropped and the animation may be finalized.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+ide_animation_start (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+
+  g_return_if_fail (IDE_IS_ANIMATION (animation));
+  g_return_if_fail (!animation->priv->tween_handler);
+
+  priv = animation->priv;
+
+  g_object_ref_sink (animation);
+  ide_animation_load_begin_values (animation);
+
+  if (priv->frame_clock)
+    {
+      priv->begin_msec = gdk_frame_clock_get_frame_time (priv->frame_clock) / 1000UL;
+      priv->tween_handler =
+        g_signal_connect (priv->frame_clock,
+                          "update",
+                          G_CALLBACK (ide_animation_widget_tick_cb),
+                          animation);
+      gdk_frame_clock_begin_updating (priv->frame_clock);
+    }
+  else
+    {
+      priv->begin_msec = g_get_monotonic_time () / 1000UL;
+      priv->tween_handler = ide_frame_source_add (FALLBACK_FRAME_RATE,
+                                                 ide_animation_timeout_cb,
+                                                 animation);
+    }
+}
+
+
+/**
+ * ide_animation_stop:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * Stops a running animation. The internal reference to the animation is
+ * dropped and therefore may cause the object to finalize.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+ide_animation_stop (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+
+  g_return_if_fail (IDE_IS_ANIMATION (animation));
+
+  priv = animation->priv;
+
+  if (priv->tween_handler)
+    {
+      if (priv->frame_clock)
+        {
+          gdk_frame_clock_end_updating (priv->frame_clock);
+          g_signal_handler_disconnect (priv->frame_clock, priv->tween_handler);
+          priv->tween_handler = 0;
+        }
+      else
+        {
+          g_source_remove (priv->tween_handler);
+          priv->tween_handler = 0;
+        }
+      ide_animation_unload_begin_values (animation);
+      g_object_unref (animation);
+    }
+}
+
+
+/**
+ * ide_animation_add_property:
+ * @animation: (in): A #IdeAnimation.
+ * @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.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+ide_animation_add_property (IdeAnimation *animation,
+                            GParamSpec   *pspec,
+                            const GValue *value)
+{
+  IdeAnimationPrivate *priv;
+  Tween tween = { 0 };
+  GType type;
+
+  g_return_if_fail (IDE_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->priv->target);
+  g_return_if_fail (!animation->priv->tween_handler);
+
+  priv = animation->priv;
+
+  type = G_TYPE_FROM_INSTANCE (priv->target);
+  tween.is_child = !g_type_is_a (type, pspec->owner_type);
+  if (tween.is_child)
+    {
+      if (!GTK_IS_WIDGET (priv->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 (priv->tweens, tween);
+}
+
+
+/**
+ * ide_animation_dispose:
+ * @object: (in): A #IdeAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+ide_animation_dispose (GObject *object)
+{
+  IdeAnimationPrivate *priv = IDE_ANIMATION (object)->priv;
+
+  g_clear_object (&priv->target);
+  g_clear_object (&priv->frame_clock);
+
+  G_OBJECT_CLASS (ide_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * ide_animation_finalize:
+ * @object: (in): A #IdeAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+ide_animation_finalize (GObject *object)
+{
+  IdeAnimationPrivate *priv = IDE_ANIMATION (object)->priv;
+  Tween *tween;
+  gint i;
+
+  for (i = 0; i < priv->tweens->len; i++)
+    {
+      tween = &g_array_index (priv->tweens, Tween, i);
+      g_value_unset (&tween->begin);
+      g_value_unset (&tween->end);
+      g_param_spec_unref (tween->pspec);
+    }
+
+  g_array_unref (priv->tweens);
+
+  G_OBJECT_CLASS (ide_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * ide_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
+ide_animation_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  IdeAnimation *animation = IDE_ANIMATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DURATION:
+      animation->priv->duration_msec = g_value_get_uint (value);
+      break;
+
+    case PROP_FRAME_CLOCK:
+      ide_animation_set_frame_clock (animation, g_value_get_object (value));
+      break;
+
+    case PROP_MODE:
+      animation->priv->mode = g_value_get_enum (value);
+      break;
+
+    case PROP_TARGET:
+      ide_animation_set_target (animation, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+
+/**
+ * ide_animation_class_init:
+ * @klass: (in): A #IdeAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Returns: None.
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+ide_animation_class_init (IdeAnimationClass *klass)
+{
+  GObjectClass *object_class;
+
+  gDebug = !!g_getenv ("IDE_ANIMATION_DEBUG");
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->dispose = ide_animation_dispose;
+  object_class->finalize = ide_animation_finalize;
+  object_class->set_property = ide_animation_set_property;
+  g_type_class_add_private (object_class, sizeof (IdeAnimationPrivate));
+
+  /**
+   * IdeAnimation:duration:
+   *
+   * The "duration" property is the total number of milliseconds that the
+   * animation should run before being completed.
+   */
+  gParamSpecs[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));
+  g_object_class_install_property (object_class, PROP_DURATION,
+                                   gParamSpecs[PROP_DURATION]);
+
+  gParamSpecs[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));
+  g_object_class_install_property (object_class, PROP_FRAME_CLOCK,
+                                   gParamSpecs[PROP_FRAME_CLOCK]);
+
+  /**
+   * IdeAnimation: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.
+   */
+  gParamSpecs[PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       _("Mode"),
+                       _("The animation mode"),
+                       IDE_TYPE_ANIMATION_MODE,
+                       IDE_ANIMATION_LINEAR,
+                       (G_PARAM_WRITABLE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_MODE,
+                                   gParamSpecs[PROP_MODE]);
+
+  /**
+   * IdeAnimation:target:
+   *
+   * The "target" property is the #GObject that should have it's properties
+   * animated.
+   */
+  gParamSpecs[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_property (object_class, PROP_TARGET,
+                                   gParamSpecs[PROP_TARGET]);
+
+  /**
+   * IdeAnimation::tick:
+   *
+   * The "tick" signal is emitted on each frame in the animation.
+   */
+  gSignals[TICK] = g_signal_new ("tick",
+                                 IDE_TYPE_ANIMATION,
+                                 G_SIGNAL_RUN_FIRST,
+                                 0,
+                                 NULL,
+                                 NULL,
+                                 g_cclosure_marshal_VOID__VOID,
+                                 G_TYPE_NONE,
+                                 0);
+
+#define SET_ALPHA(_T, _t) \
+  gAlphaFuncs[IDE_ANIMATION_ ## _T] = ide_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);
+
+#define SET_TWEEN(_T, _t) \
+  G_STMT_START { \
+    guint idx = G_TYPE_ ## _T; \
+    gTweenFuncs[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);
+}
+
+
+/**
+ * ide_animation_init:
+ * @animation: (in): A #IdeAnimation.
+ *
+ * Initializes the #IdeAnimation instance.
+ *
+ * Returns: None.
+ * Side effects: Everything.
+ */
+static void
+ide_animation_init (IdeAnimation *animation)
+{
+  IdeAnimationPrivate *priv;
+
+  priv = G_TYPE_INSTANCE_GET_PRIVATE (animation, IDE_TYPE_ANIMATION, IdeAnimationPrivate);
+
+  animation->priv = priv;
+
+  priv->duration_msec = 250;
+  priv->mode = IDE_ANIMATION_EASE_IN_OUT_QUAD;
+  priv->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+}
+
+
+/**
+ * ide_animation_mode_get_type:
+ *
+ * Retrieves the GType for #IdeAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+ide_animation_mode_get_type (void)
+{
+  static GType type_id = 0;
+  static const GEnumValue values[] = {
+    { IDE_ANIMATION_LINEAR, "IDE_ANIMATION_LINEAR", "LINEAR" },
+    { IDE_ANIMATION_EASE_IN_QUAD, "IDE_ANIMATION_EASE_IN_QUAD", "EASE_IN_QUAD" },
+    { IDE_ANIMATION_EASE_IN_OUT_QUAD, "IDE_ANIMATION_EASE_IN_OUT_QUAD", "EASE_IN_OUT_QUAD" },
+    { IDE_ANIMATION_EASE_OUT_QUAD, "IDE_ANIMATION_EASE_OUT_QUAD", "EASE_OUT_QUAD" },
+    { IDE_ANIMATION_EASE_IN_CUBIC, "IDE_ANIMATION_EASE_IN_CUBIC", "EASE_IN_CUBIC" },
+    { IDE_ANIMATION_EASE_OUT_CUBIC, "IDE_ANIMATION_EASE_OUT_CUBIC", "EASE_OUT_CUBIC" },
+    { 0 }
+  };
+
+  if (G_UNLIKELY (!type_id))
+    type_id = g_enum_register_static ("IdeAnimationMode", values);
+  return type_id;
+}
+
+/**
+ * ide_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_rate: The target frame rate.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #IdeAnimation.
+ */
+IdeAnimation *
+ide_object_animatev (gpointer          object,
+                     IdeAnimationMode  mode,
+                     guint             duration_msec,
+                     GdkFrameClock    *frame_clock,
+                     const gchar      *first_property,
+                     va_list           args)
+{
+  IdeAnimation *animation;
+  GObjectClass *klass;
+  GObjectClass *pklass;
+  const gchar *name;
+  GParamSpec *pspec;
+  GtkWidget *parent;
+  GValue value = { 0 };
+  gchar *error = NULL;
+  GType type;
+  GType ptype;
+
+  g_return_val_if_fail (first_property != NULL, NULL);
+  g_return_val_if_fail (mode < IDE_ANIMATION_LAST, NULL);
+
+  if (!frame_clock && GTK_IS_WIDGET (object))
+    {
+      frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+    }
+
+  name = first_property;
+  type = G_TYPE_FROM_INSTANCE (object);
+  klass = G_OBJECT_GET_CLASS (object);
+  animation = g_object_new (IDE_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 its 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;
+        }
+
+      ide_animation_add_property (animation, pspec, &value);
+      g_value_unset (&value);
+    }
+  while ((name = va_arg (args, const gchar *)));
+
+  ide_animation_start (animation);
+
+  return animation;
+
+failure:
+  g_object_ref_sink (animation);
+  g_object_unref (animation);
+  return NULL;
+}
+
+/**
+ * ide_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 #IdeAnimation.
+ * Side effects: None.
+ */
+IdeAnimation*
+ide_object_animate (gpointer        object,
+                    IdeAnimationMode mode,
+                    guint           duration_msec,
+                    GdkFrameClock  *frame_clock,
+                    const gchar    *first_property,
+                    ...)
+{
+  IdeAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = ide_object_animatev (object, mode, duration_msec, frame_clock, first_property, args);
+  va_end (args);
+  return animation;
+}
+
+/**
+ * ide_object_animate_full:
+ *
+ * Return value: (transfer none): A #IdeAnimation.
+ */
+IdeAnimation*
+ide_object_animate_full (gpointer        object,
+                         IdeAnimationMode mode,
+                         guint           duration_msec,
+                         GdkFrameClock  *frame_clock,
+                         GDestroyNotify  notify,
+                         gpointer        notify_data,
+                         const gchar    *first_property,
+                         ...)
+{
+  IdeAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = ide_object_animatev (object,
+                                  mode,
+                                  duration_msec,
+                                  frame_clock,
+                                  first_property,
+                                  args);
+  va_end (args);
+
+  g_object_weak_ref (G_OBJECT (animation), (GWeakNotify) notify, notify_data);
+
+  return animation;
+}
diff --git a/libide/theatrics/ide-animation.h b/libide/theatrics/ide-animation.h
new file mode 100644
index 0000000..0f3fabe
--- /dev/null
+++ b/libide/theatrics/ide-animation.h
@@ -0,0 +1,89 @@
+/* ide-animation.h
+ *
+ * Copyright (C) 2013 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_ANIMATION_H
+#define IDE_ANIMATION_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_ANIMATION            (ide_animation_get_type())
+#define IDE_TYPE_ANIMATION_MODE       (ide_animation_mode_get_type())
+#define IDE_ANIMATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_ANIMATION, IdeAnimation))
+#define IDE_ANIMATION_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_ANIMATION, IdeAnimation 
const))
+#define IDE_ANIMATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  IDE_TYPE_ANIMATION, 
IdeAnimationClass))
+#define IDE_IS_ANIMATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDE_TYPE_ANIMATION))
+#define IDE_IS_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  IDE_TYPE_ANIMATION))
+#define IDE_ANIMATION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  IDE_TYPE_ANIMATION, 
IdeAnimationClass))
+
+typedef struct _IdeAnimation        IdeAnimation;
+typedef struct _IdeAnimationClass   IdeAnimationClass;
+typedef struct _IdeAnimationPrivate IdeAnimationPrivate;
+typedef enum   _IdeAnimationMode    IdeAnimationMode;
+
+enum _IdeAnimationMode
+{
+  IDE_ANIMATION_LINEAR,
+  IDE_ANIMATION_EASE_IN_QUAD,
+  IDE_ANIMATION_EASE_OUT_QUAD,
+  IDE_ANIMATION_EASE_IN_OUT_QUAD,
+  IDE_ANIMATION_EASE_IN_CUBIC,
+  IDE_ANIMATION_EASE_OUT_CUBIC,
+
+  IDE_ANIMATION_LAST
+};
+
+struct _IdeAnimation
+{
+  GInitiallyUnowned parent;
+
+  /*< private >*/
+  IdeAnimationPrivate *priv;
+};
+
+struct _IdeAnimationClass
+{
+  GInitiallyUnownedClass parent_class;
+};
+
+GType         ide_animation_get_type      (void);
+GType         ide_animation_mode_get_type (void);
+void          ide_animation_start         (IdeAnimation      *animation);
+void          ide_animation_stop          (IdeAnimation      *animation);
+void          ide_animation_add_property  (IdeAnimation      *animation,
+                                           GParamSpec       *pspec,
+                                           const GValue     *value);
+IdeAnimation* ide_object_animate          (gpointer          object,
+                                           IdeAnimationMode   mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+IdeAnimation* ide_object_animate_full     (gpointer          object,
+                                           IdeAnimationMode   mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           GDestroyNotify    notify,
+                                           gpointer          notify_data,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* IDE_ANIMATION_H */
diff --git a/libide/theatrics/ide-box-theatric.c b/libide/theatrics/ide-box-theatric.c
new file mode 100644
index 0000000..ee51a79
--- /dev/null
+++ b/libide/theatrics/ide-box-theatric.c
@@ -0,0 +1,336 @@
+/* ide-box-theatric.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "theatrics"
+
+#include <glib/gi18n.h>
+
+#include "ide-animation.h"
+#include "ide-box-theatric.h"
+#include "ide-cairo.h"
+
+struct _IdeBoxTheatricPrivate
+{
+  GtkWidget   *target;
+  GtkWidget   *toplevel;
+  GdkRectangle area;
+  GdkRectangle last_area;
+  GdkRGBA      background_rgba;
+
+  guint        draw_handler;
+};
+
+enum {
+  PROP_0,
+  PROP_ALPHA,
+  PROP_BACKGROUND,
+  PROP_HEIGHT,
+  PROP_TARGET,
+  PROP_WIDTH,
+  PROP_X,
+  PROP_Y,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeBoxTheatric, ide_box_theatric, G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs[LAST_PROP];
+
+static void
+get_toplevel_rect (IdeBoxTheatric *theatric,
+                   GdkRectangle  *area)
+{
+  IdeBoxTheatricPrivate *priv = theatric->priv;
+
+  gtk_widget_translate_coordinates (priv->target, priv->toplevel,
+                                    priv->area.x, priv->area.y,
+                                    &area->x, &area->y);
+
+  area->width = priv->area.width;
+  area->height = priv->area.height;
+}
+
+static gboolean
+on_toplevel_draw (GtkWidget     *widget,
+                  cairo_t       *cr,
+                  IdeBoxTheatric *theatric)
+{
+  IdeBoxTheatricPrivate *priv = theatric->priv;
+  GdkRectangle area;
+
+  get_toplevel_rect (theatric, &area);
+
+#if 0
+  g_print ("Drawing on %s at %d,%d %dx%d\n",
+           g_type_name (G_TYPE_FROM_INSTANCE (widget)),
+           area.x, area.y, area.width, area.height);
+#endif
+
+  ide_cairo_rounded_rectangle (cr, &area, 3, 3);
+  gdk_cairo_set_source_rgba (cr, &priv->background_rgba);
+  cairo_fill (cr);
+
+  priv->last_area = area;
+
+  return FALSE;
+}
+
+static void
+ide_box_theatric_notify (GObject    *object,
+                        GParamSpec *pspec)
+{
+  IdeBoxTheatricPrivate *priv = IDE_BOX_THEATRIC (object)->priv;
+
+  if (G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify)
+    G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify (object, pspec);
+
+  if (priv->target && priv->toplevel)
+    {
+      GdkWindow *window;
+      GdkRectangle area;
+
+      get_toplevel_rect (IDE_BOX_THEATRIC (object), &area);
+
+#if 0
+      g_print ("  Invalidate : %d,%d %dx%d\n",
+               area.x, area.y, area.width, area.height);
+#endif
+
+      window = gtk_widget_get_window (priv->toplevel);
+
+      gdk_window_invalidate_rect (window, &priv->last_area, TRUE);
+      gdk_window_invalidate_rect (window, &area, TRUE);
+    }
+}
+
+static void
+ide_box_theatric_dispose (GObject *object)
+{
+  IdeBoxTheatricPrivate *priv =  IDE_BOX_THEATRIC (object)->priv;
+
+  if (priv->target)
+    {
+      if (priv->draw_handler && priv->toplevel)
+        {
+          g_signal_handler_disconnect (priv->toplevel, priv->draw_handler);
+          priv->draw_handler = 0;
+        }
+      g_object_remove_weak_pointer (G_OBJECT (priv->target),
+                                    (gpointer *) &priv->target);
+      priv->target = NULL;
+    }
+
+  G_OBJECT_CLASS (ide_box_theatric_parent_class)->dispose (object);
+}
+
+static void
+ide_box_theatric_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+  switch (prop_id)
+    {
+    case PROP_ALPHA:
+      g_value_set_double (value, theatric->priv->background_rgba.alpha);
+      break;
+
+    case PROP_BACKGROUND:
+      g_value_take_string (value,
+                           gdk_rgba_to_string (&theatric->priv->background_rgba));
+      break;
+
+    case PROP_HEIGHT:
+      g_value_set_int (value, theatric->priv->area.height);
+      break;
+
+    case PROP_TARGET:
+      g_value_set_object (value, theatric->priv->target);
+      break;
+
+    case PROP_WIDTH:
+      g_value_set_int (value, theatric->priv->area.width);
+      break;
+
+    case PROP_X:
+      g_value_set_int (value, theatric->priv->area.x);
+      break;
+
+    case PROP_Y:
+      g_value_set_int (value, theatric->priv->area.y);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_box_theatric_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+  switch (prop_id)
+    {
+    case PROP_ALPHA:
+      theatric->priv->background_rgba.alpha = g_value_get_double (value);
+      break;
+
+    case PROP_BACKGROUND:
+      {
+        gdouble old_alpha = theatric->priv->background_rgba.alpha;
+
+        gdk_rgba_parse (&theatric->priv->background_rgba,
+                        g_value_get_string (value));
+        theatric->priv->background_rgba.alpha = old_alpha;
+      }
+      break;
+
+    case PROP_HEIGHT:
+      theatric->priv->area.height = g_value_get_int (value);
+      break;
+
+    case PROP_TARGET:
+      theatric->priv->target = g_value_get_object (value);
+      theatric->priv->toplevel =
+        gtk_widget_get_toplevel (theatric->priv->target);
+      g_object_add_weak_pointer (G_OBJECT (theatric->priv->target),
+                                 (gpointer *) &theatric->priv->target);
+      theatric->priv->draw_handler =
+        g_signal_connect_after (theatric->priv->toplevel,
+                                "draw",
+                                G_CALLBACK (on_toplevel_draw),
+                                theatric);
+      break;
+
+    case PROP_WIDTH:
+      theatric->priv->area.width = g_value_get_int (value);
+      break;
+
+    case PROP_X:
+      theatric->priv->area.x = g_value_get_int (value);
+      break;
+
+    case PROP_Y:
+      theatric->priv->area.y = g_value_get_int (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+
+  g_object_notify_by_pspec (object, pspec);
+}
+
+static void
+ide_box_theatric_class_init (IdeBoxTheatricClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->dispose = ide_box_theatric_dispose;
+  object_class->notify = ide_box_theatric_notify;
+  object_class->get_property = ide_box_theatric_get_property;
+  object_class->set_property = ide_box_theatric_set_property;
+
+  gParamSpecs[PROP_ALPHA] =
+    g_param_spec_double ("alpha",
+                         _("Alpha"),
+                         _("Alpha"),
+                         0.0,
+                         1.0,
+                         1.0,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_ALPHA,
+                                   gParamSpecs[PROP_ALPHA]);
+
+  gParamSpecs[PROP_BACKGROUND] =
+    g_param_spec_string ("background",
+                         _("background"),
+                         _("background"),
+                         "#000000",
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_BACKGROUND,
+                                   gParamSpecs[PROP_BACKGROUND]);
+
+  gParamSpecs[PROP_HEIGHT] =
+    g_param_spec_int ("height",
+                      _("height"),
+                      _("height"),
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_HEIGHT,
+                                   gParamSpecs[PROP_HEIGHT]);
+
+  gParamSpecs[PROP_TARGET] =
+    g_param_spec_object ("target",
+                         _("Target"),
+                         _("Target"),
+                         GTK_TYPE_WIDGET,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TARGET,
+                                   gParamSpecs[PROP_TARGET]);
+
+  gParamSpecs[PROP_WIDTH] =
+    g_param_spec_int ("width",
+                      _("width"),
+                      _("width"),
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_WIDTH,
+                                   gParamSpecs[PROP_WIDTH]);
+
+  gParamSpecs[PROP_X] =
+    g_param_spec_int ("x",
+                      _("x"),
+                      _("x"),
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_X,
+                                   gParamSpecs[PROP_X]);
+
+  gParamSpecs[PROP_Y] =
+    g_param_spec_int ("y",
+                      _("y"),
+                      _("y"),
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_Y,
+                                   gParamSpecs[PROP_Y]);
+}
+
+static void
+ide_box_theatric_init (IdeBoxTheatric *theatric)
+{
+  theatric->priv = ide_box_theatric_get_instance_private (theatric);
+}
diff --git a/libide/theatrics/ide-box-theatric.h b/libide/theatrics/ide-box-theatric.h
new file mode 100644
index 0000000..94a4276
--- /dev/null
+++ b/libide/theatrics/ide-box-theatric.h
@@ -0,0 +1,55 @@
+/* ide-box-theatric.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BOX_THEATRIC_H
+#define IDE_BOX_THEATRIC_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BOX_THEATRIC            (ide_box_theatric_get_type())
+#define IDE_BOX_THEATRIC(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_BOX_THEATRIC, 
IdeBoxTheatric))
+#define IDE_BOX_THEATRIC_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_BOX_THEATRIC, 
IdeBoxTheatric const))
+#define IDE_BOX_THEATRIC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  IDE_TYPE_BOX_THEATRIC, 
IdeBoxTheatricClass))
+#define IDE_IS_BOX_THEATRIC(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDE_TYPE_BOX_THEATRIC))
+#define IDE_IS_BOX_THEATRIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  IDE_TYPE_BOX_THEATRIC))
+#define IDE_BOX_THEATRIC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  IDE_TYPE_BOX_THEATRIC, 
IdeBoxTheatricClass))
+
+typedef struct _IdeBoxTheatric        IdeBoxTheatric;
+typedef struct _IdeBoxTheatricClass   IdeBoxTheatricClass;
+typedef struct _IdeBoxTheatricPrivate IdeBoxTheatricPrivate;
+
+struct _IdeBoxTheatric
+{
+  GObject parent;
+
+  /*< private >*/
+  IdeBoxTheatricPrivate *priv;
+};
+
+struct _IdeBoxTheatricClass
+{
+  GObjectClass parent_class;
+};
+
+GType ide_box_theatric_get_type (void);
+
+G_END_DECLS
+
+#endif /* IDE_BOX_THEATRIC_H */
diff --git a/libide/theatrics/ide-frame-source.c b/libide/theatrics/ide-frame-source.c
new file mode 100644
index 0000000..9aebfae
--- /dev/null
+++ b/libide/theatrics/ide-frame-source.c
@@ -0,0 +1,134 @@
+/*
+ * Based upon code from Clutter:
+ *
+ * Authored By Neil Roberts <neil linux intel com>
+ *
+ * Copyright (C) 2009 Intel Corporation.
+ * Copyright (C) 2012 Christian Hergert.
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-frame-source.h"
+
+typedef struct
+{
+   GSource parent;
+   guint   fps;
+   guint   frame_count;
+   gint64  start_time;
+} IdeFrameSource;
+
+static gboolean
+ide_frame_source_prepare (GSource *source,
+                          gint    *timeout_)
+{
+   IdeFrameSource *fsource = (IdeFrameSource *)(gpointer)source;
+   gint64 current_time;
+   guint elapsed_time;
+   guint new_frame_num;
+   guint frame_time;
+
+   current_time = g_source_get_time(source) / 1000;
+   elapsed_time = current_time - fsource->start_time;
+   new_frame_num = elapsed_time * fsource->fps / 1000;
+
+   /* If time has gone backwards or the time since the last frame is
+    * greater than the two frames worth then reset the time and do a
+    * frame now */
+   if (new_frame_num < fsource->frame_count ||
+       new_frame_num - fsource->frame_count > 2) {
+      /* Get the frame time rounded up to the nearest ms */
+      frame_time = (1000 + fsource->fps - 1) / fsource->fps;
+
+      /* Reset the start time */
+      fsource->start_time = current_time;
+
+      /* Move the start time as if one whole frame has elapsed */
+      fsource->start_time -= frame_time;
+      fsource->frame_count = 0;
+      *timeout_ = 0;
+      return TRUE;
+   } else if (new_frame_num > fsource->frame_count) {
+      *timeout_ = 0;
+      return TRUE;
+   } else {
+      *timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
+      return FALSE;
+   }
+}
+
+static gboolean
+ide_frame_source_check (GSource *source)
+{
+   gint timeout_;
+   return ide_frame_source_prepare(source, &timeout_);
+}
+
+static gboolean
+ide_frame_source_dispatch (GSource     *source,
+                           GSourceFunc  source_func,
+                           gpointer     user_data)
+{
+   IdeFrameSource *fsource = (IdeFrameSource *)(gpointer)source;
+   gboolean ret;
+
+   if ((ret = source_func(user_data)))
+      fsource->frame_count++;
+   return ret;
+}
+
+static GSourceFuncs source_funcs = {
+   ide_frame_source_prepare,
+   ide_frame_source_check,
+   ide_frame_source_dispatch,
+};
+
+/**
+ * ide_frame_source_add:
+ * @frames_per_sec: (in): Target frames per second.
+ * @callback: (in) (scope notified): A #GSourceFunc to execute.
+ * @user_data: (in): User data for @callback.
+ *
+ * Creates a new frame source that will execute when the timeout interval
+ * for the source has elapsed. The timing will try to synchronize based
+ * on the end time of the animation.
+ *
+ * Returns: A source id that can be removed with g_source_remove().
+ */
+guint
+ide_frame_source_add (guint       frames_per_sec,
+                      GSourceFunc callback,
+                      gpointer    user_data)
+{
+   IdeFrameSource *fsource;
+   GSource *source;
+   guint ret;
+
+   g_return_val_if_fail (frames_per_sec > 0, 0);
+   g_return_val_if_fail (frames_per_sec <= 120, 0);
+
+   source = g_source_new(&source_funcs, sizeof(IdeFrameSource));
+   fsource = (IdeFrameSource *)(gpointer)source;
+   fsource->fps = frames_per_sec;
+   fsource->frame_count = 0;
+   fsource->start_time = g_get_monotonic_time() / 1000;
+   g_source_set_callback(source, callback, user_data, NULL);
+   g_source_set_name(source, "IdeFrameSource");
+
+   ret = g_source_attach(source, NULL);
+   g_source_unref(source);
+
+   return ret;
+}
diff --git a/libide/theatrics/ide-frame-source.h b/libide/theatrics/ide-frame-source.h
new file mode 100644
index 0000000..720b1bb
--- /dev/null
+++ b/libide/theatrics/ide-frame-source.h
@@ -0,0 +1,32 @@
+/* ide-frame-source.h
+ *
+ * Copyright (C) 2013 Christian Hergert <christian hergert me>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_FRAME_SOURCE_H
+#define IDE_FRAME_SOURCE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint ide_frame_source_add (guint       frames_per_sec,
+                            GSourceFunc callback,
+                            gpointer    user_data);
+
+G_END_DECLS
+
+#endif /* IDE_FRAME_SOURCE_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]