summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Soriano <csoriano@gnome.org>2017-02-13 23:03:12 +0100
committerCarlos Soriano <csoriano@gnome.org>2017-02-13 23:15:02 +0100
commit7f8cc0b8892105b24f944ac24248ba3b5f37ecbf (patch)
tree493ede2ced7db065bf4ee233725ce1398401ff05
parentff83776ed08fdb08e84beacfd0b5873b55311005 (diff)
downloadnautilus-7f8cc0b8892105b24f944ac24248ba3b5f37ecbf.tar.gz
toolbar: add theatrics animation to the operations button
We have been going back and forth of a solution to make the operations button noticeable enough once it appears. First we implemented a highlight inside the button. But that wasn't enough, so we had to show the operations popover at the start. That is not really good because it appears in every window, and the user has to explicitly close it, which is annoying. This patch implements another animation highlight, which is more visually bold to try to catch the attention, in order to not need to show the operations popover. https://bugzilla.gnome.org/show_bug.cgi?id=753728
-rw-r--r--src/Makefile.am8
-rw-r--r--src/animation/egg-animation.c1184
-rw-r--r--src/animation/egg-animation.h77
-rw-r--r--src/animation/egg-frame-source.c130
-rw-r--r--src/animation/egg-frame-source.h32
-rw-r--r--src/animation/ide-box-theatric.c420
-rw-r--r--src/animation/ide-box-theatric.h33
-rw-r--r--src/animation/ide-cairo.c84
-rw-r--r--src/animation/ide-cairo.h68
-rw-r--r--src/nautilus-toolbar.c34
10 files changed, 2068 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 562af23f8..db0a8027d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -138,6 +138,14 @@ nautilus_built_sources = \
$(NULL)
nautilus_no_main_sources = \
+ animation/egg-animation.c \
+ animation/egg-animation.h \
+ animation/egg-frame-source.c \
+ animation/egg-frame-source.h \
+ animation/ide-box-theatric.c \
+ animation/ide-box-theatric.h \
+ animation/ide-cairo.c \
+ animation/ide-cairo.h \
gtk/nautilusgtkplacesview.c \
gtk/nautilusgtkplacesviewprivate.h \
gtk/nautilusgtkplacesviewrow.c \
diff --git a/src/animation/egg-animation.c b/src/animation/egg-animation.c
new file mode 100644
index 000000000..c8b025874
--- /dev/null
+++ b/src/animation/egg-animation.c
@@ -0,0 +1,1184 @@
+/* egg-animation.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "animation/egg-animation.h"
+#include "animation/egg-frame-source.h"
+
+#define FALLBACK_FRAME_RATE 60
+
+typedef gdouble (*AlphaFunc) (gdouble offset);
+typedef void (*TweenFunc) (const GValue *begin,
+ const GValue *end,
+ GValue *value,
+ gdouble offset);
+
+typedef struct
+{
+ gboolean is_child; /* Does GParamSpec belong to parent widget */
+ GParamSpec *pspec; /* GParamSpec of target property */
+ GValue begin; /* Begin value in animation */
+ GValue end; /* End value in animation */
+} Tween;
+
+
+struct _EggAnimation
+{
+ GInitiallyUnowned parent_instance;
+
+ gpointer target; /* Target object to animate */
+ guint64 begin_msec; /* Time in which animation started */
+ guint duration_msec; /* Duration of animation */
+ guint mode; /* Tween mode */
+ gulong tween_handler; /* GSource or signal handler */
+ gulong after_paint_handler; /* signal handler */
+ gdouble last_offset; /* Track our last offset */
+ GArray *tweens; /* Array of tweens to perform */
+ GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */
+ GDestroyNotify notify; /* Notify callback */
+ gpointer notify_data; /* Data for notify */
+};
+
+G_DEFINE_TYPE (EggAnimation, egg_animation, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+ PROP_0,
+ PROP_DURATION,
+ PROP_FRAME_CLOCK,
+ PROP_MODE,
+ PROP_TARGET,
+ LAST_PROP
+};
+
+
+enum {
+ TICK,
+ LAST_SIGNAL
+};
+
+
+/*
+ * Helper macros.
+ */
+#define LAST_FUNDAMENTAL 64
+#define TWEEN(type) \
+ static void \
+ tween_ ## type (const GValue * begin, \
+ const GValue * end, \
+ GValue * value, \
+ gdouble offset) \
+ { \
+ g ## type x = g_value_get_ ## type (begin); \
+ g ## type y = g_value_get_ ## type (end); \
+ g_value_set_ ## type (value, x + ((y - x) * offset)); \
+ }
+
+
+/*
+ * Globals.
+ */
+static AlphaFunc alpha_funcs[EGG_ANIMATION_LAST];
+static gboolean debug;
+static GParamSpec *properties[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+static TweenFunc tween_funcs[LAST_FUNDAMENTAL];
+static guint slow_down_factor = 1;
+
+
+/*
+ * Tweeners for basic types.
+ */
+TWEEN (int);
+TWEEN (uint);
+TWEEN (long);
+TWEEN (ulong);
+TWEEN (float);
+TWEEN (double);
+
+
+/**
+ * egg_animation_alpha_ease_in_cubic:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_CUBIC means the valu ewill be transformed into
+ * cubic acceleration (x * x * x).
+ */
+static gdouble
+egg_animation_alpha_ease_in_cubic (gdouble offset)
+{
+ return offset * offset * offset;
+}
+
+
+static gdouble
+egg_animation_alpha_ease_out_cubic (gdouble offset)
+{
+ gdouble p = offset - 1.0;
+
+ return p * p * p + 1.0;
+}
+
+static gdouble
+egg_animation_alpha_ease_in_out_cubic (gdouble offset)
+{
+ if (offset < .5)
+ return egg_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0;
+ else
+ return .5 + egg_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0;
+}
+
+
+/**
+ * egg_animation_alpha_linear:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_LINEAR means no tranformation will be made.
+ *
+ * Returns: @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_linear (gdouble offset)
+{
+ return offset;
+}
+
+
+/**
+ * egg_animation_alpha_ease_in_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_IN_QUAD means that the value will be transformed
+ * into a quadratic acceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_in_quad (gdouble offset)
+{
+ return offset * offset;
+}
+
+
+/**
+ * egg_animation_alpha_ease_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
+ * into a quadratic deceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_out_quad (gdouble offset)
+{
+ return -1.0 * offset * (offset - 2.0);
+}
+
+
+/**
+ * egg_animation_alpha_ease_in_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
+ * into a quadratic acceleration for the first half, and quadratic
+ * deceleration the second half.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_in_out_quad (gdouble offset)
+{
+ offset *= 2.0;
+ if (offset < 1.0)
+ return 0.5 * offset * offset;
+ offset -= 1.0;
+ return -0.5 * (offset * (offset - 2.0) - 1.0);
+}
+
+
+/**
+ * egg_animation_load_begin_values:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Load the begin values for all the properties we are about to
+ * animate.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_load_begin_values (EggAnimation *animation)
+{
+ GtkContainer *container;
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ if (tween->is_child)
+ {
+ container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
+ gtk_container_child_get_property (container,
+ animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ else
+ {
+ g_object_get_property (animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ }
+}
+
+
+/**
+ * egg_animation_unload_begin_values:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Unloads the begin values for the animation. This might be particularly
+ * useful once we support pointer types.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_unload_begin_values (EggAnimation *animation)
+{
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ }
+}
+
+
+/**
+ * egg_animation_get_offset:
+ * @animation: A #EggAnimation.
+ * @frame_time: the time to present the frame, or 0 for current timing.
+ *
+ * Retrieves the position within the animation from 0.0 to 1.0. This
+ * value is calculated using the msec of the beginning of the animation
+ * and the current time.
+ *
+ * Returns: The offset of the animation from 0.0 to 1.0.
+ */
+static gdouble
+egg_animation_get_offset (EggAnimation *animation,
+ gint64 frame_time)
+{
+ gdouble offset;
+ gint64 frame_msec;
+
+ g_return_val_if_fail (EGG_IS_ANIMATION (animation), 0.0);
+
+ if (frame_time == 0)
+ {
+ if (animation->frame_clock != NULL)
+ frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
+ else
+ frame_time = g_get_monotonic_time ();
+ }
+
+ frame_msec = frame_time / 1000L;
+
+ offset = (gdouble) (frame_msec - animation->begin_msec) /
+ (gdouble) MAX (animation->duration_msec, 1);
+
+ return CLAMP (offset, 0.0, 1.0);
+}
+
+
+/**
+ * egg_animation_update_property:
+ * @animation: (in): A #EggAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): a #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of a property on an object using @value.
+ *
+ * Side effects: The property of @target is updated.
+ */
+static void
+egg_animation_update_property (EggAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ g_assert (EGG_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ g_object_set_property (target, tween->pspec->name, value);
+}
+
+
+/**
+ * egg_animation_update_child_property:
+ * @animation: (in): A #EggAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of the parent widget of the target to @value.
+ *
+ * Side effects: The property of @target<!-- -->'s parent widget is updated.
+ */
+static void
+egg_animation_update_child_property (EggAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ GtkWidget *parent;
+
+ g_assert (EGG_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (target));
+ gtk_container_child_set_property (GTK_CONTAINER (parent),
+ target,
+ tween->pspec->name,
+ value);
+}
+
+
+/**
+ * egg_animation_get_value_at_offset:
+ * @animation: (in): A #EggAnimation.
+ * @offset: (in): The offset in the animation from 0.0 to 1.0.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (out): A #GValue in which to store the property.
+ *
+ * Retrieves a value for a particular position within the animation.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_get_value_at_offset (EggAnimation *animation,
+ gdouble offset,
+ Tween *tween,
+ GValue *value)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (tween != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type == tween->pspec->value_type);
+
+ if (value->g_type < LAST_FUNDAMENTAL)
+ {
+ /*
+ * If you hit the following assertion, you need to add a function
+ * to create the new value at the given offset.
+ */
+ g_assert (tween_funcs[value->g_type]);
+ tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
+ }
+ else
+ {
+ /*
+ * TODO: Support complex transitions.
+ */
+ if (offset >= 1.0)
+ g_value_copy (&tween->end, value);
+ }
+}
+
+static void
+egg_animation_set_frame_clock (EggAnimation *animation,
+ GdkFrameClock *frame_clock)
+{
+ if (animation->frame_clock != frame_clock)
+ {
+ g_clear_object (&animation->frame_clock);
+ animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
+ }
+}
+
+static void
+egg_animation_set_target (EggAnimation *animation,
+ gpointer target)
+{
+ g_assert (!animation->target);
+
+ animation->target = g_object_ref (target);
+
+ if (GTK_IS_WIDGET (animation->target))
+ egg_animation_set_frame_clock (animation,
+ gtk_widget_get_frame_clock (animation->target));
+}
+
+
+/**
+ * egg_animation_tick:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Moves the object properties to the next position in the animation.
+ *
+ * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+egg_animation_tick (EggAnimation *animation,
+ gdouble offset)
+{
+ gdouble alpha;
+ GValue value = { 0 };
+ Tween *tween;
+ guint i;
+
+ g_return_val_if_fail (EGG_IS_ANIMATION (animation), FALSE);
+
+ if (offset == animation->last_offset)
+ return offset < 1.0;
+
+ alpha = alpha_funcs[animation->mode](offset);
+
+ /*
+ * Update property values.
+ */
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_init (&value, tween->pspec->value_type);
+ egg_animation_get_value_at_offset (animation, alpha, tween, &value);
+ if (!tween->is_child)
+ {
+ egg_animation_update_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ else
+ {
+ egg_animation_update_child_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ g_value_unset (&value);
+ }
+
+ /*
+ * Notify anyone interested in the tick signal.
+ */
+ g_signal_emit (animation, signals[TICK], 0);
+
+ /*
+ * Flush any outstanding events to the graphics server (in the case of X).
+ */
+#if !GTK_CHECK_VERSION (3, 13, 0)
+ if (GTK_IS_WIDGET (animation->target))
+ {
+ GdkWindow *window;
+
+ if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
+ gdk_window_flush (window);
+ }
+#endif
+
+ animation->last_offset = offset;
+
+ return offset < 1.0;
+}
+
+
+/**
+ * egg_animation_timeout_cb:
+ * @user_data: (in): A #EggAnimation.
+ *
+ * Timeout from the main loop to move to the next step of the animation.
+ *
+ * Returns: %TRUE until the animation has completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+egg_animation_timeout_cb (gpointer user_data)
+{
+ EggAnimation *animation = user_data;
+ gboolean ret;
+ gdouble offset;
+
+ offset = egg_animation_get_offset (animation, 0);
+
+ if (!(ret = egg_animation_tick (animation, offset)))
+ egg_animation_stop (animation);
+
+ return ret;
+}
+
+
+static gboolean
+egg_animation_widget_tick_cb (GdkFrameClock *frame_clock,
+ EggAnimation *animation)
+{
+ gboolean ret = G_SOURCE_REMOVE;
+
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+ g_assert (EGG_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ gdouble offset;
+
+ offset = egg_animation_get_offset (animation, 0);
+
+ if (!(ret = egg_animation_tick (animation, offset)))
+ egg_animation_stop (animation);
+ }
+
+ return ret;
+}
+
+
+static void
+egg_animation_widget_after_paint_cb (GdkFrameClock *frame_clock,
+ EggAnimation *animation)
+{
+ gint64 base_time;
+ gint64 interval;
+ gint64 next_frame_time;
+ gdouble offset;
+
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+ g_assert (EGG_IS_ANIMATION (animation));
+
+ base_time = gdk_frame_clock_get_frame_time (frame_clock);
+ gdk_frame_clock_get_refresh_info (frame_clock, base_time, &interval, &next_frame_time);
+
+ offset = egg_animation_get_offset (animation, next_frame_time);
+
+ egg_animation_tick (animation, offset);
+}
+
+
+/**
+ * egg_animation_start:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Start the animation. When the animation stops, the internal reference will
+ * be dropped and the animation may be finalized.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_start (EggAnimation *animation)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (!animation->tween_handler);
+
+ g_object_ref_sink (animation);
+ egg_animation_load_begin_values (animation);
+
+ if (animation->frame_clock)
+ {
+ animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL;
+ animation->tween_handler =
+ g_signal_connect (animation->frame_clock,
+ "update",
+ G_CALLBACK (egg_animation_widget_tick_cb),
+ animation);
+ animation->after_paint_handler =
+ g_signal_connect (animation->frame_clock,
+ "after-paint",
+ G_CALLBACK (egg_animation_widget_after_paint_cb),
+ animation);
+ gdk_frame_clock_begin_updating (animation->frame_clock);
+ }
+ else
+ {
+ animation->begin_msec = g_get_monotonic_time () / 1000UL;
+ animation->tween_handler = egg_frame_source_add (FALLBACK_FRAME_RATE,
+ egg_animation_timeout_cb,
+ animation);
+ }
+}
+
+
+static void
+egg_animation_notify (EggAnimation *self)
+{
+ g_assert (EGG_IS_ANIMATION (self));
+
+ if (self->notify != NULL)
+ {
+ GDestroyNotify notify = self->notify;
+ gpointer data = self->notify_data;
+
+ self->notify = NULL;
+ self->notify_data = NULL;
+
+ notify (data);
+ }
+}
+
+
+/**
+ * egg_animation_stop:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Stops a running animation. The internal reference to the animation is
+ * dropped and therefore may cause the object to finalize.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_stop (EggAnimation *animation)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ if (animation->frame_clock)
+ {
+ gdk_frame_clock_end_updating (animation->frame_clock);
+ g_signal_handler_disconnect (animation->frame_clock, animation->tween_handler);
+ g_signal_handler_disconnect (animation->frame_clock, animation->after_paint_handler);
+ animation->tween_handler = 0;
+ }
+ else
+ {
+ g_source_remove (animation->tween_handler);
+ animation->tween_handler = 0;
+ }
+ egg_animation_unload_begin_values (animation);
+ egg_animation_notify (animation);
+ g_object_unref (animation);
+ }
+}
+
+
+/**
+ * egg_animation_add_property:
+ * @animation: (in): A #EggAnimation.
+ * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
+ * @value: (in): The new value for the property at the end of the animation.
+ *
+ * Adds a new property to the set of properties to be animated during the
+ * lifetime of the animation.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_add_property (EggAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value)
+{
+ Tween tween = { 0 };
+ GType type;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (pspec != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type);
+ g_return_if_fail (animation->target);
+ g_return_if_fail (!animation->tween_handler);
+
+ type = G_TYPE_FROM_INSTANCE (animation->target);
+ tween.is_child = !g_type_is_a (type, pspec->owner_type);
+ if (tween.is_child)
+ {
+ if (!GTK_IS_WIDGET (animation->target))
+ {
+ g_critical (_("Cannot locate property %s in class %s"),
+ pspec->name, g_type_name (type));
+ return;
+ }
+ }
+
+ tween.pspec = g_param_spec_ref (pspec);
+ g_value_init (&tween.begin, pspec->value_type);
+ g_value_init (&tween.end, pspec->value_type);
+ g_value_copy (value, &tween.end);
+ g_array_append_val (animation->tweens, tween);
+}
+
+
+/**
+ * egg_animation_dispose:
+ * @object: (in): A #EggAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_dispose (GObject *object)
+{
+ EggAnimation *self = EGG_ANIMATION (object);
+
+ g_clear_object (&self->target);
+ g_clear_object (&self->frame_clock);
+
+ G_OBJECT_CLASS (egg_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * egg_animation_finalize:
+ * @object: (in): A #EggAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_finalize (GObject *object)
+{
+ EggAnimation *self = EGG_ANIMATION (object);
+ Tween *tween;
+ guint i;
+
+ for (i = 0; i < self->tweens->len; i++)
+ {
+ tween = &g_array_index (self->tweens, Tween, i);
+ g_value_unset (&tween->begin);
+ g_value_unset (&tween->end);
+ g_param_spec_unref (tween->pspec);
+ }
+
+ g_array_unref (self->tweens);
+
+ G_OBJECT_CLASS (egg_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * egg_animation_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+egg_animation_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggAnimation *animation = EGG_ANIMATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
+ break;
+
+ case PROP_FRAME_CLOCK:
+ egg_animation_set_frame_clock (animation, g_value_get_object (value));
+ break;
+
+ case PROP_MODE:
+ animation->mode = g_value_get_enum (value);
+ break;
+
+ case PROP_TARGET:
+ egg_animation_set_target (animation, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+
+/**
+ * egg_animation_class_init:
+ * @klass: (in): A #EggAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+egg_animation_class_init (EggAnimationClass *klass)
+{
+ GObjectClass *object_class;
+ const gchar *slow_down_factor_env;
+
+ debug = !!g_getenv ("EGG_ANIMATION_DEBUG");
+ slow_down_factor_env = g_getenv ("EGG_ANIMATION_SLOW_DOWN_FACTOR");
+
+ if (slow_down_factor_env)
+ slow_down_factor = MAX (1, atoi (slow_down_factor_env));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = egg_animation_dispose;
+ object_class->finalize = egg_animation_finalize;
+ object_class->set_property = egg_animation_set_property;
+
+ /**
+ * EggAnimation:duration:
+ *
+ * The "duration" property is the total number of milliseconds that the
+ * animation should run before being completed.
+ */
+ properties[PROP_DURATION] =
+ g_param_spec_uint ("duration",
+ "Duration",
+ "The duration of the animation",
+ 0,
+ G_MAXUINT,
+ 250,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_FRAME_CLOCK] =
+ g_param_spec_object ("frame-clock",
+ "Frame Clock",
+ "An optional frame-clock to synchronize with.",
+ GDK_TYPE_FRAME_CLOCK,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EggAnimation:mode:
+ *
+ * The "mode" property is the Alpha function that should be used to
+ * determine the offset within the animation based on the current
+ * offset in the animations duration.
+ */
+ properties[PROP_MODE] =
+ g_param_spec_enum ("mode",
+ "Mode",
+ "The animation mode",
+ EGG_TYPE_ANIMATION_MODE,
+ EGG_ANIMATION_LINEAR,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EggAnimation:target:
+ *
+ * The "target" property is the #GObject that should have its properties
+ * animated.
+ */
+ properties[PROP_TARGET] =
+ g_param_spec_object ("target",
+ "Target",
+ "The target of the animation",
+ G_TYPE_OBJECT,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ /**
+ * EggAnimation::tick:
+ *
+ * The "tick" signal is emitted on each frame in the animation.
+ */
+ signals[TICK] = g_signal_new ("tick",
+ EGG_TYPE_ANIMATION,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+#define SET_ALPHA(_T, _t) \
+ alpha_funcs[EGG_ANIMATION_ ## _T] = egg_animation_alpha_ ## _t
+
+ SET_ALPHA (LINEAR, linear);
+ SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
+ SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
+ SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
+ SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
+ SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
+ SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
+
+#define SET_TWEEN(_T, _t) \
+ G_STMT_START { \
+ guint idx = G_TYPE_ ## _T; \
+ tween_funcs[idx] = tween_ ## _t; \
+ } G_STMT_END
+
+ SET_TWEEN (INT, int);
+ SET_TWEEN (UINT, uint);
+ SET_TWEEN (LONG, long);
+ SET_TWEEN (ULONG, ulong);
+ SET_TWEEN (FLOAT, float);
+ SET_TWEEN (DOUBLE, double);
+}
+
+
+/**
+ * egg_animation_init:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Initializes the #EggAnimation instance.
+ *
+ * Side effects: Everything.
+ */
+static void
+egg_animation_init (EggAnimation *animation)
+{
+ animation->duration_msec = 250;
+ animation->mode = EGG_ANIMATION_EASE_IN_OUT_QUAD;
+ animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+ animation->last_offset = -G_MINDOUBLE;
+}
+
+
+/**
+ * egg_animation_mode_get_type:
+ *
+ * Retrieves the GType for #EggAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+egg_animation_mode_get_type (void)
+{
+ static GType type_id = 0;
+ static const GEnumValue values[] = {
+ { EGG_ANIMATION_LINEAR, "EGG_ANIMATION_LINEAR", "linear" },
+ { EGG_ANIMATION_EASE_IN_QUAD, "EGG_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
+ { EGG_ANIMATION_EASE_IN_OUT_QUAD, "EGG_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
+ { EGG_ANIMATION_EASE_OUT_QUAD, "EGG_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
+ { EGG_ANIMATION_EASE_IN_CUBIC, "EGG_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
+ { EGG_ANIMATION_EASE_OUT_CUBIC, "EGG_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
+ { EGG_ANIMATION_EASE_IN_OUT_CUBIC, "EGG_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
+ { 0 }
+ };
+
+ if (G_UNLIKELY (!type_id))
+ type_id = g_enum_register_static ("EggAnimationMode", values);
+ return type_id;
+}
+
+/**
+ * egg_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_clock: (nullable): The #GdkFrameClock to synchronize to.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #EggAnimation.
+ */
+EggAnimation *
+egg_object_animatev (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args)
+{
+ EggAnimation *animation;
+ GObjectClass *klass;
+ GObjectClass *pklass;
+ const gchar *name;
+ GParamSpec *pspec;
+ GtkWidget *parent;
+ GValue value = { 0 };
+ gchar *error = NULL;
+ GType type;
+ GType ptype;
+ gboolean enable_animations;
+
+ g_return_val_if_fail (first_property != NULL, NULL);
+ g_return_val_if_fail (mode < EGG_ANIMATION_LAST, NULL);
+
+ if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+
+ /*
+ * If we have a frame clock, then we must be in the gtk thread and we
+ * should check GtkSettings for disabled animations. If we are disabled,
+ * we will just make the timeout immediate.
+ */
+ if (frame_clock != NULL)
+ {
+ g_object_get (gtk_settings_get_default (),
+ "gtk-enable-animations", &enable_animations,
+ NULL);
+
+ if (enable_animations == FALSE)
+ duration_msec = 0;
+ }
+
+ name = first_property;
+ type = G_TYPE_FROM_INSTANCE (object);
+ klass = G_OBJECT_GET_CLASS (object);
+ animation = g_object_new (EGG_TYPE_ANIMATION,
+ "duration", duration_msec,
+ "frame-clock", frame_clock,
+ "mode", mode,
+ "target", object,
+ NULL);
+
+ do
+ {
+ /*
+ * First check for the property on the object. If that does not exist
+ * then check if the object has a parent and look at its child
+ * properties (if it's a GtkWidget).
+ */
+ if (!(pspec = g_object_class_find_property (klass, name)))
+ {
+ if (!g_type_is_a (type, GTK_TYPE_WIDGET))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ if (!(parent = gtk_widget_get_parent (object)))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ pklass = G_OBJECT_GET_CLASS (parent);
+ ptype = G_TYPE_FROM_INSTANCE (parent);
+ if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
+ {
+ g_critical (_("Failed to find property %s in %s or parent %s"),
+ name, g_type_name (type), g_type_name (ptype));
+ goto failure;
+ }
+ }
+
+ g_value_init (&value, pspec->value_type);
+ G_VALUE_COLLECT (&value, args, 0, &error);
+ if (error != NULL)
+ {
+ g_critical (_("Failed to retrieve va_list value: %s"), error);
+ g_free (error);
+ goto failure;
+ }
+
+ egg_animation_add_property (animation, pspec, &value);
+ g_value_unset (&value);
+ }
+ while ((name = va_arg (args, const gchar *)));
+
+ egg_animation_start (animation);
+
+ return animation;
+
+failure:
+ g_object_ref_sink (animation);
+ g_object_unref (animation);
+ return NULL;
+}
+
+/**
+ * egg_object_animate:
+ * @object: (in): A #GObject.
+ * @mode: (in): The animation mode.
+ * @duration_msec: (in): The duration in milliseconds.
+ * @first_property: (in): The first property to animate.
+ *
+ * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
+ * will be animated from their current value to the target value over the time period.
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ * Side effects: None.
+ */
+EggAnimation*
+egg_object_animate (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...)
+{
+ EggAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = egg_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ return animation;
+}
+
+/**
+ * egg_object_animate_full:
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ */
+EggAnimation*
+egg_object_animate_full (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...)
+{
+ EggAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = egg_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ animation->notify = notify;
+ animation->notify_data = notify_data;
+
+ return animation;
+}
diff --git a/src/animation/egg-animation.h b/src/animation/egg-animation.h
new file mode 100644
index 000000000..62d979e55
--- /dev/null
+++ b/src/animation/egg-animation.h
@@ -0,0 +1,77 @@
+/* egg-animation.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_ANIMATION_H
+#define EGG_ANIMATION_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_ANIMATION (egg_animation_get_type())
+#define EGG_TYPE_ANIMATION_MODE (egg_animation_mode_get_type())
+
+G_DECLARE_FINAL_TYPE (EggAnimation, egg_animation,
+ EGG, ANIMATION, GInitiallyUnowned)
+
+typedef enum _EggAnimationMode EggAnimationMode;
+
+enum _EggAnimationMode
+{
+ EGG_ANIMATION_LINEAR,
+ EGG_ANIMATION_EASE_IN_QUAD,
+ EGG_ANIMATION_EASE_OUT_QUAD,
+ EGG_ANIMATION_EASE_IN_OUT_QUAD,
+ EGG_ANIMATION_EASE_IN_CUBIC,
+ EGG_ANIMATION_EASE_OUT_CUBIC,
+ EGG_ANIMATION_EASE_IN_OUT_CUBIC,
+
+ EGG_ANIMATION_LAST
+};
+
+GType egg_animation_mode_get_type (void);
+void egg_animation_start (EggAnimation *animation);
+void egg_animation_stop (EggAnimation *animation);
+void egg_animation_add_property (EggAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value);
+
+EggAnimation *egg_object_animatev (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args);
+EggAnimation* egg_object_animate (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+EggAnimation* egg_object_animate_full (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* EGG_ANIMATION_H */
diff --git a/src/animation/egg-frame-source.c b/src/animation/egg-frame-source.c
new file mode 100644
index 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..3ba234fd0
--- /dev/null
+++ b/src/animation/egg-frame-source.h
@@ -0,0 +1,32 @@
+/* egg-frame-source.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_FRAME_SOURCE_H
+#define EGG_FRAME_SOURCE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint egg_frame_source_add (guint frames_per_sec,
+ GSourceFunc callback,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* EGG_FRAME_SOURCE_H */
diff --git a/src/animation/ide-box-theatric.c b/src/animation/ide-box-theatric.c
new file mode 100644
index 000000000..cad8d4a44
--- /dev/null
+++ b/src/animation/ide-box-theatric.c
@@ -0,0 +1,420 @@
+/* ide-box-theatric.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-box-theatric"
+
+#include <animation/egg-animation.h>
+#include <glib/gi18n.h>
+
+#include "animation/ide-box-theatric.h"
+#include "animation/ide-cairo.h"
+
+struct _IdeBoxTheatric
+{
+ GObject parent_instance;
+
+ GtkWidget *target;
+ GtkWidget *toplevel;
+
+ GIcon *icon;
+ cairo_surface_t *icon_surface;
+ guint icon_surface_size;
+
+ GdkRectangle area;
+ GdkRectangle last_area;
+ GdkRGBA background_rgba;
+ gdouble alpha;
+
+ guint draw_handler;
+
+ guint background_set : 1;
+ guint pixbuf_failed : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ALPHA,
+ PROP_BACKGROUND,
+ PROP_HEIGHT,
+ PROP_ICON,
+ PROP_TARGET,
+ PROP_WIDTH,
+ PROP_X,
+ PROP_Y,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (IdeBoxTheatric, ide_box_theatric, G_TYPE_OBJECT)
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+get_toplevel_rect (IdeBoxTheatric *theatric,
+ GdkRectangle *area)
+{
+ gtk_widget_translate_coordinates (theatric->target, theatric->toplevel,
+ theatric->area.x, theatric->area.y,
+ &area->x, &area->y);
+
+ area->width = theatric->area.width;
+ area->height = theatric->area.height;
+}
+
+static gboolean
+on_toplevel_draw (GtkWidget *widget,
+ cairo_t *cr,
+ IdeBoxTheatric *self)
+{
+ GdkRectangle area;
+
+ g_assert (IDE_IS_BOX_THEATRIC (self));
+
+ get_toplevel_rect (self, &area);
+
+#if 0
+ g_print ("Drawing on %s at %d,%d %dx%d\n",
+ g_type_name (G_TYPE_FROM_INSTANCE (widget)),
+ area.x, area.y, area.width, area.height);
+#endif
+
+ if (self->background_set)
+ {
+ GdkRGBA bg;
+
+ bg = self->background_rgba;
+ bg.alpha = self->alpha;
+
+ ide_cairo_rounded_rectangle (cr, &area, 3, 3);
+ gdk_cairo_set_source_rgba (cr, &bg);
+ cairo_fill (cr);
+ }
+
+ /* Load an icon if necessary and cache the surface */
+ if (self->icon != NULL && self->icon_surface == NULL && !self->pixbuf_failed)
+ {
+ g_autoptr(GtkIconInfo) icon_info = NULL;
+ GtkIconTheme *icon_theme;
+ gint width;
+
+ width = area.width * 4;
+ icon_theme = gtk_icon_theme_get_default ();
+ icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
+ self->icon,
+ width,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+
+ if (icon_info != NULL)
+ {
+ GdkWindow *window = gtk_widget_get_window (widget);
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (self->target);
+ pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);
+
+ if (pixbuf != NULL)
+ {
+ self->icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
+ self->icon_surface_size = width;
+ self->pixbuf_failed = FALSE;
+ }
+ else
+ {
+ self->pixbuf_failed = TRUE;
+ }
+ }
+ }
+
+ if (self->icon_surface != NULL)
+ {
+ cairo_translate (cr, area.x, area.y);
+ cairo_rectangle (cr, 0, 0, area.width, area.height);
+ cairo_scale (cr,
+ area.width / (gdouble)self->icon_surface_size,
+ area.height / (gdouble)self->icon_surface_size);
+ cairo_set_source_surface (cr, self->icon_surface, 0, 0);
+ cairo_paint_with_alpha (cr, self->alpha);
+ }
+
+ self->last_area = area;
+
+ return FALSE;
+}
+
+static void
+ide_box_theatric_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
+
+ if (G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify)
+ G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify (object, pspec);
+
+ if (self->target && self->toplevel)
+ {
+ GdkWindow *window;
+ GdkRectangle area;
+
+ get_toplevel_rect (IDE_BOX_THEATRIC (object), &area);
+
+#if 0
+ g_print (" Invalidate : %d,%d %dx%d\n",
+ area.x, area.y, area.width, area.height);
+#endif
+
+ window = gtk_widget_get_window (self->toplevel);
+
+ if (window != NULL)
+ {
+ gdk_window_invalidate_rect (window, &self->last_area, TRUE);
+ gdk_window_invalidate_rect (window, &area, TRUE);
+ }
+ }
+}
+
+static void
+ide_box_theatric_dispose (GObject *object)
+{
+ IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
+
+ if (self->target)
+ {
+ if (self->draw_handler && self->toplevel)
+ {
+ g_signal_handler_disconnect (self->toplevel, self->draw_handler);
+ self->draw_handler = 0;
+ }
+ g_object_remove_weak_pointer (G_OBJECT (self->target),
+ (gpointer *) &self->target);
+ self->target = NULL;
+ }
+
+ g_clear_pointer (&self->icon_surface, cairo_surface_destroy);
+ g_clear_object (&self->icon);
+
+ G_OBJECT_CLASS (ide_box_theatric_parent_class)->dispose (object);
+}
+
+static void
+ide_box_theatric_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALPHA:
+ g_value_set_double (value, theatric->alpha);
+ break;
+
+ case PROP_BACKGROUND:
+ g_value_take_string (value,
+ gdk_rgba_to_string (&theatric->background_rgba));
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_int (value, theatric->area.height);
+ break;
+
+ case PROP_ICON:
+ g_value_set_object (value, theatric->icon);
+ break;
+
+ case PROP_TARGET:
+ g_value_set_object (value, theatric->target);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_int (value, theatric->area.width);
+ break;
+
+ case PROP_X:
+ g_value_set_int (value, theatric->area.x);
+ break;
+
+ case PROP_Y:
+ g_value_set_int (value, theatric->area.y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_box_theatric_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALPHA:
+ theatric->alpha = g_value_get_double (value);
+ break;
+
+ case PROP_BACKGROUND:
+ {
+ const gchar *str = g_value_get_string (value);
+
+ if (str == NULL)
+ {
+ gdk_rgba_parse (&theatric->background_rgba, "#000000");
+ theatric->background_rgba.alpha = 0;
+ theatric->background_set = FALSE;
+ }
+ else
+ {
+ gdk_rgba_parse (&theatric->background_rgba, str);
+ theatric->background_set = TRUE;
+ }
+ }
+ break;
+
+ case PROP_HEIGHT:
+ theatric->area.height = g_value_get_int (value);
+ break;
+
+ case PROP_ICON:
+ g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
+ g_clear_object (&theatric->icon);
+ theatric->icon = g_value_dup_object (value);
+ theatric->pixbuf_failed = FALSE;
+ break;
+
+ case PROP_TARGET:
+ theatric->target = g_value_get_object (value);
+ theatric->toplevel = gtk_widget_get_toplevel (theatric->target);
+ g_object_add_weak_pointer (G_OBJECT (theatric->target),
+ (gpointer *) &theatric->target);
+ theatric->draw_handler =
+ g_signal_connect_after (theatric->toplevel,
+ "draw",
+ G_CALLBACK (on_toplevel_draw),
+ theatric);
+ break;
+
+ case PROP_WIDTH:
+ theatric->area.width = g_value_get_int (value);
+ break;
+
+ case PROP_X:
+ theatric->area.x = g_value_get_int (value);
+ break;
+
+ case PROP_Y:
+ theatric->area.y = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+
+ g_object_notify_by_pspec (object, pspec);
+}
+
+static void
+ide_box_theatric_class_init (IdeBoxTheatricClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = ide_box_theatric_dispose;
+ object_class->notify = ide_box_theatric_notify;
+ object_class->get_property = ide_box_theatric_get_property;
+ object_class->set_property = ide_box_theatric_set_property;
+
+ properties[PROP_ALPHA] =
+ g_param_spec_double ("alpha",
+ "Alpha",
+ "Alpha",
+ 0.0,
+ 1.0,
+ 1.0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_BACKGROUND] =
+ g_param_spec_string ("background",
+ "background",
+ "background",
+ "#000000",
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_HEIGHT] =
+ g_param_spec_int ("height",
+ "height",
+ "height",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ICON] =
+ g_param_spec_object ("icon",
+ "Icon",
+ "The GIcon to render over the background",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_TARGET] =
+ g_param_spec_object ("target",
+ "Target",
+ "Target",
+ GTK_TYPE_WIDGET,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_WIDTH] =
+ g_param_spec_int ("width",
+ "width",
+ "width",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_X] =
+ g_param_spec_int ("x",
+ "x",
+ "x",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_Y] =
+ g_param_spec_int ("y",
+ "y",
+ "y",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_box_theatric_init (IdeBoxTheatric *theatric)
+{
+}
diff --git a/src/animation/ide-box-theatric.h b/src/animation/ide-box-theatric.h
new file mode 100644
index 000000000..d2545f0e2
--- /dev/null
+++ b/src/animation/ide-box-theatric.h
@@ -0,0 +1,33 @@
+/* ide-box-theatric.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BOX_THEATRIC_H
+#define IDE_BOX_THEATRIC_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BOX_THEATRIC (ide_box_theatric_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBoxTheatric, ide_box_theatric,
+ IDE, BOX_THEATRIC, GObject)
+
+G_END_DECLS
+
+#endif /* IDE_BOX_THEATRIC_H */
diff --git a/src/animation/ide-cairo.c b/src/animation/ide-cairo.c
new file mode 100644
index 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..703821b1d
--- /dev/null
+++ b/src/animation/ide-cairo.h
@@ -0,0 +1,68 @@
+/* ide-cairo.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CAIRO_H
+#define IDE_CAIRO_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+cairo_region_t *ide_cairo_region_create_from_clip_extents (cairo_t *cr);
+void ide_cairo_rounded_rectangle (cairo_t *cr,
+ const GdkRectangle *rect,
+ gint x_radius,
+ gint y_radius);
+
+static inline gboolean
+_ide_cairo_rectangle_x2 (const cairo_rectangle_int_t *rect)
+{
+ return rect->x + rect->width;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_y2 (const cairo_rectangle_int_t *rect)
+{
+ return rect->y + rect->height;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_center (const cairo_rectangle_int_t *rect)
+{
+ return rect->x + (rect->width/2);
+}
+
+static inline gboolean
+_ide_cairo_rectangle_middle (const cairo_rectangle_int_t *rect)
+{
+ return rect->y + (rect->height/2);
+}
+
+static inline cairo_bool_t
+_ide_cairo_rectangle_contains_rectangle (const cairo_rectangle_int_t *a,
+ const cairo_rectangle_int_t *b)
+{
+ return (a->x <= b->x &&
+ a->x + (int) a->width >= b->x + (int) b->width &&
+ a->y <= b->y &&
+ a->y + (int) a->height >= b->y + (int) b->height);
+}
+
+G_END_DECLS
+
+#endif /* IDE_CAIRO_H */
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
index 432ce1e80..e78dffe92 100644
--- a/src/nautilus-toolbar.c
+++ b/src/nautilus-toolbar.c
@@ -37,6 +37,9 @@
#include "nautilus-file-undo-manager.h"
#include "nautilus-toolbar-menu-sections.h"
+#include "animation/ide-box-theatric.h"
+#include "animation/egg-animation.h"
+
#include <glib/gi18n.h>
#include <math.h>
@@ -44,6 +47,9 @@
#define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */
#define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */
+#define ANIMATION_X_GROW 30
+#define ANIMATION_Y_GROW 30
+
typedef enum
{
NAUTILUS_NAVIGATION_DIRECTION_NONE,
@@ -574,8 +580,32 @@ update_operations (NautilusToolbar *self)
* property set. */
if (gtk_widget_is_visible (GTK_WIDGET (self)))
{
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->operations_button),
- TRUE);
+ GtkAllocation rect;
+ IdeBoxTheatric *theatric;
+
+ gtk_widget_get_allocation (GTK_WIDGET (self->operations_button), &rect);
+ theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
+ "alpha", 0.9,
+ "background", "#fdfdfd",
+ "target", self->operations_button,
+ "height", rect.height,
+ "width", rect.width,
+ "x", rect.x,
+ "y", rect.y,
+ NULL);
+
+ egg_object_animate_full (theatric,
+ EGG_ANIMATION_EASE_IN_CUBIC,
+ 250,
+ gtk_widget_get_frame_clock (GTK_WIDGET (self->operations_button)),
+ g_object_unref,
+ theatric,
+ "x", rect.x - ANIMATION_X_GROW,
+ "width", rect.width + (ANIMATION_X_GROW * 2),
+ "y", rect.y - ANIMATION_Y_GROW,
+ "height", rect.height + (ANIMATION_Y_GROW * 2),
+ "alpha", 0.0,
+ NULL);
}
}