[nautilus/wip/antoniof/gnome-42: 58/72] Revert "toolbar: Drop theatrics animation on the operations button"




commit d0ad71d65f3d751afdfc85dfda846c5103169306
Author: Ondrej Holy <oholy redhat com>
Date:   Fri Feb 11 14:28:04 2022 +0100

    Revert "toolbar: Drop theatrics animation on the operations button"
    
    This reverts commit 266f8913a0b7e099829524df747a2362a70d34f6.

 src/animation/egg-animation.c    | 1183 ++++++++++++++++++++++++++++++++++++++
 src/animation/egg-animation.h    |   74 +++
 src/animation/egg-frame-source.c |  130 +++++
 src/animation/egg-frame-source.h |   29 +
 src/animation/ide-box-theatric.c |  417 ++++++++++++++
 src/animation/ide-box-theatric.h |   30 +
 src/animation/ide-cairo.c        |   84 +++
 src/animation/ide-cairo.h        |   65 +++
 src/meson.build                  |    8 +
 src/nautilus-toolbar.c           |   40 ++
 10 files changed, 2060 insertions(+)
---
diff --git a/src/animation/egg-animation.c b/src/animation/egg-animation.c
new file mode 100644
index 000000000..7ceba88c4
--- /dev/null
+++ b/src/animation/egg-animation.c
@@ -0,0 +1,1183 @@
+/* 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_clear_signal_handler (&animation->tween_handler, animation->frame_clock);
+          g_clear_signal_handler (&animation->after_paint_handler, animation->frame_clock);
+        }
+      else
+        {
+          g_source_remove (animation->tween_handler);
+          animation->tween_handler = 0;
+        }
+      egg_animation_unload_begin_values (animation);
+      egg_animation_notify (animation);
+      g_object_unref (animation);
+    }
+}
+
+
+/**
+ * egg_animation_add_property:
+ * @animation: (in): A #EggAnimation.
+ * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
+ * @value: (in): The new value for the property at the end of the animation.
+ *
+ * Adds a new property to the set of properties to be animated during the
+ * lifetime of the animation.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_add_property (EggAnimation *animation,
+                            GParamSpec   *pspec,
+                            const GValue *value)
+{
+  Tween tween = { 0 };
+  GType type;
+
+  g_return_if_fail (EGG_IS_ANIMATION (animation));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (value != NULL);
+  g_return_if_fail (value->g_type);
+  g_return_if_fail (animation->target);
+  g_return_if_fail (!animation->tween_handler);
+
+  type = G_TYPE_FROM_INSTANCE (animation->target);
+  tween.is_child = !g_type_is_a (type, pspec->owner_type);
+  if (tween.is_child)
+    {
+      if (!GTK_IS_WIDGET (animation->target))
+        {
+          g_critical (_("Cannot locate property %s in class %s"),
+                      pspec->name, g_type_name (type));
+          return;
+        }
+    }
+
+  tween.pspec = g_param_spec_ref (pspec);
+  g_value_init (&tween.begin, pspec->value_type);
+  g_value_init (&tween.end, pspec->value_type);
+  g_value_copy (value, &tween.end);
+  g_array_append_val (animation->tweens, tween);
+}
+
+
+/**
+ * egg_animation_dispose:
+ * @object: (in): A #EggAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_dispose (GObject *object)
+{
+  EggAnimation *self = EGG_ANIMATION (object);
+
+  g_clear_object (&self->target);
+  g_clear_object (&self->frame_clock);
+
+  G_OBJECT_CLASS (egg_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * egg_animation_finalize:
+ * @object: (in): A #EggAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_finalize (GObject *object)
+{
+  EggAnimation *self = EGG_ANIMATION (object);
+  Tween *tween;
+  guint i;
+
+  for (i = 0; i < self->tweens->len; i++)
+    {
+      tween = &g_array_index (self->tweens, Tween, i);
+      g_value_unset (&tween->begin);
+      g_value_unset (&tween->end);
+      g_param_spec_unref (tween->pspec);
+    }
+
+  g_array_unref (self->tweens);
+
+  G_OBJECT_CLASS (egg_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * egg_animation_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+egg_animation_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  EggAnimation *animation = EGG_ANIMATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DURATION:
+      animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
+      break;
+
+    case PROP_FRAME_CLOCK:
+      egg_animation_set_frame_clock (animation, g_value_get_object (value));
+      break;
+
+    case PROP_MODE:
+      animation->mode = g_value_get_enum (value);
+      break;
+
+    case PROP_TARGET:
+      egg_animation_set_target (animation, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+
+/**
+ * egg_animation_class_init:
+ * @klass: (in): A #EggAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+egg_animation_class_init (EggAnimationClass *klass)
+{
+  GObjectClass *object_class;
+  const gchar *slow_down_factor_env;
+
+  debug = !!g_getenv ("EGG_ANIMATION_DEBUG");
+  slow_down_factor_env = g_getenv ("EGG_ANIMATION_SLOW_DOWN_FACTOR");
+
+  if (slow_down_factor_env)
+    slow_down_factor = MAX (1, atoi (slow_down_factor_env));
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->dispose = egg_animation_dispose;
+  object_class->finalize = egg_animation_finalize;
+  object_class->set_property = egg_animation_set_property;
+
+  /**
+   * EggAnimation:duration:
+   *
+   * The "duration" property is the total number of milliseconds that the
+   * animation should run before being completed.
+   */
+  properties[PROP_DURATION] =
+    g_param_spec_uint ("duration",
+                       "Duration",
+                       "The duration of the animation",
+                       0,
+                       G_MAXUINT,
+                       250,
+                       (G_PARAM_WRITABLE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_FRAME_CLOCK] =
+    g_param_spec_object ("frame-clock",
+                         "Frame Clock",
+                         "An optional frame-clock to synchronize with.",
+                         GDK_TYPE_FRAME_CLOCK,
+                         (G_PARAM_WRITABLE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  /**
+   * EggAnimation:mode:
+   *
+   * The "mode" property is the Alpha function that should be used to
+   * determine the offset within the animation based on the current
+   * offset in the animations duration.
+   */
+  properties[PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       "Mode",
+                       "The animation mode",
+                       EGG_TYPE_ANIMATION_MODE,
+                       EGG_ANIMATION_LINEAR,
+                       (G_PARAM_WRITABLE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS));
+
+  /**
+   * EggAnimation:target:
+   *
+   * The "target" property is the #GObject that should have its properties
+   * animated.
+   */
+  properties[PROP_TARGET] =
+    g_param_spec_object ("target",
+                         "Target",
+                         "The target of the animation",
+                         G_TYPE_OBJECT,
+                         (G_PARAM_WRITABLE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  /**
+   * EggAnimation::tick:
+   *
+   * The "tick" signal is emitted on each frame in the animation.
+   */
+  signals[TICK] = g_signal_new ("tick",
+                                 EGG_TYPE_ANIMATION,
+                                 G_SIGNAL_RUN_FIRST,
+                                 0,
+                                 NULL, NULL, NULL,
+                                 G_TYPE_NONE,
+                                 0);
+
+#define SET_ALPHA(_T, _t) \
+  alpha_funcs[EGG_ANIMATION_ ## _T] = egg_animation_alpha_ ## _t
+
+  SET_ALPHA (LINEAR, linear);
+  SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
+  SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
+  SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
+  SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
+  SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
+  SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
+
+#define SET_TWEEN(_T, _t) \
+  G_STMT_START { \
+    guint idx = G_TYPE_ ## _T; \
+    tween_funcs[idx] = tween_ ## _t; \
+  } G_STMT_END
+
+  SET_TWEEN (INT, int);
+  SET_TWEEN (UINT, uint);
+  SET_TWEEN (LONG, long);
+  SET_TWEEN (ULONG, ulong);
+  SET_TWEEN (FLOAT, float);
+  SET_TWEEN (DOUBLE, double);
+}
+
+
+/**
+ * egg_animation_init:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Initializes the #EggAnimation instance.
+ *
+ * Side effects: Everything.
+ */
+static void
+egg_animation_init (EggAnimation *animation)
+{
+  animation->duration_msec = 250;
+  animation->mode = EGG_ANIMATION_EASE_IN_OUT_QUAD;
+  animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+  animation->last_offset = -G_MINDOUBLE;
+}
+
+
+/**
+ * egg_animation_mode_get_type:
+ *
+ * Retrieves the GType for #EggAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+egg_animation_mode_get_type (void)
+{
+  static GType type_id = 0;
+  static const GEnumValue values[] = {
+    { EGG_ANIMATION_LINEAR, "EGG_ANIMATION_LINEAR", "linear" },
+    { EGG_ANIMATION_EASE_IN_QUAD, "EGG_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
+    { EGG_ANIMATION_EASE_IN_OUT_QUAD, "EGG_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
+    { EGG_ANIMATION_EASE_OUT_QUAD, "EGG_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
+    { EGG_ANIMATION_EASE_IN_CUBIC, "EGG_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
+    { EGG_ANIMATION_EASE_OUT_CUBIC, "EGG_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
+    { EGG_ANIMATION_EASE_IN_OUT_CUBIC, "EGG_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
+    { 0 }
+  };
+
+  if (G_UNLIKELY (!type_id))
+    type_id = g_enum_register_static ("EggAnimationMode", values);
+  return type_id;
+}
+
+/**
+ * egg_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_clock: (nullable): The #GdkFrameClock to synchronize to.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #EggAnimation.
+ */
+EggAnimation *
+egg_object_animatev (gpointer          object,
+                     EggAnimationMode  mode,
+                     guint             duration_msec,
+                     GdkFrameClock    *frame_clock,
+                     const gchar      *first_property,
+                     va_list           args)
+{
+  EggAnimation *animation;
+  GObjectClass *klass;
+  GObjectClass *pklass;
+  const gchar *name;
+  GParamSpec *pspec;
+  GtkWidget *parent;
+  GValue value = { 0 };
+  gchar *error = NULL;
+  GType type;
+  GType ptype;
+  gboolean enable_animations;
+
+  g_return_val_if_fail (first_property != NULL, NULL);
+  g_return_val_if_fail (mode < EGG_ANIMATION_LAST, NULL);
+
+  if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
+    frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+
+  /*
+   * If we have a frame clock, then we must be in the gtk thread and we
+   * should check GtkSettings for disabled animations. If we are disabled,
+   * we will just make the timeout immediate.
+   */
+  if (frame_clock != NULL)
+    {
+      g_object_get (gtk_settings_get_default (),
+                    "gtk-enable-animations", &enable_animations,
+                    NULL);
+
+      if (enable_animations == FALSE)
+        duration_msec = 0;
+    }
+
+  name = first_property;
+  type = G_TYPE_FROM_INSTANCE (object);
+  klass = G_OBJECT_GET_CLASS (object);
+  animation = g_object_new (EGG_TYPE_ANIMATION,
+                            "duration", duration_msec,
+                            "frame-clock", frame_clock,
+                            "mode", mode,
+                            "target", object,
+                            NULL);
+
+  do
+    {
+      /*
+       * First check for the property on the object. If that does not exist
+       * then check if the object has a parent and look at its child
+       * properties (if it's a GtkWidget).
+       */
+      if (!(pspec = g_object_class_find_property (klass, name)))
+        {
+          if (!g_type_is_a (type, GTK_TYPE_WIDGET))
+            {
+              g_critical (_("Failed to find property %s in %s"),
+                          name, g_type_name (type));
+              goto failure;
+            }
+          if (!(parent = gtk_widget_get_parent (object)))
+            {
+              g_critical (_("Failed to find property %s in %s"),
+                          name, g_type_name (type));
+              goto failure;
+            }
+          pklass = G_OBJECT_GET_CLASS (parent);
+          ptype = G_TYPE_FROM_INSTANCE (parent);
+          if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
+            {
+              g_critical (_("Failed to find property %s in %s or parent %s"),
+                          name, g_type_name (type), g_type_name (ptype));
+              goto failure;
+            }
+        }
+
+      g_value_init (&value, pspec->value_type);
+      G_VALUE_COLLECT (&value, args, 0, &error);
+      if (error != NULL)
+        {
+          g_critical (_("Failed to retrieve va_list value: %s"), error);
+          g_free (error);
+          goto failure;
+        }
+
+      egg_animation_add_property (animation, pspec, &value);
+      g_value_unset (&value);
+    }
+  while ((name = va_arg (args, const gchar *)));
+
+  egg_animation_start (animation);
+
+  return animation;
+
+failure:
+  g_object_ref_sink (animation);
+  g_object_unref (animation);
+  return NULL;
+}
+
+/**
+ * egg_object_animate:
+ * @object: (in): A #GObject.
+ * @mode: (in): The animation mode.
+ * @duration_msec: (in): The duration in milliseconds.
+ * @first_property: (in): The first property to animate.
+ *
+ * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
+ * will be animated from their current value to the target value over the time period.
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ * Side effects: None.
+ */
+EggAnimation*
+egg_object_animate (gpointer        object,
+                    EggAnimationMode mode,
+                    guint           duration_msec,
+                    GdkFrameClock  *frame_clock,
+                    const gchar    *first_property,
+                    ...)
+{
+  EggAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = egg_object_animatev (object,
+                                   mode,
+                                   duration_msec,
+                                   frame_clock,
+                                   first_property,
+                                   args);
+  va_end (args);
+
+  return animation;
+}
+
+/**
+ * egg_object_animate_full:
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ */
+EggAnimation*
+egg_object_animate_full (gpointer        object,
+                         EggAnimationMode mode,
+                         guint           duration_msec,
+                         GdkFrameClock  *frame_clock,
+                         GDestroyNotify  notify,
+                         gpointer        notify_data,
+                         const gchar    *first_property,
+                         ...)
+{
+  EggAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = egg_object_animatev (object,
+                                   mode,
+                                   duration_msec,
+                                   frame_clock,
+                                   first_property,
+                                   args);
+  va_end (args);
+
+  animation->notify = notify;
+  animation->notify_data = notify_data;
+
+  return animation;
+}
diff --git a/src/animation/egg-animation.h b/src/animation/egg-animation.h
new file mode 100644
index 000000000..45b4f3960
--- /dev/null
+++ b/src/animation/egg-animation.h
@@ -0,0 +1,74 @@
+/* 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/>.
+ */
+
+#pragma once
+
+#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
\ No newline at end of file
diff --git a/src/animation/egg-frame-source.c b/src/animation/egg-frame-source.c
new file mode 100644
index 000000000..5672a05e0
--- /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 000000000..376d54534
--- /dev/null
+++ b/src/animation/egg-frame-source.h
@@ -0,0 +1,29 @@
+/* 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint egg_frame_source_add (guint       frames_per_sec,
+                            GSourceFunc callback,
+                            gpointer    user_data);
+
+G_END_DECLS
\ No newline at end of file
diff --git a/src/animation/ide-box-theatric.c b/src/animation/ide-box-theatric.c
new file mode 100644
index 000000000..1e882eae8
--- /dev/null
+++ b/src/animation/ide-box-theatric.c
@@ -0,0 +1,417 @@
+/* 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;
+
+  gulong           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->toplevel)
+        g_clear_signal_handler (&self->draw_handler, self->toplevel);
+      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 000000000..9eb81c131
--- /dev/null
+++ b/src/animation/ide-box-theatric.h
@@ -0,0 +1,30 @@
+/* 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/>.
+ */
+
+#pragma once
+
+#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
\ No newline at end of file
diff --git a/src/animation/ide-cairo.c b/src/animation/ide-cairo.c
new file mode 100644
index 000000000..a4fba52a9
--- /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 000000000..5d3bb2943
--- /dev/null
+++ b/src/animation/ide-cairo.h
@@ -0,0 +1,65 @@
+/* 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/>.
+ */
+
+#pragma once
+
+#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
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 4a11e4ed8..a8c650e97 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -54,6 +54,14 @@ libnautilus_sources = [
     interface_prefix: 'org.gnome',
     namespace: 'Nautilus'
   ),
+  '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/nautilusgtkbookmarksmanager.c',
   'gtk/nautilusgtkbookmarksmanager.h',
   'gtk/nautilusgtkplacessidebar.c',
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
index 4cfa50c56..5fe4e63a4 100644
--- a/src/nautilus-toolbar.c
+++ b/src/nautilus-toolbar.c
@@ -25,6 +25,9 @@
 #include <glib/gi18n.h>
 #include <math.h>
 
+#include "animation/ide-box-theatric.h"
+#include "animation/egg-animation.h"
+
 #include "nautilus-application.h"
 #include "nautilus-bookmark.h"
 #include "nautilus-file-operations.h"
@@ -43,6 +46,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
+
 /* Just design, context at https://gitlab.gnome.org/GNOME/nautilus/issues/548#note_274131 */
 
 typedef enum
@@ -496,6 +502,40 @@ update_operations (NautilusToolbar *self)
         gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer),
                                        TRUE);
         gtk_widget_queue_draw (self->operations_icon);
+
+        /* Show the popover at start to increase visibility.
+         * Check whether the toolbar is visible or not before showing the
+         * popover. This can happens if the window has the disables-chrome
+         * property set. */
+        if (gtk_widget_is_visible (GTK_WIDGET (self)))
+        {
+            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);
+        }
     }
 
     /* Since we removed the info widgets, we need to restore the focus */


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