[nautilus] toolbar: add theatrics animation to the operations button



commit 7f8cc0b8892105b24f944ac24248ba3b5f37ecbf
Author: Carlos Soriano <csoriano gnome org>
Date:   Mon Feb 13 23:03:12 2017 +0100

    toolbar: add theatrics animation to the operations button
    
    We have been going back and forth of a solution to make the operations
    button noticeable enough once it appears.
    
    First we implemented a highlight inside the button. But that wasn't
    enough, so we had to show the operations popover at the start.
    
    That is not really good because it appears in every window, and the user
    has to explicitly close it, which is annoying.
    
    This patch implements another animation highlight, which is more
    visually bold to try to catch the attention, in order to not need to
    show the operations popover.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=753728

 src/Makefile.am                  |    8 +
 src/animation/egg-animation.c    | 1184 ++++++++++++++++++++++++++++++++++++++
 src/animation/egg-animation.h    |   77 +++
 src/animation/egg-frame-source.c |  130 +++++
 src/animation/egg-frame-source.h |   32 +
 src/animation/ide-box-theatric.c |  420 ++++++++++++++
 src/animation/ide-box-theatric.h |   33 ++
 src/animation/ide-cairo.c        |   84 +++
 src/animation/ide-cairo.h        |   68 +++
 src/nautilus-toolbar.c           |   34 +-
 10 files changed, 2068 insertions(+), 2 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 562af23..db0a802 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -138,6 +138,14 @@ nautilus_built_sources = \
        $(NULL)
 
 nautilus_no_main_sources = \
+       animation/egg-animation.c                               \
+       animation/egg-animation.h                               \
+       animation/egg-frame-source.c                            \
+       animation/egg-frame-source.h                            \
+       animation/ide-box-theatric.c                            \
+       animation/ide-box-theatric.h                            \
+       animation/ide-cairo.c                                   \
+       animation/ide-cairo.h                                   \
        gtk/nautilusgtkplacesview.c                     \
        gtk/nautilusgtkplacesviewprivate.h              \
        gtk/nautilusgtkplacesviewrow.c                  \
diff --git a/src/animation/egg-animation.c b/src/animation/egg-animation.c
new file mode 100644
index 0000000..c8b0258
--- /dev/null
+++ b/src/animation/egg-animation.c
@@ -0,0 +1,1184 @@
+/* egg-animation.c
+ *
+ * Copyright (C) 2010-2016 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/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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_signal_handler_disconnect (animation->frame_clock, animation->tween_handler);
+          g_signal_handler_disconnect (animation->frame_clock, animation->after_paint_handler);
+          animation->tween_handler = 0;
+        }
+      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;
+}
diff --git a/src/animation/egg-animation.h b/src/animation/egg-animation.h
new file mode 100644
index 0000000..62d979e
--- /dev/null
+++ b/src/animation/egg-animation.h
@@ -0,0 +1,77 @@
+/* egg-animation.h
+ *
+ * Copyright (C) 2010-2016 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 EGG_ANIMATION_H
+#define EGG_ANIMATION_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_ANIMATION      (egg_animation_get_type())
+#define EGG_TYPE_ANIMATION_MODE (egg_animation_mode_get_type())
+
+G_DECLARE_FINAL_TYPE (EggAnimation, egg_animation,
+                      EGG, ANIMATION, GInitiallyUnowned)
+
+typedef enum   _EggAnimationMode    EggAnimationMode;
+
+enum _EggAnimationMode
+{
+  EGG_ANIMATION_LINEAR,
+  EGG_ANIMATION_EASE_IN_QUAD,
+  EGG_ANIMATION_EASE_OUT_QUAD,
+  EGG_ANIMATION_EASE_IN_OUT_QUAD,
+  EGG_ANIMATION_EASE_IN_CUBIC,
+  EGG_ANIMATION_EASE_OUT_CUBIC,
+  EGG_ANIMATION_EASE_IN_OUT_CUBIC,
+
+  EGG_ANIMATION_LAST
+};
+
+GType         egg_animation_mode_get_type (void);
+void          egg_animation_start         (EggAnimation     *animation);
+void          egg_animation_stop          (EggAnimation     *animation);
+void          egg_animation_add_property  (EggAnimation     *animation,
+                                           GParamSpec       *pspec,
+                                           const GValue     *value);
+
+EggAnimation *egg_object_animatev         (gpointer          object,
+                                           EggAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           const gchar      *first_property,
+                                           va_list           args);
+EggAnimation* egg_object_animate          (gpointer          object,
+                                           EggAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+EggAnimation* egg_object_animate_full     (gpointer          object,
+                                           EggAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           GDestroyNotify    notify,
+                                           gpointer          notify_data,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* EGG_ANIMATION_H */
diff --git a/src/animation/egg-frame-source.c b/src/animation/egg-frame-source.c
new file mode 100644
index 0000000..5672a05
--- /dev/null
+++ b/src/animation/egg-frame-source.c
@@ -0,0 +1,130 @@
+/* egg-frame-source.c
+ *
+ * Copyright (C) 2010-2016 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/>.
+ */
+
+#include "animation/egg-frame-source.h"
+
+typedef struct
+{
+   GSource parent;
+   guint   fps;
+   guint   frame_count;
+   gint64  start_time;
+} EggFrameSource;
+
+static gboolean
+egg_frame_source_prepare (GSource *source,
+                          gint    *timeout_)
+{
+   EggFrameSource *fsource = (EggFrameSource *)(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
+egg_frame_source_check (GSource *source)
+{
+   gint timeout_;
+   return egg_frame_source_prepare(source, &timeout_);
+}
+
+static gboolean
+egg_frame_source_dispatch (GSource     *source,
+                           GSourceFunc  source_func,
+                           gpointer     user_data)
+{
+   EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
+   gboolean ret;
+
+   if ((ret = source_func(user_data)))
+      fsource->frame_count++;
+   return ret;
+}
+
+static GSourceFuncs source_funcs = {
+   egg_frame_source_prepare,
+   egg_frame_source_check,
+   egg_frame_source_dispatch,
+};
+
+/**
+ * egg_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
+egg_frame_source_add (guint       frames_per_sec,
+                      GSourceFunc callback,
+                      gpointer    user_data)
+{
+   EggFrameSource *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(EggFrameSource));
+   fsource = (EggFrameSource *)(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, "EggFrameSource");
+
+   ret = g_source_attach(source, NULL);
+   g_source_unref(source);
+
+   return ret;
+}
diff --git a/src/animation/egg-frame-source.h b/src/animation/egg-frame-source.h
new file mode 100644
index 0000000..3ba234f
--- /dev/null
+++ b/src/animation/egg-frame-source.h
@@ -0,0 +1,32 @@
+/* egg-frame-source.h
+ *
+ * Copyright (C) 2010-2016 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 EGG_FRAME_SOURCE_H
+#define EGG_FRAME_SOURCE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint egg_frame_source_add (guint       frames_per_sec,
+                            GSourceFunc callback,
+                            gpointer    user_data);
+
+G_END_DECLS
+
+#endif /* EGG_FRAME_SOURCE_H */
diff --git a/src/animation/ide-box-theatric.c b/src/animation/ide-box-theatric.c
new file mode 100644
index 0000000..cad8d4a
--- /dev/null
+++ b/src/animation/ide-box-theatric.c
@@ -0,0 +1,420 @@
+/* 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 "ide-box-theatric"
+
+#include <animation/egg-animation.h>
+#include <glib/gi18n.h>
+
+#include "animation/ide-box-theatric.h"
+#include "animation/ide-cairo.h"
+
+struct _IdeBoxTheatric
+{
+  GObject          parent_instance;
+
+  GtkWidget       *target;
+  GtkWidget       *toplevel;
+
+  GIcon           *icon;
+  cairo_surface_t *icon_surface;
+  guint            icon_surface_size;
+
+  GdkRectangle     area;
+  GdkRectangle     last_area;
+  GdkRGBA          background_rgba;
+  gdouble          alpha;
+
+  guint            draw_handler;
+
+  guint            background_set : 1;
+  guint            pixbuf_failed : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_ALPHA,
+  PROP_BACKGROUND,
+  PROP_HEIGHT,
+  PROP_ICON,
+  PROP_TARGET,
+  PROP_WIDTH,
+  PROP_X,
+  PROP_Y,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (IdeBoxTheatric, ide_box_theatric, G_TYPE_OBJECT)
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+get_toplevel_rect (IdeBoxTheatric *theatric,
+                   GdkRectangle  *area)
+{
+  gtk_widget_translate_coordinates (theatric->target, theatric->toplevel,
+                                    theatric->area.x, theatric->area.y,
+                                    &area->x, &area->y);
+
+  area->width = theatric->area.width;
+  area->height = theatric->area.height;
+}
+
+static gboolean
+on_toplevel_draw (GtkWidget      *widget,
+                  cairo_t        *cr,
+                  IdeBoxTheatric *self)
+{
+  GdkRectangle area;
+
+  g_assert (IDE_IS_BOX_THEATRIC (self));
+
+  get_toplevel_rect (self, &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
+
+  if (self->background_set)
+    {
+      GdkRGBA bg;
+
+      bg = self->background_rgba;
+      bg.alpha = self->alpha;
+
+      ide_cairo_rounded_rectangle (cr, &area, 3, 3);
+      gdk_cairo_set_source_rgba (cr, &bg);
+      cairo_fill (cr);
+    }
+
+  /* Load an icon if necessary and cache the surface */
+  if (self->icon != NULL && self->icon_surface == NULL && !self->pixbuf_failed)
+    {
+      g_autoptr(GtkIconInfo) icon_info = NULL;
+      GtkIconTheme *icon_theme;
+      gint width;
+
+      width = area.width * 4;
+      icon_theme = gtk_icon_theme_get_default ();
+      icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
+                                                  self->icon,
+                                                  width,
+                                                  GTK_ICON_LOOKUP_FORCE_SIZE);
+
+      if (icon_info != NULL)
+        {
+          GdkWindow *window = gtk_widget_get_window (widget);
+          g_autoptr(GdkPixbuf) pixbuf = NULL;
+          GtkStyleContext *context;
+
+          context = gtk_widget_get_style_context (self->target);
+          pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);
+
+          if (pixbuf != NULL)
+            {
+              self->icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
+              self->icon_surface_size = width;
+              self->pixbuf_failed = FALSE;
+            }
+          else
+            {
+              self->pixbuf_failed = TRUE;
+            }
+        }
+    }
+
+  if (self->icon_surface != NULL)
+    {
+      cairo_translate (cr, area.x, area.y);
+      cairo_rectangle (cr, 0, 0, area.width, area.height);
+      cairo_scale (cr,
+                   area.width / (gdouble)self->icon_surface_size,
+                   area.height / (gdouble)self->icon_surface_size);
+      cairo_set_source_surface (cr, self->icon_surface, 0, 0);
+      cairo_paint_with_alpha (cr, self->alpha);
+    }
+
+  self->last_area = area;
+
+  return FALSE;
+}
+
+static void
+ide_box_theatric_notify (GObject    *object,
+                        GParamSpec *pspec)
+{
+  IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
+
+  if (G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify)
+    G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify (object, pspec);
+
+  if (self->target && self->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 (self->toplevel);
+
+      if (window != NULL)
+        {
+          gdk_window_invalidate_rect (window, &self->last_area, TRUE);
+          gdk_window_invalidate_rect (window, &area, TRUE);
+        }
+    }
+}
+
+static void
+ide_box_theatric_dispose (GObject *object)
+{
+  IdeBoxTheatric *self =  IDE_BOX_THEATRIC (object);
+
+  if (self->target)
+    {
+      if (self->draw_handler && self->toplevel)
+        {
+          g_signal_handler_disconnect (self->toplevel, self->draw_handler);
+          self->draw_handler = 0;
+        }
+      g_object_remove_weak_pointer (G_OBJECT (self->target),
+                                    (gpointer *) &self->target);
+      self->target = NULL;
+    }
+
+  g_clear_pointer (&self->icon_surface, cairo_surface_destroy);
+  g_clear_object (&self->icon);
+
+  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->alpha);
+      break;
+
+    case PROP_BACKGROUND:
+      g_value_take_string (value,
+                           gdk_rgba_to_string (&theatric->background_rgba));
+      break;
+
+    case PROP_HEIGHT:
+      g_value_set_int (value, theatric->area.height);
+      break;
+
+    case PROP_ICON:
+      g_value_set_object (value, theatric->icon);
+      break;
+
+    case PROP_TARGET:
+      g_value_set_object (value, theatric->target);
+      break;
+
+    case PROP_WIDTH:
+      g_value_set_int (value, theatric->area.width);
+      break;
+
+    case PROP_X:
+      g_value_set_int (value, theatric->area.x);
+      break;
+
+    case PROP_Y:
+      g_value_set_int (value, theatric->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->alpha = g_value_get_double (value);
+      break;
+
+    case PROP_BACKGROUND:
+      {
+        const gchar *str = g_value_get_string (value);
+
+        if (str == NULL)
+          {
+            gdk_rgba_parse (&theatric->background_rgba, "#000000");
+            theatric->background_rgba.alpha = 0;
+            theatric->background_set = FALSE;
+          }
+        else
+          {
+            gdk_rgba_parse (&theatric->background_rgba, str);
+            theatric->background_set = TRUE;
+          }
+      }
+      break;
+
+    case PROP_HEIGHT:
+      theatric->area.height = g_value_get_int (value);
+      break;
+
+    case PROP_ICON:
+      g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
+      g_clear_object (&theatric->icon);
+      theatric->icon = g_value_dup_object (value);
+      theatric->pixbuf_failed = FALSE;
+      break;
+
+    case PROP_TARGET:
+      theatric->target = g_value_get_object (value);
+      theatric->toplevel = gtk_widget_get_toplevel (theatric->target);
+      g_object_add_weak_pointer (G_OBJECT (theatric->target),
+                                 (gpointer *) &theatric->target);
+      theatric->draw_handler =
+        g_signal_connect_after (theatric->toplevel,
+                                "draw",
+                                G_CALLBACK (on_toplevel_draw),
+                                theatric);
+      break;
+
+    case PROP_WIDTH:
+      theatric->area.width = g_value_get_int (value);
+      break;
+
+    case PROP_X:
+      theatric->area.x = g_value_get_int (value);
+      break;
+
+    case PROP_Y:
+      theatric->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;
+
+  properties[PROP_ALPHA] =
+    g_param_spec_double ("alpha",
+                         "Alpha",
+                         "Alpha",
+                         0.0,
+                         1.0,
+                         1.0,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_BACKGROUND] =
+    g_param_spec_string ("background",
+                         "background",
+                         "background",
+                         "#000000",
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_HEIGHT] =
+    g_param_spec_int ("height",
+                      "height",
+                      "height",
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ICON] =
+    g_param_spec_object ("icon",
+                         "Icon",
+                         "The GIcon to render over the background",
+                         G_TYPE_ICON,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_TARGET] =
+    g_param_spec_object ("target",
+                         "Target",
+                         "Target",
+                         GTK_TYPE_WIDGET,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_WIDTH] =
+    g_param_spec_int ("width",
+                      "width",
+                      "width",
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_X] =
+    g_param_spec_int ("x",
+                      "x",
+                      "x",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties[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_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_box_theatric_init (IdeBoxTheatric *theatric)
+{
+}
diff --git a/src/animation/ide-box-theatric.h b/src/animation/ide-box-theatric.h
new file mode 100644
index 0000000..d2545f0
--- /dev/null
+++ b/src/animation/ide-box-theatric.h
@@ -0,0 +1,33 @@
+/* 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())
+
+G_DECLARE_FINAL_TYPE (IdeBoxTheatric, ide_box_theatric,
+                      IDE, BOX_THEATRIC, GObject)
+
+G_END_DECLS
+
+#endif /* IDE_BOX_THEATRIC_H */
diff --git a/src/animation/ide-cairo.c b/src/animation/ide-cairo.c
new file mode 100644
index 0000000..a4fba52
--- /dev/null
+++ b/src/animation/ide-cairo.c
@@ -0,0 +1,84 @@
+/* ide-cairo.c
+ *
+ * Copyright (C) 2014 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/>.
+ */
+
+#include "ide-cairo.h"
+
+cairo_region_t *
+ide_cairo_region_create_from_clip_extents (cairo_t *cr)
+{
+  cairo_rectangle_int_t crect;
+  GdkRectangle rect;
+
+  g_return_val_if_fail (cr, NULL);
+
+  gdk_cairo_get_clip_rectangle (cr, &rect);
+  crect.x = rect.x;
+  crect.y = rect.y;
+  crect.width = rect.width;
+  crect.height = rect.height;
+
+  return cairo_region_create_rectangle (&crect);
+}
+
+void
+ide_cairo_rounded_rectangle (cairo_t            *cr,
+                            const GdkRectangle *rect,
+                            gint                x_radius,
+                            gint                y_radius)
+{
+  gint x;
+  gint y;
+  gint width;
+  gint height;
+  gint x1, x2;
+  gint y1, y2;
+  gint xr1, xr2;
+  gint yr1, yr2;
+
+  g_return_if_fail (cr);
+  g_return_if_fail (rect);
+
+  x = rect->x;
+  y = rect->y;
+  width = rect->width;
+  height = rect->height;
+
+  x1 = x;
+  x2 = x1 + width;
+  y1 = y;
+  y2 = y1 + height;
+
+  x_radius = MIN (x_radius, width / 2.0);
+  y_radius = MIN (y_radius, width / 2.0);
+
+  xr1 = x_radius;
+  xr2 = x_radius / 2.0;
+  yr1 = y_radius;
+  yr2 = y_radius / 2.0;
+
+  cairo_move_to (cr, x1 + xr1, y1);
+  cairo_line_to (cr, x2 - xr1, y1);
+  cairo_curve_to (cr, x2 - xr2, y1, x2, y1 + yr2, x2, y1 + yr1);
+  cairo_line_to (cr, x2, y2 - yr1);
+  cairo_curve_to (cr, x2, y2 - yr2, x2 - xr2, y2, x2 - xr1, y2);
+  cairo_line_to (cr, x1 + xr1, y2);
+  cairo_curve_to (cr, x1 + xr2, y2, x1, y2 - yr2, x1, y2 - yr1);
+  cairo_line_to (cr, x1, y1 + yr1);
+  cairo_curve_to (cr, x1, y1 + yr2, x1 + xr2, y1, x1 + xr1, y1);
+  cairo_close_path (cr);
+}
diff --git a/src/animation/ide-cairo.h b/src/animation/ide-cairo.h
new file mode 100644
index 0000000..703821b
--- /dev/null
+++ b/src/animation/ide-cairo.h
@@ -0,0 +1,68 @@
+/* ide-cairo.h
+ *
+ * Copyright (C) 2014 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_CAIRO_H
+#define IDE_CAIRO_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+cairo_region_t *ide_cairo_region_create_from_clip_extents (cairo_t            *cr);
+void            ide_cairo_rounded_rectangle               (cairo_t            *cr,
+                                                           const GdkRectangle *rect,
+                                                           gint                x_radius,
+                                                           gint                y_radius);
+
+static inline gboolean
+_ide_cairo_rectangle_x2 (const cairo_rectangle_int_t *rect)
+{
+  return rect->x + rect->width;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_y2 (const cairo_rectangle_int_t *rect)
+{
+  return rect->y + rect->height;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_center (const cairo_rectangle_int_t *rect)
+{
+  return rect->x + (rect->width/2);
+}
+
+static inline gboolean
+_ide_cairo_rectangle_middle (const cairo_rectangle_int_t *rect)
+{
+  return rect->y + (rect->height/2);
+}
+
+static inline cairo_bool_t
+_ide_cairo_rectangle_contains_rectangle (const cairo_rectangle_int_t *a,
+                                         const cairo_rectangle_int_t *b)
+{
+    return (a->x <= b->x &&
+            a->x + (int) a->width >= b->x + (int) b->width &&
+            a->y <= b->y &&
+            a->y + (int) a->height >= b->y + (int) b->height);
+}
+
+G_END_DECLS
+
+#endif /* IDE_CAIRO_H */
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
index 432ce1e..e78dffe 100644
--- a/src/nautilus-toolbar.c
+++ b/src/nautilus-toolbar.c
@@ -37,6 +37,9 @@
 #include "nautilus-file-undo-manager.h"
 #include "nautilus-toolbar-menu-sections.h"
 
+#include "animation/ide-box-theatric.h"
+#include "animation/egg-animation.h"
+
 #include <glib/gi18n.h>
 #include <math.h>
 
@@ -44,6 +47,9 @@
 #define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */
 #define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */
 
+#define ANIMATION_X_GROW 30
+#define ANIMATION_Y_GROW 30
+
 typedef enum
 {
     NAUTILUS_NAVIGATION_DIRECTION_NONE,
@@ -574,8 +580,32 @@ update_operations (NautilusToolbar *self)
          * property set. */
         if (gtk_widget_is_visible (GTK_WIDGET (self)))
         {
-            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->operations_button),
-                                          TRUE);
+            GtkAllocation rect;
+            IdeBoxTheatric *theatric;
+
+            gtk_widget_get_allocation (GTK_WIDGET (self->operations_button), &rect);
+            theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
+                                     "alpha", 0.9,
+                                     "background", "#fdfdfd",
+                                     "target", self->operations_button,
+                                     "height", rect.height,
+                                     "width", rect.width,
+                                     "x", rect.x,
+                                     "y", rect.y,
+                                     NULL);
+
+            egg_object_animate_full (theatric,
+                                     EGG_ANIMATION_EASE_IN_CUBIC,
+                                     250,
+                                     gtk_widget_get_frame_clock (GTK_WIDGET (self->operations_button)),
+                                     g_object_unref,
+                                     theatric,
+                                     "x", rect.x - ANIMATION_X_GROW,
+                                     "width", rect.width + (ANIMATION_X_GROW * 2),
+                                     "y", rect.y - ANIMATION_Y_GROW,
+                                     "height", rect.height + (ANIMATION_Y_GROW * 2),
+                                     "alpha", 0.0,
+                                     NULL);
         }
     }
 


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