summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndrej Holy <oholy@redhat.com>2022-02-11 14:28:04 +0100
committerOndrej Holy <oholy@redhat.com>2022-02-11 14:59:48 +0100
commit8583bc4aeff42cd751b4a5597b1ae3dd730b05ac (patch)
treed1e6ac86cdf0167d5b9a9f22e807c56b66ec87a3
parentc70fd032f18b0921798ee8eb9069d308cbde0f9b (diff)
downloadnautilus-8583bc4aeff42cd751b4a5597b1ae3dd730b05ac.tar.gz
Revert "toolbar: Drop theatrics animation on the operations button"
This reverts commit 266f8913a0b7e099829524df747a2362a70d34f6.
-rw-r--r--src/animation/egg-animation.c1183
-rw-r--r--src/animation/egg-animation.h74
-rw-r--r--src/animation/egg-frame-source.c130
-rw-r--r--src/animation/egg-frame-source.h29
-rw-r--r--src/animation/ide-box-theatric.c417
-rw-r--r--src/animation/ide-box-theatric.h30
-rw-r--r--src/animation/ide-cairo.c84
-rw-r--r--src/animation/ide-cairo.h65
-rw-r--r--src/meson.build8
-rw-r--r--src/nautilus-toolbar.c40
10 files changed, 2060 insertions, 0 deletions
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 c6f03517c..ed09db3fd 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 */