[Xfce4-commits] [apps/xfdashboard] 01/01: Add hard-coded animation for expand and collapse of XfdashboardCollapse

noreply at xfce.org noreply at xfce.org
Wed Oct 23 18:46:25 CEST 2019


This is an automated email from the git hooks/post-receive script.

n   o   m   a   d       p   u   s   h   e   d       a       c   o   m   m   i   t       t   o       b   r   a   n   c   h       m   a   s   t   e   r   
   in repository apps/xfdashboard.

commit 0fa7008e0e8c7485347135fdd6b037561ef31937
Author: Stephan Haller <nomad at froevel.de>
Date:   Wed Oct 23 18:45:42 2019 +0200

    Add hard-coded animation for expand and collapse of XfdashboardCollapse
---
 libxfdashboard/Makefile.am        |   6 +
 libxfdashboard/animation.c        | 683 ++++++++++++++++++++++++++
 libxfdashboard/animation.h        | 105 ++++
 libxfdashboard/application.c      |   2 +
 libxfdashboard/collapse-box.c     |  42 ++
 libxfdashboard/debug.h            |   2 +
 libxfdashboard/theme-animation.c  | 998 ++++++++++++++++++++++++++++++++++++++
 libxfdashboard/theme-animation.h  | 108 +++++
 libxfdashboard/theme.c            |  81 +++-
 libxfdashboard/theme.h            |   2 +
 libxfdashboard/transition-group.c | 683 ++++++++++++++++++++++++++
 libxfdashboard/transition-group.h |  95 ++++
 12 files changed, 2806 insertions(+), 1 deletion(-)

diff --git a/libxfdashboard/Makefile.am b/libxfdashboard/Makefile.am
index fe6ae6c..1d18cb0 100644
--- a/libxfdashboard/Makefile.am
+++ b/libxfdashboard/Makefile.am
@@ -28,6 +28,7 @@ libxfdashboard_la_built_sources = \
 libxfdashboard_la_headers = \
 	action-button.h \
 	actor.h \
+	animation.h \
 	application.h \
 	application-button.h \
 	application-database.h \
@@ -80,11 +81,13 @@ libxfdashboard_la_headers = \
 	stylable.h \
 	text-box.h \
 	theme.h \
+	theme-animation.h \
 	theme-css.h \
 	theme-effects.h \
 	theme-layout.h \
 	toggle-button.h \
 	tooltip-action.h \
+	transition-group.h \
 	types.h \
 	utils.h \
 	view.h \
@@ -105,6 +108,7 @@ libxfdashboard_la_SOURCES = \
 	$(xfdashboard_headers) \
 	action-button.c \
 	actor.c \
+	animation.c \
 	application.c \
 	application-button.c \
 	application-database.c \
@@ -156,11 +160,13 @@ libxfdashboard_la_SOURCES = \
 	stylable.c \
 	text-box.c \
 	theme.c \
+	theme-animation.c \
 	theme-css.c \
 	theme-effects.c \
 	theme-layout.c \
 	toggle-button.c \
 	tooltip-action.c \
+	transition-group.c \
 	utils.c \
 	view.c \
 	view-manager.c \
diff --git a/libxfdashboard/animation.c b/libxfdashboard/animation.c
new file mode 100644
index 0000000..a70a71f
--- /dev/null
+++ b/libxfdashboard/animation.c
@@ -0,0 +1,683 @@
+/*
+ * animation: A animation for an actor
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+/**
+ * SECTION:animation
+ * @short_description: An animation for an actor
+ * @include: xfdashboard/animation.h
+ *
+ * This actor is a #XfdashboardButton and behaves exactly like a key binding which
+ * performs a specified action on a specific actor when the associated key
+ * combination is pressed. But instead of a key combination a button is displayed
+ * and the action performed when this button is clicked.
+ *
+ * A #XfdashboardAnimation is usually created in the layout definition
+ * of a theme but it can also be created with xfdashboard_animation_new()
+ * followed by a call to xfdashboard_animation_set_target() and
+ * xfdashboard_animation_set_action() to configure it.
+ *
+ * For example a #XfdashboardAnimation can be created which will quit the
+ * application when clicked:
+ *
+ * |[<!-- language="C" -->
+ *   ClutterActor       *actionButton;
+ *
+ *   actionButton=xfdashboard_animation_new();
+ *   xfdashboard_animation_set_target(XFDASHBOARD_ANIMATION(actionButton), "XfdashboardApplication");
+ *   xfdashboard_animation_set_action(XFDASHBOARD_ANIMATION(actionButton), "exit");
+ * ]|
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libxfdashboard/animation.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <libxfdashboard/transition-group.h>
+#include <libxfdashboard/application.h>
+#include <libxfdashboard/theme.h>
+#include <libxfdashboard/compat.h>
+#include <libxfdashboard/debug.h>
+
+
+/* Define this class in GObject system */
+struct _XfdashboardAnimationPrivate
+{
+	/* Properties related */
+	gchar								*id;
+
+	/* Instance related */
+	GSList								*entries;
+
+	XfdashboardAnimationDoneCallback	doneCallback;
+	gpointer							doneUserData;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardAnimation,
+							xfdashboard_animation,
+							G_TYPE_OBJECT);
+
+/* Properties */
+enum
+{
+	PROP_0,
+
+	PROP_ID,
+
+	PROP_LAST
+};
+
+static GParamSpec* XfdashboardAnimationProperties[PROP_LAST]={ 0, };
+
+
+/* IMPLEMENTATION: Private variables and methods */
+
+typedef struct _XfdashboardAnimationEntry		XfdashboardAnimationEntry;
+struct _XfdashboardAnimationEntry
+{
+	XfdashboardAnimation	*self;
+	ClutterActor			*actor;
+	ClutterTransition		*transition;
+	guint					actorDestroyID;
+	guint					transitionStoppedID;
+	guint					newFrameSignalID;
+};
+
+/* Time at a transition has elapsed. This signal is only catched once for
+ * a transition, so complete missing "to" values at the transition(s).
+ */
+static void _xfdashboard_animation_on_transition_new_frame(ClutterTransition *inTransition,
+															gint inElapsed,
+															gpointer inUserData)
+{
+	XfdashboardAnimationEntry		*entry;
+	GSList							*transitions;
+	GSList							*iter;
+
+	g_return_if_fail(CLUTTER_IS_TRANSITION(inTransition));
+	g_return_if_fail(inUserData);
+
+	entry=(XfdashboardAnimationEntry*)inUserData;
+
+	/* Check if we have to handle a transition group */
+	if(XFDASHBOARD_IS_TRANSITION_GROUP(entry->transition))
+	{
+		/* Get list of transitions from group */
+		transitions=xfdashboard_transition_group_get_transitions(XFDASHBOARD_TRANSITION_GROUP(entry->transition));
+
+		/* Iterate through transitions of group and complete missing "to" values */
+		for(iter=transitions; iter; iter=g_slist_next(iter))
+		{
+			ClutterPropertyTransition	*transition;
+			ClutterInterval				*interval;
+			GValue						*intervalToValue;
+			ClutterAnimatable			*animatable;
+			GValue						toValue=G_VALUE_INIT;
+
+			/* Skip invalid transitions or transitions not modifiing properties */
+			if(!iter->data) continue;
+
+			if(!CLUTTER_IS_PROPERTY_TRANSITION(iter->data))
+			{
+#ifdef DEBUG
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"Transition %s@%p is not a ClutterPropertyTransition",
+									G_OBJECT_TYPE_NAME(iter->data), iter->data);
+#endif
+				continue;
+			}
+
+			transition=CLUTTER_PROPERTY_TRANSITION(iter->data);
+
+			/* Check if "to" value is missed at property-modifiing transition */
+			interval=clutter_transition_get_interval(CLUTTER_TRANSITION(transition));
+			if(!interval)
+			{
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"No interval set at transition %s@%p for property %s",
+									G_OBJECT_TYPE_NAME(transition), transition,
+									clutter_property_transition_get_property_name(transition));
+				continue;
+			}
+
+			intervalToValue=clutter_interval_peek_final_value(interval);
+			if(!intervalToValue)
+			{
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"Could not get final value from interval set at transition %s@%p for property %s",
+									G_OBJECT_TYPE_NAME(transition), transition,
+									clutter_property_transition_get_property_name(transition));
+				continue;
+			}
+
+			if(clutter_interval_is_valid(interval))
+			{
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"Valid interval set at transition %s@%p for property %s - no need to complete final value",
+									G_OBJECT_TYPE_NAME(transition), transition,
+									clutter_property_transition_get_property_name(transition));
+				continue;
+			}
+
+			/* Complete missing "to" value */
+			animatable=clutter_transition_get_animatable(CLUTTER_TRANSITION(transition));
+			if(!animatable)
+			{
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"Cannot determine final value from interval set at transition %s@%p for property %s as no animatable actor was set",
+									G_OBJECT_TYPE_NAME(transition), transition,
+									clutter_property_transition_get_property_name(transition));
+				continue;
+			}
+
+			g_object_get_property(G_OBJECT(animatable),
+									clutter_property_transition_get_property_name(transition),
+									&toValue);
+			if(G_VALUE_TYPE(&toValue)!=G_TYPE_INVALID)
+			{
+#ifdef DEBUG
+				gchar	*valueString;
+
+				valueString=g_strdup_value_contents(&toValue);
+				XFDASHBOARD_DEBUG(entry->self, ANIMATION,
+									"Set final value %s (type %s) at interval set of transition %s@%p for property %s",
+									valueString, G_VALUE_TYPE_NAME(&toValue),
+									G_OBJECT_TYPE_NAME(transition), transition,
+									clutter_property_transition_get_property_name(transition));
+				g_free(valueString);
+#endif
+				clutter_interval_set_final_value(interval, &toValue);
+			}
+			g_value_unset(&toValue);
+		}
+
+		/* Release allocated resources */
+		g_slist_free_full(transitions, g_object_unref);
+	}
+
+	/* We handled the transition so remove signal handler */
+	g_signal_handler_disconnect(entry->transition, entry->newFrameSignalID);
+	entry->newFrameSignalID=0;
+}
+
+/* Frees an animation entry */
+static void _xfdashboard_animation_entry_free(XfdashboardAnimationEntry *inData)
+{
+	g_return_if_fail(inData);
+
+	/* Release allocated resources */
+	if(inData->newFrameSignalID) g_signal_handler_disconnect(inData->transition, inData->newFrameSignalID);
+	if(inData->transitionStoppedID) g_signal_handler_disconnect(inData->transition, inData->transitionStoppedID);
+	if(inData->actorDestroyID) g_signal_handler_disconnect(inData->actor, inData->actorDestroyID);
+	if(inData->transition) g_object_unref(inData->transition);
+	if(inData->actor)
+	{
+		clutter_actor_remove_transition(inData->actor, inData->self->priv->id);
+		g_object_unref(inData->actor);
+	}
+	g_free(inData);
+}
+
+/* The transition, we added to an actor, has stopped. If transition reached end of
+ * timeline, remove entry from. When removing entry from list of entries, we unref
+ * objects and disconnect signal handlers automatically. No need to do it here.
+ */
+static void _xfdashboard_animation_on_transition_stopped(XfdashboardAnimation *self,
+															gboolean isFinished,
+															gpointer inUserData)
+{
+	XfdashboardAnimationPrivate		*priv;
+	ClutterTransition				*destroyedTransition;
+	GSList							*iter;
+	XfdashboardAnimationEntry		*entry;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(self));
+	g_return_if_fail(CLUTTER_IS_TRANSITION(inUserData));
+
+	priv=self->priv;
+	destroyedTransition=CLUTTER_TRANSITION(inUserData);
+
+	/* Only handle stopped transition if it reached end of timeline because this
+	 * signal is emitted either the transition has been stopped manually or
+	 * programmically or it has reached the end of timeline even after all repeats
+	 * has passed. So we have to check the "is-finished" parameter.
+	 */
+	if(!isFinished)
+	{
+#ifdef DEBUG
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Do not remove entry for manually stopped transition of animation '%s'",
+							priv->id);
+#endif
+		return;
+	}
+
+	XFDASHBOARD_DEBUG(self, ANIMATION,
+						"Stopped animation '%s'",
+						priv->id);
+
+	/* Find entries to remove from list of entries */
+	for(iter=priv->entries; iter; iter=g_slist_next(iter))
+	{
+		/* Get entry */
+		entry=(XfdashboardAnimationEntry*)iter->data;
+		if(!entry) continue;
+
+		/* Check if the currently iterated entry must be freed */
+		if(entry->transition==destroyedTransition)
+		{
+#ifdef DEBUG
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Transition %s@%p of actor %s@%p stopped, removing entry %p from animation list of animation '%s'",
+								G_OBJECT_TYPE_NAME(destroyedTransition),
+								destroyedTransition,
+								G_OBJECT_TYPE_NAME(entry->actor),
+								entry->actor,
+								iter->data,
+								priv->id);
+#endif
+
+			priv->entries=g_slist_remove_link(priv->entries, iter);
+			_xfdashboard_animation_entry_free(entry);
+			g_slist_free_1(iter);
+		}
+	}
+
+	/* If list of entries is empty now, remove animation */
+	if(g_slist_length(priv->entries)==0)
+	{
+#ifdef DEBUG
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Animation list is empty after stopped transition, unreffing animation '%s'",
+							priv->id);
+#endif
+		g_object_unref(self);
+	}
+}
+
+/* The actor, we added a transition to, is going to be destroyed, so remove entry
+ * from list. When removing entry from list of entries, we unref objects and
+ * disconnect signal handlers automatically. No need to do it here.
+ */
+static void _xfdashboard_animation_on_actor_destroyed(XfdashboardAnimation *self,
+														gpointer inUserData)
+{
+	XfdashboardAnimationPrivate		*priv;
+	ClutterActor					*destroyedActor;
+	GSList							*iter;
+	XfdashboardAnimationEntry		*entry;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(self));
+	g_return_if_fail(CLUTTER_IS_ACTOR(inUserData));
+
+	priv=self->priv;
+	destroyedActor=CLUTTER_ACTOR(inUserData);
+
+	/* Find entries to remove from list of entries */
+	for(iter=priv->entries; iter; iter=g_slist_next(iter))
+	{
+		/* Get entry */
+		entry=(XfdashboardAnimationEntry*)iter->data;
+		if(!entry) continue;
+
+		/* Check if the currently iterated entry must be freed */
+		if(entry->actor==destroyedActor)
+		{
+#ifdef DEBUG
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Actor %s@%p destroyed, removing entry %p from animation list of animation '%s'",
+								G_OBJECT_TYPE_NAME(destroyedActor),
+								destroyedActor,
+								iter->data,
+								priv->id);
+#endif
+
+			priv->entries=g_slist_remove_link(priv->entries, iter);
+			_xfdashboard_animation_entry_free(entry);
+			g_slist_free_1(iter);
+		}
+	}
+
+	/* If list of entries is empty now, remove animation */
+	if(g_slist_length(priv->entries)==0)
+	{
+#ifdef DEBUG
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Animation list is empty after destroyed actor, unreffing animation '%s'",
+							priv->id);
+#endif
+		g_object_unref(self);
+	}
+}
+
+/* Adds a transition group to an actor for this animation */
+static void _xfdashboard_animation_real_add_animation(XfdashboardAnimation *self,
+														ClutterActor *inActor,
+														ClutterTransition *inTransition)
+{
+	XfdashboardAnimationPrivate		*priv;
+	XfdashboardAnimationEntry		*data;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(self));
+	g_return_if_fail(CLUTTER_IS_ACTOR(inActor));
+	g_return_if_fail(CLUTTER_IS_TRANSITION(inTransition));
+
+	priv=self->priv;
+
+	/* Create animation entry data */
+	data=g_new0(XfdashboardAnimationEntry, 1);
+	if(!data)
+	{
+		g_critical(_("Cannot allocate memory for animation entry with actor '%s' at animation '%s'"),
+					G_OBJECT_TYPE_NAME(inActor),
+					priv->id);
+		return;
+	}
+
+	data->self=self;
+	data->actor=g_object_ref(inActor);
+	data->transition=g_object_ref(inTransition);
+	data->actorDestroyID=g_signal_connect_swapped(inActor,
+													"destroy",
+													G_CALLBACK(_xfdashboard_animation_on_actor_destroyed),
+													self);
+	data->transitionStoppedID=g_signal_connect_swapped(inTransition,
+														"stopped",
+														G_CALLBACK(_xfdashboard_animation_on_transition_stopped),
+														self);
+	data->newFrameSignalID=g_signal_connect(inTransition,
+											"new-frame",
+											G_CALLBACK(_xfdashboard_animation_on_transition_new_frame),
+											data);
+
+	/* Add animation entry data to list to entries */
+	priv->entries=g_slist_prepend(priv->entries, data);
+}
+
+/* Set animation ID */
+static void _xfdashboard_animation_set_id(XfdashboardAnimation *self, const gchar *inID)
+{
+	XfdashboardAnimationPrivate		*priv;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(self));
+	g_return_if_fail(!inID || *inID);
+
+	priv=self->priv;
+
+	/* Set value if changed */
+	if(g_strcmp0(priv->id, inID)!=0)
+	{
+		if(priv->id)
+		{
+			g_free(priv->id);
+			priv->id=NULL;
+		}
+
+		if(inID)
+		{
+			priv->id=g_strdup(inID);
+		}
+
+		/* Notify about property change */
+		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardAnimationProperties[PROP_ID]);
+	}
+}
+
+/* IMPLEMENTATION: GObject */
+
+/* Dispose this object */
+static void _xfdashboard_animation_dispose(GObject *inObject)
+{
+	XfdashboardAnimation			*self=XFDASHBOARD_ANIMATION(inObject);
+	XfdashboardAnimationPrivate		*priv=self->priv;
+
+	XFDASHBOARD_DEBUG(self, ANIMATION,
+						"Destroying animation '%s'",
+						priv->id);
+
+	/* Call done callback first before any private data is destroyed */
+	if(priv->doneCallback)
+	{
+		/* Call done callback function */
+		(priv->doneCallback)(self, priv->doneUserData);
+
+		/* Unset done callback data */
+		priv->doneCallback=NULL;
+		priv->doneUserData=NULL;
+	}
+
+	/* Release our allocated variables
+	 * Order is important as the ID MUST be released at last!
+	 */
+	if(priv->entries)
+	{
+		g_slist_free_full(priv->entries, (GDestroyNotify)_xfdashboard_animation_entry_free);
+		priv->entries=NULL;
+	}
+
+	if(priv->id)
+	{
+		g_free(priv->id);
+		priv->id=NULL;
+	}
+
+	/* Call parent's class dispose method */
+	G_OBJECT_CLASS(xfdashboard_animation_parent_class)->dispose(inObject);
+}
+
+/* Set/get properties */
+static void _xfdashboard_animation_set_property(GObject *inObject,
+												guint inPropID,
+												const GValue *inValue,
+												GParamSpec *inSpec)
+{
+	XfdashboardAnimation			*self=XFDASHBOARD_ANIMATION(inObject);
+
+	switch(inPropID)
+	{
+		case PROP_ID:
+			_xfdashboard_animation_set_id(self, g_value_get_string(inValue));
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
+			break;
+	}
+}
+
+static void _xfdashboard_animation_get_property(GObject *inObject,
+												guint inPropID,
+												GValue *outValue,
+												GParamSpec *inSpec)
+{
+	XfdashboardAnimation			*self=XFDASHBOARD_ANIMATION(inObject);
+	XfdashboardAnimationPrivate		*priv=self->priv;
+
+	switch(inPropID)
+	{
+		case PROP_ID:
+			g_value_set_string(outValue, priv->id);
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
+			break;
+	}
+}
+
+/* Class initialization
+ * Override functions in parent classes and define properties
+ * and signals
+ */
+void xfdashboard_animation_class_init(XfdashboardAnimationClass *klass)
+{
+	GObjectClass			*gobjectClass=G_OBJECT_CLASS(klass);
+
+	/* Override functions */
+	klass->add_animation=_xfdashboard_animation_real_add_animation;
+
+	gobjectClass->dispose=_xfdashboard_animation_dispose;
+	gobjectClass->set_property=_xfdashboard_animation_set_property;
+	gobjectClass->get_property=_xfdashboard_animation_get_property;
+
+	/* Define properties */
+	/**
+	 * XfdashboardAnimation:id:
+	 *
+	 * A string with the animation ID
+	 */
+	XfdashboardAnimationProperties[PROP_ID]=
+		g_param_spec_string("id",
+								_("ID"),
+								_("The animation ID"),
+								NULL,
+								G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardAnimationProperties);
+}
+
+/* Object initialization
+ * Create private structure and set up default values
+ */
+void xfdashboard_animation_init(XfdashboardAnimation *self)
+{
+	XfdashboardAnimationPrivate		*priv;
+
+	priv=self->priv=xfdashboard_animation_get_instance_private(self);
+
+	/* Set up default values */
+	priv->id=NULL;
+	priv->entries=NULL;
+}
+
+
+/* IMPLEMENTATION: Public API */
+
+/**
+ * xfdashboard_animation_new:
+ * @inSender: A #ClutterActor emitting the animation signal
+ * @inSignal: A string containing the signal emitted at sending actor
+ *
+ * Creates a new animation of type #XfdashboardAnimation matching the sending
+ * actor at @inSender and the emitted signal at @inSignal.
+ *
+ * This function is the logical equivalent of:
+ *
+ * |[<!-- language="C" -->
+ *   XfdashboardTheme          *theme;
+ *   XfdashboardThemeAnimation *theme_animations;
+ *
+ *   theme=xfdashboard_application_get_theme(NULL);
+ *   theme_animations=xfdashboard_theme_get_animation(theme);
+ *   animation=xfdashboard_theme_animation_create(theme_animations, inSender, inSignal);
+ * ]|
+ *
+ * Return value: (transfer full): The instance of #XfdashboardAnimation.
+ */
+XfdashboardAnimation* xfdashboard_animation_new(XfdashboardActor *inSender, const gchar *inSignal)
+{
+	XfdashboardTheme				*theme;
+	XfdashboardThemeAnimation		*themeAnimation;
+	XfdashboardAnimation			*animation;
+
+	g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
+	g_return_val_if_fail(inSignal && *inSignal, NULL);
+
+	theme=xfdashboard_application_get_theme(NULL);
+	themeAnimation=xfdashboard_theme_get_animation(theme);
+	animation=xfdashboard_theme_animation_create(themeAnimation, inSender, inSignal);
+
+	return(animation);
+}
+
+/**
+ * xfdashboard_animation_get_id:
+ * @self: A #XfdashboardAnimation
+ *
+ * Retrieves the animation ID of @self.
+ *
+ * Return value: A string with animations's ID
+ */
+const gchar* xfdashboard_animation_get_id(XfdashboardAnimation *self)
+{
+	g_return_val_if_fail(XFDASHBOARD_IS_ANIMATION(self), NULL);
+
+	return(self->priv->id);
+}
+
+/**
+ * xfdashboard_animation_run:
+ * @self: A #XfdashboardAnimation
+ * @inCallback: Function to call when animation is destroyed
+ * @inUserData: Data to pass to callback function
+ *
+ * Starts the animation of @self. The callback function @inCallback
+ * with this animation and the user-data at @inUserData is called
+ * when the animation is destroyed, either is has reached the end
+ * of its timeline or was stopped before.
+ */
+void xfdashboard_animation_run(XfdashboardAnimation *self,
+								XfdashboardAnimationDoneCallback inCallback,
+								gpointer inUserData)
+{
+	XfdashboardAnimationPrivate		*priv;
+	GSList							*iter;
+	XfdashboardAnimationEntry		*entry;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(self));
+
+	priv=self->priv;
+
+	/* Store callback function and its user data which is called
+	 * in destruction function when animation is done, e.g.
+	 * animation completed or was removed before it completed.
+	 */
+	priv->doneCallback=inCallback;
+	priv->doneUserData=inUserData;
+
+	/* Add all transition to their actors now */
+	for(iter=priv->entries; iter; iter=g_slist_next(iter))
+	{
+		/* Get entry */
+		entry=(XfdashboardAnimationEntry*)iter->data;
+		if(!entry) continue;
+
+		/* Add transition to actor which will start immediately */
+		clutter_actor_add_transition(entry->actor, priv->id, entry->transition);
+#ifdef DEBUG
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Animation '%s' added transition %p to actor %s@%p",
+							priv->id,
+							entry->transition,
+							G_OBJECT_TYPE_NAME(entry->actor),
+							entry->actor);
+#endif
+	}
+
+	XFDASHBOARD_DEBUG(self, ANIMATION,
+						"Started animation '%s'",
+						priv->id);
+}
diff --git a/libxfdashboard/animation.h b/libxfdashboard/animation.h
new file mode 100644
index 0000000..9380e00
--- /dev/null
+++ b/libxfdashboard/animation.h
@@ -0,0 +1,105 @@
+/*
+ * animation: A animation for an actor
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+#ifndef __LIBXFDASHBOARD_ANIMATION__
+#define __LIBXFDASHBOARD_ANIMATION__
+
+#if !defined(__LIBXFDASHBOARD_H_INSIDE__) && !defined(LIBXFDASHBOARD_COMPILATION)
+#error "Only <libxfdashboard/libxfdashboard.h> can be included directly."
+#endif
+
+#include <clutter/clutter.h>
+
+#include <libxfdashboard/actor.h>
+
+G_BEGIN_DECLS
+
+/* Object declaration */
+#define XFDASHBOARD_TYPE_ANIMATION				(xfdashboard_animation_get_type())
+#define XFDASHBOARD_ANIMATION(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), XFDASHBOARD_TYPE_ANIMATION, XfdashboardAnimation))
+#define XFDASHBOARD_IS_ANIMATION(obj)			(G_TYPE_CHECK_INSTANCE_TYPE((obj), XFDASHBOARD_TYPE_ANIMATION))
+#define XFDASHBOARD_ANIMATION_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), XFDASHBOARD_TYPE_ANIMATION, XfdashboardAnimationClass))
+#define XFDASHBOARD_IS_ANIMATION_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), XFDASHBOARD_TYPE_ANIMATION))
+#define XFDASHBOARD_ANIMATION_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), XFDASHBOARD_TYPE_ANIMATION, XfdashboardAnimationClass))
+
+typedef struct _XfdashboardAnimation			XfdashboardAnimation;
+typedef struct _XfdashboardAnimationClass		XfdashboardAnimationClass;
+typedef struct _XfdashboardAnimationPrivate		XfdashboardAnimationPrivate;
+
+/**
+ * XfdashboardAnimation:
+ *
+ * The #XfdashboardAnimation structure contains only private data and
+ * should be accessed using the provided API
+ */struct _XfdashboardAnimation
+{
+	/*< private >*/
+	/* Parent instance */
+	GObject								parent_instance;
+
+	/* Private structure */
+	XfdashboardAnimationPrivate			*priv;
+};
+
+/**
+ * XfdashboardAnimationClass:
+ *
+ * The #XfdashboardAnimationClass structure contains only private data
+ */
+struct _XfdashboardAnimationClass
+{
+	/*< private >*/
+	/* Parent class */
+	GObjectClass						parent_class;
+
+	/*< public >*/
+	/* Virtual functions */
+	void (*add_animation)(XfdashboardAnimation *self, ClutterActor *inActor, ClutterTransition *inTransition);
+	void (*started)(XfdashboardAnimation *self);
+	void (*stopped)(XfdashboardAnimation *self);
+};
+
+/* Public API */
+GType xfdashboard_animation_get_type(void) G_GNUC_CONST;
+
+XfdashboardAnimation* xfdashboard_animation_new(XfdashboardActor *inSender, const gchar *inSignal);
+
+const gchar* xfdashboard_animation_get_id(XfdashboardAnimation *self);
+
+/**
+ * XfdashboardAnimationDoneCallback:
+ * @inAnimation: The animation which completed
+ * @inUserData: Data passed to the function, set with xfdashboard_animation_run()
+ *
+ * A callback called when animation, started by xfdashboard_animation_run(),
+ * has completed and will be destroyed.
+ */
+typedef void (*XfdashboardAnimationDoneCallback)(XfdashboardAnimation *inAnimation, gpointer inUserData);
+
+void xfdashboard_animation_run(XfdashboardAnimation *self,
+								XfdashboardAnimationDoneCallback inCallback,
+								gpointer inUserData);
+
+G_END_DECLS
+
+#endif	/* __LIBXFDASHBOARD_ANIMATION__ */
diff --git a/libxfdashboard/application.c b/libxfdashboard/application.c
index 7c87afb..85a241f 100644
--- a/libxfdashboard/application.c
+++ b/libxfdashboard/application.c
@@ -645,6 +645,8 @@ static gint _xfdashboard_application_handle_command_line_arguments(XfdashboardAp
 										{ "images", XFDASHBOARD_DEBUG_IMAGES },
 										{ "windows", XFDASHBOARD_DEBUG_WINDOWS },
 										{ "window-tracker", XFDASHBOARD_DEBUG_WINDOWS },
+										{ "animation", XFDASHBOARD_DEBUG_ANIMATION },
+										{ "animations", XFDASHBOARD_DEBUG_ANIMATION },
 									};
 
 		/* Parse debug flags */
diff --git a/libxfdashboard/collapse-box.c b/libxfdashboard/collapse-box.c
index 2d538ea..71820c9 100644
--- a/libxfdashboard/collapse-box.c
+++ b/libxfdashboard/collapse-box.c
@@ -34,6 +34,7 @@
 #include <libxfdashboard/focus-manager.h>
 #include <libxfdashboard/utils.h>
 #include <libxfdashboard/compat.h>
+#include <libxfdashboard/animation.h>
 
 
 /* Define this class in GObject system */
@@ -55,6 +56,7 @@ struct _XfdashboardCollapseBoxPrivate
 
 	gboolean				expandedByPointer;
 	gboolean				expandedByFocus;
+	XfdashboardAnimation	*expandCollapseAnimation;
 };
 
 G_DEFINE_TYPE_WITH_CODE(XfdashboardCollapseBox,
@@ -239,6 +241,22 @@ static void _xfdashboard_collapse_box_on_focus_changed(XfdashboardCollapseBox *s
 		}
 }
 
+/* Collapse or expand animation has stopped and is done */
+static void _xfdashboard_collapse_box_animation_done(XfdashboardAnimation *inAnimation, gpointer inUserData)
+{
+	XfdashboardCollapseBox			*self;
+	XfdashboardCollapseBoxPrivate	*priv;
+
+	g_return_if_fail(XFDASHBOARD_IS_ANIMATION(inAnimation));
+	g_return_if_fail(XFDASHBOARD_IS_COLLAPSE_BOX(inUserData));
+
+	self=XFDASHBOARD_COLLAPSE_BOX(inUserData);
+	priv=self->priv;
+
+	/* Collapse or expand animation is done, so reset pointer to running animation */
+	priv->expandCollapseAnimation=NULL;
+}
+
 /* IMPLEMENTATION: Interface ClutterContainer */
 
 /* An actor was added to container */
@@ -629,6 +647,7 @@ static void xfdashboard_collapse_box_init(XfdashboardCollapseBox *self)
 	priv->focusChangedSignalID=0;
 	priv->expandedByPointer=FALSE;
 	priv->expandedByFocus=FALSE;
+	priv->expandCollapseAnimation=NULL;
 
 	/* Set up actor */
 	clutter_actor_set_reactive(CLUTTER_ACTOR(self), TRUE);
@@ -666,6 +685,7 @@ gboolean xfdashboard_collapse_box_get_collapsed(XfdashboardCollapseBox *self)
 void xfdashboard_collapse_box_set_collapsed(XfdashboardCollapseBox *self, gboolean inCollapsed)
 {
 	XfdashboardCollapseBoxPrivate	*priv;
+	XfdashboardAnimation			*animation;
 
 	g_return_if_fail(XFDASHBOARD_IS_COLLAPSE_BOX(self));
 
@@ -674,6 +694,25 @@ void xfdashboard_collapse_box_set_collapsed(XfdashboardCollapseBox *self, gboole
 	/* Only set value if it changes */
 	if(inCollapsed!=priv->isCollapsed)
 	{
+		/* Create animation for new expand/collapse state before stopping
+		 * any possibly running animation for the old expand/collapse state,
+		 * so that the new animation will take over any property value in
+		 * the middle of currently running animation. If we stop the animation
+		 * beforehand then the new animation would take the initial value in
+		 * a state assuming that the current animation has completed.
+		 */
+		animation=xfdashboard_animation_new(XFDASHBOARD_ACTOR(self), inCollapsed ? "collapse" : "expand");
+
+		/* Expand/collapse state changed, so stop any running animation */
+		if(priv->expandCollapseAnimation)
+		{
+			g_object_unref(priv->expandCollapseAnimation);
+			priv->expandCollapseAnimation=NULL;
+		}
+
+		/* Store new animation */
+		priv->expandCollapseAnimation=animation;
+
 		/* Set new value */
 		priv->isCollapsed=inCollapsed;
 		clutter_actor_queue_relayout(CLUTTER_ACTOR(self));
@@ -683,6 +722,9 @@ void xfdashboard_collapse_box_set_collapsed(XfdashboardCollapseBox *self, gboole
 
 		/* Emit signal */
 		g_signal_emit(self, XfdashboardCollapseBoxSignals[SIGNAL_COLLAPSED_CHANGED], 0, priv->isCollapsed);
+
+		/* Start animation */
+		xfdashboard_animation_run(priv->expandCollapseAnimation, _xfdashboard_collapse_box_animation_done, self);
 	}
 }
 
diff --git a/libxfdashboard/debug.h b/libxfdashboard/debug.h
index a43302b..7510016 100644
--- a/libxfdashboard/debug.h
+++ b/libxfdashboard/debug.h
@@ -44,6 +44,7 @@ G_BEGIN_DECLS
  * @XFDASHBOARD_DEBUG_IMAGES: Images related debug message (image cache etc.)
  * @XFDASHBOARD_DEBUG_WINDOWS: Windows related debug message (window tracker, workspaces, windows, monitors etc.)
  * @XFDASHBOARD_DEBUG_PLUGINS: Plug-ins related debug message (plugin manager and plugin base class)
+ * @XFDASHBOARD_DEBUG_ANIMATION: Animation related debug message
  *
  * Debug categories
  */
@@ -57,6 +58,7 @@ typedef enum /*< skip,flags,prefix=XFDASHBOARD_DEBUG >*/
 	XFDASHBOARD_DEBUG_IMAGES		= 1 << 5,
 	XFDASHBOARD_DEBUG_WINDOWS		= 1 << 6,
 	XFDASHBOARD_DEBUG_PLUGINS		= 1 << 7,
+	XFDASHBOARD_DEBUG_ANIMATION		= 1 << 8
 } XfdashboardDebugFlags;
 
 #ifdef XFDASHBOARD_ENABLE_DEBUG
diff --git a/libxfdashboard/theme-animation.c b/libxfdashboard/theme-animation.c
new file mode 100644
index 0000000..b90537b
--- /dev/null
+++ b/libxfdashboard/theme-animation.c
@@ -0,0 +1,998 @@
+/*
+ * theme-animation: A theme used for building animations by XML files
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libxfdashboard/theme-animation.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <libxfdashboard/transition-group.h>
+#include <libxfdashboard/stylable.h>
+#include <libxfdashboard/enums.h>
+#include <libxfdashboard/utils.h>
+#include <libxfdashboard/compat.h>
+#include <libxfdashboard/debug.h>
+
+
+/* Define this class in GObject system */
+struct _XfdashboardThemeAnimationPrivate
+{
+	/* Instance related */
+	GSList			*specs;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardThemeAnimation,
+							xfdashboard_theme_animation,
+							G_TYPE_OBJECT)
+
+
+/* IMPLEMENTATION: Private variables and methods */
+enum
+{
+	XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER=0,
+	XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_STAGE=1
+};
+
+typedef struct _XfdashboardThemeAnimationSpec				XfdashboardThemeAnimationSpec;
+struct _XfdashboardThemeAnimationSpec
+{
+	gint					refCount;
+	gchar					*id;
+	XfdashboardCssSelector	*senderSelector;
+	gchar					*signal;
+	GSList					*targets;
+};
+
+typedef struct _XfdashboardThemeAnimationTargets			XfdashboardThemeAnimationTargets;
+struct _XfdashboardThemeAnimationTargets
+{
+	gint					refCount;
+	XfdashboardCssSelector	*targetSelector;
+	guint					origin;
+	ClutterTimeline			*timeline;
+	GSList					*properties;
+};
+
+typedef struct _XfdashboardThemeAnimationTargetsProperty	XfdashboardThemeAnimationTargetsProperty;
+struct _XfdashboardThemeAnimationTargetsProperty
+{
+	gint					refCount;
+	gchar					*name;
+	GValue					from;
+	GValue					to;
+};
+
+/* Create, destroy, ref and unref animation targets data */
+static XfdashboardThemeAnimationTargetsProperty* _xfdashboard_theme_animation_targets_property_new(const gchar *inPropertyName,
+																									const gchar *inPropertyFrom,
+																									const gchar *inPropertyTo)
+{
+	XfdashboardThemeAnimationTargetsProperty		*data;
+
+	g_return_val_if_fail(inPropertyName && *inPropertyName, NULL);
+	g_return_val_if_fail(!inPropertyFrom || *inPropertyFrom, NULL);
+	g_return_val_if_fail(!inPropertyTo || *inPropertyTo, NULL);
+
+	/* Create animation targets property data */
+	data=g_new0(XfdashboardThemeAnimationTargetsProperty, 1);
+	if(!data)
+	{
+		g_critical(_("Cannot allocate memory for animation targets property '%s'"), inPropertyName);
+		return(NULL);
+	}
+
+	data->refCount=1;
+	data->name=g_strdup(inPropertyName);
+
+	if(inPropertyFrom)
+	{
+		g_value_init(&data->from, G_TYPE_STRING);
+		g_value_set_string(&data->from, inPropertyFrom);
+	}
+	
+	if(inPropertyTo)
+	{
+		g_value_init(&data->to, G_TYPE_STRING);
+		g_value_set_string(&data->to, inPropertyTo);
+	}
+
+	return(data);
+}
+
+static void _xfdashboard_theme_animation_targets_property_free(XfdashboardThemeAnimationTargetsProperty *inData)
+{
+	g_return_if_fail(inData);
+
+	/* Free property data */
+	if(inData->name) g_free(inData->name);
+	g_value_unset(&inData->from);
+	g_value_unset(&inData->to);
+	g_free(inData);
+}
+
+static XfdashboardThemeAnimationTargetsProperty* _xfdashboard_theme_animation_targets_property_ref(XfdashboardThemeAnimationTargetsProperty *inData)
+{
+	g_return_val_if_fail(inData, NULL);
+
+	inData->refCount++;
+	return(inData);
+}
+
+static void _xfdashboard_theme_animation_targets_property_unref(XfdashboardThemeAnimationTargetsProperty *inData)
+{
+	g_return_if_fail(inData);
+
+	if(inData->refCount==1) _xfdashboard_theme_animation_targets_property_free(inData);
+		else inData->refCount--;
+}
+
+/* Create, destroy, ref and unref animation targets data */
+static XfdashboardThemeAnimationTargets* _xfdashboard_theme_animation_targets_new(XfdashboardCssSelector *inTargetSelector,
+																					gint inOrigin,
+																					ClutterTimeline *inTimelineSource)
+{
+	XfdashboardThemeAnimationTargets		*data;
+
+	g_return_val_if_fail(!inTargetSelector || XFDASHBOARD_IS_CSS_SELECTOR(inTargetSelector), NULL);
+	g_return_val_if_fail(inOrigin>=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER && inOrigin<=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_STAGE, NULL);
+	g_return_val_if_fail(CLUTTER_IS_TIMELINE(inTimelineSource), NULL);
+
+	/* Create animation targets data */
+	data=g_new0(XfdashboardThemeAnimationTargets, 1);
+	if(!data)
+	{
+		gchar							*selector;
+
+		selector=xfdashboard_css_selector_to_string(inTargetSelector);
+		g_critical(_("Cannot allocate memory for animation targets data with selector '%s'"), selector);
+		g_free(selector);
+
+		return(NULL);
+	}
+
+	data->refCount=1;
+	data->targetSelector=(inTargetSelector ? g_object_ref(inTargetSelector) : NULL);
+	data->origin=inOrigin;
+	data->timeline=g_object_ref(inTimelineSource);
+	data->properties=NULL;
+
+	return(data);
+}
+
+static void _xfdashboard_theme_animation_targets_free(XfdashboardThemeAnimationTargets *inData)
+{
+	g_return_if_fail(inData);
+
+#ifdef DEBUG
+	if(inData->refCount>1)
+	{
+		g_critical(_("Freeing animation targets data at %p with a reference counter of %d greater than one"),
+					inData,
+					inData->refCount);
+	}
+#endif
+
+	/* Release allocated resources */
+	if(inData->targetSelector) g_object_unref(inData->targetSelector);
+	if(inData->timeline) g_object_unref(inData->timeline);
+	if(inData->properties) g_slist_free_full(inData->properties, (GDestroyNotify)_xfdashboard_theme_animation_targets_property_unref);
+	g_free(inData);
+}
+
+static XfdashboardThemeAnimationTargets* _xfdashboard_theme_animation_targets_ref(XfdashboardThemeAnimationTargets *inData)
+{
+	g_return_val_if_fail(inData, NULL);
+
+	inData->refCount++;
+	return(inData);
+}
+
+static void _xfdashboard_theme_animation_targets_unref(XfdashboardThemeAnimationTargets *inData)
+{
+	g_return_if_fail(inData);
+
+	if(inData->refCount==1) _xfdashboard_theme_animation_targets_free(inData);
+		else inData->refCount--;
+}
+
+/* Add property name and from-to values to animation targets data */
+static void _xfdashboard_theme_animation_targets_add_property(XfdashboardThemeAnimationTargets *inData,
+																XfdashboardThemeAnimationTargetsProperty *inProperty)
+{
+	g_return_if_fail(inData);
+	g_return_if_fail(inProperty);
+
+	/* Add property to animation targets data */
+	inData->properties=g_slist_prepend(inData->properties, _xfdashboard_theme_animation_targets_property_ref(inProperty));
+}
+
+
+/* Create, destroy, ref and unref animation specification data */
+static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_spec_new(const gchar *inID,
+																			XfdashboardCssSelector *inSenderSelector,
+																			const gchar *inSignal)
+{
+	XfdashboardThemeAnimationSpec		*data;
+
+	g_return_val_if_fail(inID && *inID, NULL);
+	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(inSenderSelector), NULL);
+	g_return_val_if_fail(inSignal && *inSignal, NULL);
+
+	/* Create animation specification data */
+	data=g_new0(XfdashboardThemeAnimationSpec, 1);
+	if(!data)
+	{
+		gchar							*selector;
+
+		selector=xfdashboard_css_selector_to_string(inSenderSelector);
+		g_critical(_("Cannot allocate memory for animation specification data with sender '%s' and signal '%s'"),
+					selector,
+					inSignal);
+		g_free(selector);
+
+		return(NULL);
+	}
+
+	data->refCount=1;
+	data->id=g_strdup(inID);
+	data->senderSelector=g_object_ref(inSenderSelector);
+	data->signal=g_strdup(inSignal);
+	data->targets=NULL;
+
+	return(data);
+}
+
+static void _xfdashboard_theme_animation_spec_free(XfdashboardThemeAnimationSpec *inData)
+{
+	g_return_if_fail(inData);
+
+#ifdef DEBUG
+	if(inData->refCount>1)
+	{
+		g_critical(_("Freeing animation specification data at %p with a reference counter of %d greater than one"),
+					inData,
+					inData->refCount);
+	}
+#endif
+
+	/* Release allocated resources */
+	if(inData->id) g_free(inData->id);
+	if(inData->senderSelector) g_object_unref(inData->senderSelector);
+	if(inData->signal) g_free(inData->signal);
+	if(inData->targets) g_slist_free_full(inData->targets, (GDestroyNotify)_xfdashboard_theme_animation_targets_unref);
+	g_free(inData);
+}
+
+static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_spec_ref(XfdashboardThemeAnimationSpec *inData)
+{
+	g_return_val_if_fail(inData, NULL);
+
+	inData->refCount++;
+	return(inData);
+}
+
+static void _xfdashboard_theme_animation_spec_unref(XfdashboardThemeAnimationSpec *inData)
+{
+	g_return_if_fail(inData);
+
+	if(inData->refCount==1) _xfdashboard_theme_animation_spec_free(inData);
+		else inData->refCount--;
+}
+
+/* Add a animation target to animation specification */
+static void _xfdashboard_theme_animation_spec_add_targets(XfdashboardThemeAnimationSpec *inData,
+															XfdashboardThemeAnimationTargets *inTargets)
+{
+	g_return_if_fail(inData);
+	g_return_if_fail(inTargets);
+
+	/* Add target to specification */
+	inData->targets=g_slist_prepend(inData->targets, _xfdashboard_theme_animation_targets_ref(inTargets));
+}
+
+/* Find best matching animation for sender and signal */
+static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_find_animation_spec(XfdashboardThemeAnimation *self,
+																						XfdashboardStylable *inSender,
+																						const gchar *inSignal)
+{
+	XfdashboardThemeAnimationPrivate			*priv;
+	GSList										*iter;
+	gint										score;
+	XfdashboardThemeAnimationSpec				*spec;
+	gint										bestScore;
+	XfdashboardThemeAnimationSpec				*bestAnimation;
+
+	g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
+	g_return_val_if_fail(XFDASHBOARD_IS_STYLABLE(inSender), NULL);
+	g_return_val_if_fail(inSignal && *inSignal, NULL);
+
+	priv=self->priv;
+	bestScore=0;
+	bestAnimation=NULL;
+
+	/* Iterate through all animation specification and get its score against sender.
+	 * If the iterated specification gets a higher score than the previous one,
+	 * remember this specification for return value.
+	 */
+	for(iter=priv->specs; iter; iter=g_slist_next(iter))
+	{
+		/* Get currently iterated specification */
+		spec=(XfdashboardThemeAnimationSpec*)iter->data;
+		if(!spec) continue;
+
+		/* Skip animation specification if its signal definition does not match
+		 * the emitted signal.
+		 */
+		if(g_strcmp0(spec->signal, inSignal)!=0) continue;
+
+		/* Get score of currently iterated specification against sender.
+		 * Skip this iterated specification if score is zero or lower.
+		 */
+		score=xfdashboard_css_selector_score(spec->senderSelector, inSender);
+		if(score<=0) continue;
+
+		/* If score is higher than the previous best matching one, then release
+		 * old specificationr remembered and ref new one.
+		 */
+		if(score>bestScore)
+		{
+			/* Release old remembered specification */
+			if(bestAnimation) _xfdashboard_theme_animation_spec_unref(bestAnimation);
+
+			/* Remember new one and take a reference */
+			bestScore=score;
+			bestAnimation=_xfdashboard_theme_animation_spec_ref(spec);
+		}
+	}
+
+	/* Return best matching animation specification found */
+	return(bestAnimation);
+}
+
+/* Find actors matching an animation target data */
+static gboolean _xfdashboard_theme_animation_find_actors_for_animation_targets_traverse_callback(ClutterActor *inActor, gpointer inUserData)
+{
+	GSList		**actors=(GSList**)inUserData;
+
+	*actors=g_slist_prepend(*actors, inActor);
+
+	return(XFDASHBOARD_TRAVERSAL_CONTINUE);
+}
+
+static GSList* _xfdashboard_theme_animation_find_actors_for_animation_targets(XfdashboardThemeAnimation *self,
+																				XfdashboardThemeAnimationTargets *inTargetSpec,
+																				ClutterActor *inSender)
+{
+	ClutterActor								*rootNode;
+	GSList										*actors;
+
+	g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
+	g_return_val_if_fail(inTargetSpec, NULL);
+	g_return_val_if_fail(CLUTTER_IS_ACTOR(inSender), NULL);
+
+
+	/* Depending on origin at animation target data select root node to start
+	 * traversal and collecting matching actors.
+	 */
+	rootNode=NULL;
+	if(inTargetSpec->origin==XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER)
+	{
+		rootNode=inSender;
+	}
+
+	/* Traverse through actors beginning at the root node and collect each actor
+	 * matching the target selector but only if a target selector is set. If
+	 * target selector is NULL then set up a single-item list containing only
+	 * the sender actor as list of actors found.
+	 */
+	actors=NULL;
+	if(inTargetSpec->targetSelector)
+	{
+		xfdashboard_traverse_actor(rootNode,
+									inTargetSpec->targetSelector,
+									_xfdashboard_theme_animation_find_actors_for_animation_targets_traverse_callback,
+									&actors);
+	}
+		else
+		{
+			actors=g_slist_prepend(actors, inSender);
+		}
+
+	/* Correct order in list */
+	actors=g_slist_reverse(actors);
+
+	/* Return list of actors found */
+	return(actors);
+}
+
+
+/* IMPLEMENTATION: GObject */
+
+/* Dispose this object */
+static void _xfdashboard_theme_animation_dispose(GObject *inObject)
+{
+	XfdashboardThemeAnimation				*self=XFDASHBOARD_THEME_ANIMATION(inObject);
+	XfdashboardThemeAnimationPrivate		*priv=self->priv;
+
+	/* Release allocated resources */
+	if(priv->specs)
+	{
+		g_slist_free_full(priv->specs, (GDestroyNotify)_xfdashboard_theme_animation_spec_unref);
+		priv->specs=NULL;
+	}
+
+	/* Call parent's class dispose method */
+	G_OBJECT_CLASS(xfdashboard_theme_animation_parent_class)->dispose(inObject);
+}
+
+/* Class initialization
+ * Override functions in parent classes and define properties
+ * and signals
+ */
+void xfdashboard_theme_animation_class_init(XfdashboardThemeAnimationClass *klass)
+{
+	GObjectClass		*gobjectClass=G_OBJECT_CLASS(klass);
+
+	/* Override functions */
+	gobjectClass->dispose=_xfdashboard_theme_animation_dispose;
+}
+
+/* Object initialization
+ * Create private structure and set up default values
+ */
+void xfdashboard_theme_animation_init(XfdashboardThemeAnimation *self)
+{
+	XfdashboardThemeAnimationPrivate		*priv;
+
+	priv=self->priv=xfdashboard_theme_animation_get_instance_private(self);
+
+	/* Set default values */
+	priv->specs=NULL;
+}
+
+/* IMPLEMENTATION: Errors */
+
+GQuark xfdashboard_theme_animation_error_quark(void)
+{
+	return(g_quark_from_static_string("xfdashboard-theme-animation-error-quark"));
+}
+
+/* IMPLEMENTATION: Public API */
+
+/* Create new instance */
+XfdashboardThemeAnimation* xfdashboard_theme_animation_new(void)
+{
+	return(XFDASHBOARD_THEME_ANIMATION(g_object_new(XFDASHBOARD_TYPE_THEME_ANIMATION, NULL)));
+}
+
+
+/* Load a XML file into theme */
+gboolean xfdashboard_theme_animation_add_file(XfdashboardThemeAnimation *self,
+												const gchar *inPath,
+												GError **outError)
+{
+	XfdashboardThemeAnimationPrivate			*priv;
+	XfdashboardThemeAnimationSpec				*spec;
+	gchar										*id;
+	gchar										*sender;
+	gchar										*signal;
+	XfdashboardCssSelector						*senderSelector;
+	ClutterTimeline								*timeline;
+	gint										timelineDelay;
+	gint										timelineDuration;
+	guint										timelineMode;
+	gint										timelineRepeat;
+	XfdashboardThemeAnimationTargets			*targets;
+	XfdashboardCssSelector						*targetsSelector;
+	gint										targetsOrigin;
+	XfdashboardThemeAnimationTargetsProperty	*property;
+	gchar										*propertyName;
+	gchar										*propertyFrom;
+	gchar										*propertyTo;
+
+	g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), FALSE);
+	g_return_val_if_fail(inPath!=NULL && *inPath!=0, FALSE);
+	g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
+
+	priv=self->priv;
+
+	/* TODO: Replace with real loading code ;) */
+
+	/* <trigger id="expand-workspace" sender="#workspace-selector-collapse-box" signal="expand"> */
+	id="expand-workspace";
+	sender="XfdashboardCollapseBox";
+	signal="expand";
+
+	senderSelector=xfdashboard_css_selector_new_from_string(sender);
+	spec=_xfdashboard_theme_animation_spec_new(id, senderSelector, signal);
+	g_object_unref(senderSelector);
+
+	/*   <timeline delay="0" duration="500" mode=ease-out-cubic" repeat="0" (optional, default=0) > */
+	timelineDelay=0;
+	timelineDuration=500;
+	timelineMode=CLUTTER_EASE_OUT_CUBIC;
+	timelineRepeat=0;
+
+	timeline=clutter_timeline_new(timelineDuration);
+	clutter_timeline_set_delay(timeline, timelineDelay);
+	clutter_timeline_set_progress_mode(timeline, timelineMode);
+	clutter_timeline_set_repeat_count(timeline, timelineRepeat);
+
+	/*     <apply to="" (optional, default="" -> sender) origin="sender" (optional, default="sender") > */
+	targetsSelector=NULL;
+	targetsOrigin=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER;
+
+	targets=_xfdashboard_theme_animation_targets_new(targetsSelector, targetsOrigin, timeline);
+
+	/*       <property name="collapse-fraction" from="" (optional, default="") to="" (optional, default="")> */
+	propertyName="width";
+	propertyFrom=NULL;
+	propertyTo=NULL;
+
+	property=_xfdashboard_theme_animation_targets_property_new(propertyName, propertyFrom, propertyTo);
+
+	/*       </property> */
+	_xfdashboard_theme_animation_targets_add_property(targets, property);
+	_xfdashboard_theme_animation_targets_property_unref(property);
+	property=NULL;
+
+	/*     </apply> */
+	_xfdashboard_theme_animation_spec_add_targets(spec, targets);
+	_xfdashboard_theme_animation_targets_unref(targets);
+	targets=NULL;
+
+	/*   </timeline> */
+	g_object_unref(timeline);
+	timeline=NULL;
+
+	/* </trigger> */
+	priv->specs=g_slist_prepend(priv->specs, spec);
+
+
+
+	/* <trigger id="collapse-workspace" sender="#workspace-selector-collapse-box" signal="collapse"> */
+	id="collapse-workspace";
+	sender="XfdashboardCollapseBox";
+	signal="collapse";
+
+	senderSelector=xfdashboard_css_selector_new_from_string(sender);
+	spec=_xfdashboard_theme_animation_spec_new(id, senderSelector, signal);
+	g_object_unref(senderSelector);
+
+	/*   <timeline delay="0" duration="500" mode=ease-in-cubic" repeat="0" (optional, default=0) > */
+	timelineDelay=0;
+	timelineDuration=500;
+	timelineMode=CLUTTER_EASE_OUT_CUBIC;
+	timelineRepeat=0;
+
+	timeline=clutter_timeline_new(timelineDuration);
+	clutter_timeline_set_delay(timeline, timelineDelay);
+	clutter_timeline_set_progress_mode(timeline, timelineMode);
+	clutter_timeline_set_repeat_count(timeline, timelineRepeat);
+
+	/*     <apply to="" (optional, default="" -> sender) origin="sender" (optional, default="sender") > */
+	targetsSelector=NULL;
+	targetsOrigin=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER;
+
+	targets=_xfdashboard_theme_animation_targets_new(targetsSelector, targetsOrigin, timeline);
+
+	/*       <property name="collapse-fraction" from="" (optional, default="") to="" (optional, default="")> */
+	propertyName="width";
+	propertyFrom=NULL;
+	propertyTo=NULL;
+
+	property=_xfdashboard_theme_animation_targets_property_new(propertyName, propertyFrom, propertyTo);
+
+	/*       </property> */
+	_xfdashboard_theme_animation_targets_add_property(targets, property);
+	_xfdashboard_theme_animation_targets_property_unref(property);
+	property=NULL;
+
+	/*     </apply> */
+	_xfdashboard_theme_animation_spec_add_targets(spec, targets);
+	_xfdashboard_theme_animation_targets_unref(targets);
+	targets=NULL;
+
+	/*   </timeline> */
+	g_object_unref(timeline);
+	timeline=NULL;
+
+	/* </trigger> */
+	priv->specs=g_slist_prepend(priv->specs, spec);
+
+
+	/* TODO: Just for testing animations */
+	g_message("Loading animations done!");
+
+	/* If we get here loading and parsing XML file was successful
+	 * so return TRUE here
+	 */
+	return(TRUE);
+}
+
+/* Build requested animation */
+XfdashboardAnimation* xfdashboard_theme_animation_create(XfdashboardThemeAnimation *self,
+															XfdashboardActor *inSender,
+															const gchar *inSignal)
+{
+	XfdashboardThemeAnimationSpec							*spec;
+	XfdashboardAnimation									*animation;
+	XfdashboardAnimationClass								*animationClass;
+	GSList													*iterTargets;
+	gint													counterTargets;
+#ifdef DEBUG
+	gboolean												doDebug=TRUE;
+#endif
+
+	g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
+	g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
+	g_return_val_if_fail(inSignal && *inSignal, NULL);
+
+	animation=NULL;
+
+	/* Create empty animation */
+	animation=g_object_new(XFDASHBOARD_TYPE_ANIMATION,
+							NULL);
+	if(!animation)
+	{
+		g_critical(_("Cannot allocate memory for animation of sender '%s' and signal '%s'"),
+					G_OBJECT_TYPE_NAME(inSender),
+					inSignal);
+		return(NULL);
+	}
+
+	/* Get best matching animation specification for sender and signal.
+	 * If no matching animation specification is found, return the empty one.
+	 */
+	spec=_xfdashboard_theme_animation_find_animation_spec(self, XFDASHBOARD_STYLABLE(inSender), inSignal);
+	if(!spec)
+	{
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Could not find an animation specification for sender '%s' and signal '%s'",
+							G_OBJECT_TYPE_NAME(inSender),
+							inSignal);
+
+		/* Return NULL as no matching animation specification was found */
+		return(animation);
+	}
+
+	/* Create new animation for animation specification */
+	g_object_unref(animation);
+
+	animation=g_object_new(XFDASHBOARD_TYPE_ANIMATION,
+							"id", spec->id,
+							NULL);
+	if(!animation)
+	{
+		g_critical(_("Cannot allocate memory for animation of sender '%s' and signal '%s'"),
+					G_OBJECT_TYPE_NAME(inSender),
+					inSignal);
+		return(NULL);
+	}
+
+	animationClass=XFDASHBOARD_ANIMATION_GET_CLASS(animation);
+	if(!animationClass->add_animation)
+	{
+		g_warning(_("Will not be able to add animations to actors as object of type %s does not implement required virtual function XfdashboardAnimation::%s"), \
+					G_OBJECT_TYPE_NAME(self), \
+					"add_animation");
+	}
+
+	XFDASHBOARD_DEBUG(self, ANIMATION,
+						"Found animation specification '%s' for sender '%s' and signal '%s' with %d targets",
+						spec->id,
+						G_OBJECT_TYPE_NAME(inSender),
+						inSignal,
+						g_slist_length(spec->targets));
+
+	/* Iterate through animation targets of animation specification and create
+	 * property transition for each target and property found.
+	 */
+	for(counterTargets=0, iterTargets=spec->targets; iterTargets; iterTargets=g_slist_next(iterTargets), counterTargets++)
+	{
+		XfdashboardThemeAnimationTargets					*targets;
+		GSList												*actors;
+		GSList												*iterActors;
+		gint												counterActors;
+
+		/* Get currently iterate animation targets */
+		targets=(XfdashboardThemeAnimationTargets*)iterTargets->data;
+		if(!targets) continue;
+
+		/* Find targets to apply property transitions to */
+		actors=_xfdashboard_theme_animation_find_actors_for_animation_targets(self, targets, CLUTTER_ACTOR(inSender));
+		if(!actors) continue;
+		XFDASHBOARD_DEBUG(self, ANIMATION,
+							"Target #%d of animation specification '%s' applies to %d actors",
+							counterTargets,
+							spec->id,
+							g_slist_length(actors));
+
+		/* Iterate through actor, create a transition group to collect
+		 * property transitions at, create a property transition for each
+		 * property specified in animation target, determine "from" value
+		 * where missing, add property transition to transition group and
+		 * finally add transition group with currently iterated actor to
+		 * animation object.
+		 */
+		for(counterActors=0, iterActors=actors; iterActors; iterActors=g_slist_next(iterActors), counterActors++)
+		{
+			GSList											*iterProperties;
+			ClutterActor									*actor;
+			ClutterTransition								*transitionGroup;
+			int												counterProperties;
+
+			/* Get actor */
+			actor=(ClutterActor*)iterActors->data;
+			if(!actor) continue;
+
+			/* Create transition group to collect property transitions at */
+			transitionGroup=xfdashboard_transition_group_new();
+			if(!transitionGroup)
+			{
+				g_critical(_("Cannot allocate memory for transition group of animation specification '%s'"),
+							spec->id);
+				
+				/* Release allocated resources */
+				g_slist_free(actors);
+				g_object_unref(animation);
+
+				return(NULL);
+			}
+
+			/* Clone timeline configuration from animation target */
+			clutter_timeline_set_duration(CLUTTER_TIMELINE(transitionGroup), clutter_timeline_get_duration(targets->timeline));
+			clutter_timeline_set_delay(CLUTTER_TIMELINE(transitionGroup), clutter_timeline_get_delay(targets->timeline));
+			clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transitionGroup), clutter_timeline_get_progress_mode(targets->timeline));
+			clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transitionGroup), clutter_timeline_get_repeat_count(targets->timeline));
+
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Created transition group at %p for %d properties for target #%d and actor #%d (%s@%p) of animation specification '%s'",
+								transitionGroup,
+								g_slist_length(targets->properties),
+								counterTargets,
+								counterActors,
+								G_OBJECT_TYPE_NAME(actor),
+								actor,
+								spec->id);
+
+			/* Iterate through properties and create a property transition
+			 * with cloned timeline from animation target. Determine "from"
+			 * value if missing in animation targets property specification
+			 * and add the property transition to transition group.
+			 */
+			for(counterProperties=0, iterProperties=targets->properties; iterProperties; iterProperties=g_slist_next(iterProperties), counterProperties++)
+			{
+				XfdashboardThemeAnimationTargetsProperty	*propertyTargetSpec;
+				GParamSpec									*propertySpec;
+				GValue										fromValue=G_VALUE_INIT;
+				GValue										toValue=G_VALUE_INIT;
+
+				/* Get target's property data to animate */
+				propertyTargetSpec=(XfdashboardThemeAnimationTargetsProperty*)iterProperties->data;
+				if(!propertyTargetSpec) continue;
+
+				/* Check if actor has property to animate */
+				propertySpec=g_object_class_find_property(G_OBJECT_GET_CLASS(actor), propertyTargetSpec->name);
+				if(!propertySpec)
+				{
+					g_warning(_("Cannot create animation for non-existing property '%s' at actor of type '%s'"),
+								propertyTargetSpec->name,
+								G_OBJECT_TYPE_NAME(actor));
+
+					/* Skip this property as it does not exist at actor */
+					continue;
+				}
+
+				/* If no "from" value is set at target's property data, get
+				 * current value. Otherwise convert "from" value from target's
+				 * property data to expected type of property.
+				 */
+				if(G_VALUE_TYPE(&propertyTargetSpec->from)!=G_TYPE_INVALID)
+				{
+					g_value_init(&fromValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
+					if(!g_value_transform(&propertyTargetSpec->from, &fromValue))
+					{
+						g_warning(_("Could not transform 'from'-value of '%s' for property '%s' to type %s of class %s"),
+									g_value_get_string(&propertyTargetSpec->from),
+									propertyTargetSpec->name,
+									g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
+									G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)));
+
+						/* Unset "from" value to skip it, means it will no transition will be
+						 * create and it will not be added to transition group.
+						 */
+						g_value_unset(&fromValue);
+					}
+#ifdef DEBUG
+					if(doDebug)
+					{
+						gchar	*valueString;
+
+						valueString=g_strdup_value_contents(&propertyTargetSpec->from);
+						XFDASHBOARD_DEBUG(self, ANIMATION,
+											"%s 'from'-value %s of type %s for property '%s' to type %s of class %s for target #%d and actor #%d (%s@%p) of animation specification '%s'",
+											(G_VALUE_TYPE(&fromValue)!=G_TYPE_INVALID) ? "Converted" : "Could not convert",
+											valueString,
+											G_VALUE_TYPE_NAME(&propertyTargetSpec->from),
+											propertyTargetSpec->name,
+											g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
+											G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)),
+											counterTargets,
+											counterActors,
+											G_OBJECT_TYPE_NAME(actor),
+											actor,
+											spec->id);
+						g_free(valueString);
+					}
+#endif
+				}
+					else
+					{
+						g_value_init(&fromValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
+						g_object_get_property(G_OBJECT(actor),
+												propertyTargetSpec->name,
+												&fromValue);
+#ifdef DEBUG
+						if(doDebug)
+						{
+							gchar	*valueString;
+
+							valueString=g_strdup_value_contents(&fromValue);
+							XFDASHBOARD_DEBUG(self, ANIMATION,
+												"Fetching current 'from'-value %s for property '%s' from target #%d and actor #%d (%s@%p) of animation specification '%s' as no 'from' value was specified",
+												valueString,
+												propertyTargetSpec->name,
+												counterTargets,
+												counterActors,
+												G_OBJECT_TYPE_NAME(actor),
+												actor,
+												spec->id);
+							g_free(valueString);
+						}
+#endif
+					}
+
+				/* If "to" value is set at target's property data, convert it
+				 * from target's property data to expected type of property.
+				 */
+				if(G_VALUE_TYPE(&propertyTargetSpec->to)!=G_TYPE_INVALID)
+				{
+					g_value_init(&toValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
+					if(!g_value_transform(&propertyTargetSpec->to, &toValue))
+					{
+						g_warning(_("Could not transform 'to'-value of '%s' for property '%s' to type %s of class %s"),
+									g_value_get_string(&propertyTargetSpec->to),
+									propertyTargetSpec->name,
+									g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
+									G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)));
+
+						/* Unset "to" value to prevent setting it at transition.
+						 * The animation will set a value when starting the
+						 * animation.
+						 */
+						g_value_unset(&toValue);
+					}
+#ifdef DEBUG
+					if(doDebug)
+					{
+						gchar	*valueString;
+
+						valueString=g_strdup_value_contents(&propertyTargetSpec->to);
+						XFDASHBOARD_DEBUG(self, ANIMATION,
+											"%s 'to'-value %s of type %s for property '%s' to type %s of class %s for target #%d and actor #%d (%s@%p) of animation specification '%s'",
+											(G_VALUE_TYPE(&toValue)!=G_TYPE_INVALID) ? "Converted" : "Could not convert",
+											valueString,
+											G_VALUE_TYPE_NAME(&propertyTargetSpec->to),
+											propertyTargetSpec->name,
+											g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
+											G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)),
+											counterTargets,
+											counterActors,
+											G_OBJECT_TYPE_NAME(actor),
+											actor,
+											spec->id);
+						g_free(valueString);
+					}
+#endif
+				}
+
+				/* Create property transition for property with cloned timeline
+				 * and add this new transition to transition group if from value
+				 * is not invalid.
+				 */
+				if(G_VALUE_TYPE(&fromValue)!=G_TYPE_INVALID)
+				{
+					ClutterTransition						*propertyTransition;
+
+					/* Create property transition */
+					propertyTransition=clutter_property_transition_new(propertyTargetSpec->name);
+					if(!propertyTransition)
+					{
+						g_critical(_("Cannot allocate memory for transition of property '%s' of animation specification '%s'"),
+									propertyTargetSpec->name,
+									spec->id);
+
+						/* Release allocated resources */
+						g_value_unset(&fromValue);
+						g_value_unset(&toValue);
+
+						/* Skip this property transition */
+						continue;
+					}
+
+					/* Set "from" value */
+					clutter_transition_set_from_value(propertyTransition, &fromValue);
+
+					/* Set "to" value if valid */
+					if(G_VALUE_TYPE(&toValue)!=G_TYPE_INVALID)
+					{
+						clutter_transition_set_to_value(propertyTransition, &toValue);
+					}
+
+					/* Add property transition to transition group */
+					xfdashboard_transition_group_add_transition(XFDASHBOARD_TRANSITION_GROUP(transitionGroup), propertyTransition);
+
+					/* Release allocated resources */
+					g_object_unref(propertyTransition);
+					XFDASHBOARD_DEBUG(self, ANIMATION,
+										"Created transition for property '%s' at target #%d and actor #%d (%s@%p) of animation specification '%s'",
+										propertyTargetSpec->name,
+										counterTargets,
+										counterActors,
+										G_OBJECT_TYPE_NAME(actor),
+										actor,
+										spec->id);
+				}
+
+				/* Release allocated resources */
+				g_value_unset(&fromValue);
+				g_value_unset(&toValue);
+			}
+
+			/* Add transition group with collected property transitions
+			 * to actor.
+			 */
+			if(animationClass->add_animation)
+			{
+				animationClass->add_animation(animation, actor, transitionGroup);
+			}
+
+			/* Release allocated resources */
+			g_object_unref(transitionGroup);
+		}
+
+		/* Release allocated resources */
+		g_slist_free(actors);
+	}
+
+	/* Release allocated resources */
+	if(spec) _xfdashboard_theme_animation_spec_unref(spec);
+
+	return(animation);
+}
diff --git a/libxfdashboard/theme-animation.h b/libxfdashboard/theme-animation.h
new file mode 100644
index 0000000..f838035
--- /dev/null
+++ b/libxfdashboard/theme-animation.h
@@ -0,0 +1,108 @@
+/*
+ * theme-animation: A theme used for building animations by XML files
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+#ifndef __LIBXFDASHBOARD_THEME_ANIMATION__
+#define __LIBXFDASHBOARD_THEME_ANIMATION__
+
+#if !defined(__LIBXFDASHBOARD_H_INSIDE__) && !defined(LIBXFDASHBOARD_COMPILATION)
+#error "Only <libxfdashboard/libxfdashboard.h> can be included directly."
+#endif
+
+#include <clutter/clutter.h>
+
+#include <libxfdashboard/actor.h>
+#include <libxfdashboard/animation.h>
+
+G_BEGIN_DECLS
+
+
+/* Object declaration */
+#define XFDASHBOARD_TYPE_THEME_ANIMATION				(xfdashboard_theme_animation_get_type())
+#define XFDASHBOARD_THEME_ANIMATION(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), XFDASHBOARD_TYPE_THEME_ANIMATION, XfdashboardThemeAnimation))
+#define XFDASHBOARD_IS_THEME_ANIMATION(obj)				(G_TYPE_CHECK_INSTANCE_TYPE((obj), XFDASHBOARD_TYPE_THEME_ANIMATION))
+#define XFDASHBOARD_THEME_ANIMATION_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), XFDASHBOARD_TYPE_THEME_ANIMATION, XfdashboardThemeAnimationClass))
+#define XFDASHBOARD_IS_THEME_ANIMATION_CLASS(klass)		(G_TYPE_CHECK_CLASS_TYPE((klass), XFDASHBOARD_TYPE_THEME_ANIMATION))
+#define XFDASHBOARD_THEME_ANIMATION_GET_CLASS(obj)		(G_TYPE_INSTANCE_GET_CLASS((obj), XFDASHBOARD_TYPE_THEME_ANIMATION, XfdashboardThemeAnimationClass))
+
+typedef struct _XfdashboardThemeAnimation				XfdashboardThemeAnimation;
+typedef struct _XfdashboardThemeAnimationClass			XfdashboardThemeAnimationClass;
+typedef struct _XfdashboardThemeAnimationPrivate		XfdashboardThemeAnimationPrivate;
+
+/**
+ * XfdashboardThemeAnimation:
+ *
+ * The #XfdashboardThemeAnimation structure contains only private data and
+ * should be accessed using the provided API
+ */
+struct _XfdashboardThemeAnimation
+{
+	/*< private >*/
+	/* Parent instance */
+	GObject									parent_instance;
+
+	/* Private structure */
+	XfdashboardThemeAnimationPrivate		*priv;
+};
+
+/**
+ * XfdashboardThemeAnimationClass:
+ *
+ * The #XfdashboardThemeAnimationClass structure contains only private data
+ */
+struct _XfdashboardThemeAnimationClass
+{
+	/*< private >*/
+	/* Parent class */
+	GObjectClass							parent_class;
+
+	/*< public >*/
+	/* Virtual functions */
+};
+
+/* Errors */
+#define XFDASHBOARD_THEME_ANIMATION_ERROR				(xfdashboard_theme_animation_error_quark())
+
+GQuark xfdashboard_theme_animation_error_quark(void);
+
+typedef enum /*< prefix=XFDASHBOARD_THEME_ANIMATION_ERROR >*/
+{
+	XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
+	XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
+} XfdashboardThemeAnimationErrorEnum;
+
+/* Public API */
+GType xfdashboard_theme_animation_get_type(void) G_GNUC_CONST;
+
+XfdashboardThemeAnimation* xfdashboard_theme_animation_new(void);
+
+gboolean xfdashboard_theme_animation_add_file(XfdashboardThemeAnimation *self,
+											const gchar *inPath,
+											GError **outError);
+
+XfdashboardAnimation* xfdashboard_theme_animation_create(XfdashboardThemeAnimation *self,
+															XfdashboardActor *inSender,
+															const gchar *inSignal);
+
+G_END_DECLS
+
+#endif	/* __LIBXFDASHBOARD_THEME_ANIMATION__ */
diff --git a/libxfdashboard/theme.c b/libxfdashboard/theme.c
index f90888f..69cb1fb 100644
--- a/libxfdashboard/theme.c
+++ b/libxfdashboard/theme.c
@@ -52,6 +52,7 @@ struct _XfdashboardThemePrivate
 	XfdashboardThemeCSS			*styling;
 	XfdashboardThemeLayout		*layout;
 	XfdashboardThemeEffects		*effects;
+	XfdashboardThemeAnimation	*animation;
 
 	gchar						*userThemeStyleFile;
 	gchar						*userGlobalStyleFile;
@@ -367,7 +368,7 @@ static gboolean _xfdashboard_theme_load_resources(XfdashboardTheme *self,
 			/* Get path and file for effect resource */
 			resourceFile=g_build_filename(priv->themePath, *resource, NULL);
 
-			/* Try to load style resource */
+			/* Try to load effects resource */
 			XFDASHBOARD_DEBUG(self, THEME,
 								"Loading XML effects file %s for theme %s",
 								resourceFile,
@@ -397,9 +398,71 @@ static gboolean _xfdashboard_theme_load_resources(XfdashboardTheme *self,
 		g_strfreev(resources);
 	}
 
+	/* Create XML parser and load animation resources which are optional */
+	if(g_key_file_has_key(themeKeyFile,
+							XFDASHBOARD_THEME_GROUP,
+							"Animation",
+							NULL))
+	{
+		resources=g_key_file_get_string_list(themeKeyFile,
+												XFDASHBOARD_THEME_GROUP,
+												"Animation",
+												NULL,
+												&error);
+		if(!resources)
+		{
+			/* Set error */
+			g_propagate_error(outError, error);
+
+			/* Release allocated resources */
+			if(themeKeyFile) g_key_file_free(themeKeyFile);
+
+			/* Return FALSE to indicate error */
+			return(FALSE);
+		}
+
+		resource=resources;
+		while(*resource)
+		{
+			/* Get path and file for animation resource */
+			resourceFile=g_build_filename(priv->themePath, *resource, NULL);
+
+			/* Try to load animation resource */
+			XFDASHBOARD_DEBUG(self, THEME,
+								"Loading XML animation file %s for theme %s",
+								resourceFile,
+								priv->themeName);
+
+			if(!xfdashboard_theme_animation_add_file(priv->animation, resourceFile, &error))
+			{
+				/* Set error */
+				g_propagate_error(outError, error);
+
+				/* Release allocated resources */
+				if(resources) g_strfreev(resources);
+				if(resourceFile) g_free(resourceFile);
+				if(themeKeyFile) g_key_file_free(themeKeyFile);
+
+				/* Return FALSE to indicate error */
+				return(FALSE);
+			}
+
+			/* Release allocated resources */
+			if(resourceFile) g_free(resourceFile);
+
+			/* Continue with next entry */
+			resource++;
+			counter++;
+		}
+		g_strfreev(resources);
+	}
+
 	/* Release allocated resources */
 	if(themeKeyFile) g_key_file_free(themeKeyFile);
 
+	/* TODO: Just for testing animations  */
+	xfdashboard_theme_animation_add_file(priv->animation, "dummy", &error);
+
 	/* Return TRUE to indicate success */
 	return(TRUE);
 }
@@ -549,6 +612,7 @@ static void _xfdashboard_theme_set_theme_name(XfdashboardTheme *self, const gcha
 	priv->styling=xfdashboard_theme_css_new(priv->themePath);
 	priv->layout=xfdashboard_theme_layout_new();
 	priv->effects=xfdashboard_theme_effects_new();
+	priv->animation=xfdashboard_theme_animation_new();
 
 	/* Check for user resource files */
 	resourceFile=g_build_filename(g_get_user_config_dir(), "xfdashboard", "themes", XFDASHBOARD_USER_GLOBAL_CSS_FILE, NULL);
@@ -651,6 +715,12 @@ static void _xfdashboard_theme_dispose(GObject *inObject)
 		priv->effects=NULL;
 	}
 
+	if(priv->animation)
+	{
+		g_object_unref(priv->animation);
+		priv->animation=NULL;
+	}
+
 	/* Call parent's class dispose method */
 	G_OBJECT_CLASS(xfdashboard_theme_parent_class)->dispose(inObject);
 }
@@ -772,6 +842,7 @@ void xfdashboard_theme_init(XfdashboardTheme *self)
 	priv->styling=NULL;
 	priv->layout=NULL;
 	priv->effects=NULL;
+	priv->animation=NULL;
 }
 
 /* IMPLEMENTATION: Errors */
@@ -891,3 +962,11 @@ XfdashboardThemeEffects* xfdashboard_theme_get_effects(XfdashboardTheme *self)
 
 	return(self->priv->effects);
 }
+
+/* Get theme animation */
+XfdashboardThemeAnimation* xfdashboard_theme_get_animation(XfdashboardTheme *self)
+{
+	g_return_val_if_fail(XFDASHBOARD_IS_THEME(self), NULL);
+
+	return(self->priv->animation);
+}
diff --git a/libxfdashboard/theme.h b/libxfdashboard/theme.h
index 5ead020..dcdac4e 100644
--- a/libxfdashboard/theme.h
+++ b/libxfdashboard/theme.h
@@ -34,6 +34,7 @@
 #include <libxfdashboard/theme-css.h>
 #include <libxfdashboard/theme-layout.h>
 #include <libxfdashboard/theme-effects.h>
+#include <libxfdashboard/theme-animation.h>
 
 G_BEGIN_DECLS
 
@@ -96,6 +97,7 @@ gboolean xfdashboard_theme_load(XfdashboardTheme *self,
 XfdashboardThemeCSS* xfdashboard_theme_get_css(XfdashboardTheme *self);
 XfdashboardThemeLayout* xfdashboard_theme_get_layout(XfdashboardTheme *self);
 XfdashboardThemeEffects* xfdashboard_theme_get_effects(XfdashboardTheme *self);
+XfdashboardThemeAnimation* xfdashboard_theme_get_animation(XfdashboardTheme *self);
 
 G_END_DECLS
 
diff --git a/libxfdashboard/transition-group.c b/libxfdashboard/transition-group.c
new file mode 100644
index 0000000..1723867
--- /dev/null
+++ b/libxfdashboard/transition-group.c
@@ -0,0 +1,683 @@
+/*
+ * transition-group: A grouping transition
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+/**
+ * SECTION:transition-group
+ * @short_description: Group transitions together
+ * @include: xfdashboard/transition-group.h
+ *
+ * The #XfdashboardTransitionGroup allows running multiple #ClutterTransition
+ * instances concurrently.
+ * 
+ * The #XfdashboardTransitionGroup is a one-to-one copy of #ClutterTransitionGroup
+ * with the only enhancements that API functions to get a list of all transitions
+ * grouped together in that grouping transition was added as well as all timeline
+ * configuration, e.g. duration, direction, repeats etc., that are set on an
+ * #XfdashboardTransitionGroup will also applied to all transitions added to this
+ * group.
+ *
+ * In addition it will also reset "min-width-set", "min-height-set" and other
+ * "*-set" properties to their values before the #XfdashboardTransitionGroup
+ * was attached the animatable actor. This will reset commonly statically set
+ * size and transformation properties, like "min-width", "min-height", "natural-width"
+ * etc., which will prevent a layout manager to use the natural width and/or height
+ * of an actor. This behaviour can be turned off via setting property "reset-flags"
+ * to boolean FALSE (enabled and set to TRUE by default).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libxfdashboard/transition-group.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <libxfdashboard/compat.h>
+#include <libxfdashboard/debug.h>
+
+
+/* Forward declarations */
+typedef struct _XfdashboardTransitionActorSetFlags			XfdashboardTransitionActorSetFlags;
+struct _XfdashboardTransitionActorSetFlags
+{
+	/* Value of property "*-set" at animatable actor */
+	gboolean	fixedPosition;
+	gboolean	minWidth;
+	gboolean	minHeight;
+	gboolean	naturalWidth;
+	gboolean	naturalHeight;
+	gboolean	transform;
+	gboolean	childTransform;
+	gboolean	backgroundColor;
+};
+
+
+/* Define this class in GObject system */
+struct _XfdashboardTransitionGroupPrivate
+{
+	/* Properties related */
+	gboolean							resetFlags;
+
+	/* Instance related */
+	GHashTable							*transitions;
+	GList								*bindings;
+	XfdashboardTransitionActorSetFlags	flags;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardTransitionGroup,
+							xfdashboard_transition_group,
+							CLUTTER_TYPE_TRANSITION);
+
+
+/* Properties */
+enum
+{
+	PROP_0,
+
+	PROP_RESET_FLAGS,
+
+	PROP_LAST
+};
+
+static GParamSpec* XfdashboardTransitionGroupProperties[PROP_LAST]={ 0, };
+
+
+/* IMPLEMENTATION: Private variables and methods */
+
+typedef struct _XfdashboardTransitionGroupBindingEntry		XfdashboardTransitionGroupBindingEntry;
+struct _XfdashboardTransitionGroupBindingEntry
+{
+	XfdashboardTransitionGroup	*self;
+	GBinding					*binding;
+};
+
+/* Property names to bind when transition is added */
+static const gchar *_xfdashboard_transition_group_bindable_properties[]=
+{
+	"auto-reverse",
+	"delay",
+	"duration",
+	"loop",
+	"progress-mode",
+	"repeat-count",
+	NULL
+};
+
+/* A binding entry is notified about its destruction */
+static void _xfdashboard_transition_group_binding_notified(gpointer inData)
+{
+	XfdashboardTransitionGroup					*self;
+	XfdashboardTransitionGroupPrivate			*priv;
+	XfdashboardTransitionGroupBindingEntry		*entry;
+
+	entry=(XfdashboardTransitionGroupBindingEntry*)inData;
+	self=entry->self;
+	priv=self->priv;
+
+	/* Remove binding from list */
+	priv->bindings=g_list_remove(priv->bindings, entry->binding);
+
+	/* Release allocated resources of entry */
+	g_free(inData);
+}
+
+
+/* IMPLEMENTATION: ClutterTimeline */
+
+/* Time at transition has elapsed, so advance all transitions as well */
+static void _xfdashboard_transition_group_timeline_new_frame(ClutterTimeline *inTimeline,
+																gint inElapsed)
+{
+	XfdashboardTransitionGroup			*self;
+	XfdashboardTransitionGroupPrivate	*priv;
+	GHashTableIter						iter;
+	gpointer							element;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(inTimeline));
+
+	self=XFDASHBOARD_TRANSITION_GROUP(inTimeline);
+	priv=self->priv;
+
+	/* Iterate through transitions and advance their timeline */
+	g_hash_table_iter_init(&iter, priv->transitions);
+	while(g_hash_table_iter_next(&iter, &element, NULL))
+	{
+		clutter_timeline_advance(CLUTTER_TIMELINE(element), clutter_timeline_get_elapsed_time(inTimeline));
+		g_signal_emit_by_name(element, "new-frame", 0, clutter_timeline_get_elapsed_time(inTimeline));
+	}
+}
+
+/* This transition group was started */
+static void _xfdashboard_transition_group_timeline_started(ClutterTimeline *inTimeline)
+{
+	XfdashboardTransitionGroup			*self;
+	XfdashboardTransitionGroupPrivate	*priv;
+	GHashTableIter						iter;
+	gpointer							element;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(inTimeline));
+
+	self=XFDASHBOARD_TRANSITION_GROUP(inTimeline);
+	priv=self->priv;
+
+	/* Iterate through transitions and emit start signal */
+	g_hash_table_iter_init(&iter, priv->transitions);
+	while(g_hash_table_iter_next(&iter, &element, NULL))
+	{
+		g_signal_emit_by_name(element, "started");
+	}
+}
+
+
+/* IMPLEMENTATION: ClutterTransition */
+
+/* Transition group was attached to an animatable actor */
+static void _xfdashboard_transition_group_transition_attached(ClutterTransition *inTransition,
+																ClutterAnimatable *inAnimatable)
+{
+	XfdashboardTransitionGroup				*self;
+	XfdashboardTransitionGroupPrivate		*priv;
+	GHashTableIter							iter;
+	gpointer								element;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(inTransition));
+	g_return_if_fail(CLUTTER_IS_ANIMATABLE(inAnimatable));
+
+	self=XFDASHBOARD_TRANSITION_GROUP(inTransition);
+	priv=self->priv;
+
+	/* Iterate through transitions and set the animatable actor
+	 * it is attached to.
+	 */
+	g_hash_table_iter_init(&iter, priv->transitions);
+	while(g_hash_table_iter_next(&iter, &element, NULL))
+	{
+		clutter_transition_set_animatable(CLUTTER_TRANSITION(element), inAnimatable);
+	}
+
+	/* Get current value of "*-set" properties */
+	memset(&priv->flags, 0, sizeof(priv->flags));
+	g_object_get(inAnimatable,
+					"fixed-position-set", &priv->flags.fixedPosition,
+					"min-width-set", &priv->flags.minWidth,
+					"min-height-set", &priv->flags.minHeight,
+					"natural-width-set", &priv->flags.naturalWidth,
+					"natural-height-set", &priv->flags.naturalHeight,
+					"transform-set", &priv->flags.transform,
+					"child-transform-set", &priv->flags.childTransform,
+					"background-color-set", &priv->flags.backgroundColor,
+					NULL);
+}
+
+/* Transition group was detached from an animatable actor */
+static void _xfdashboard_transition_group_transition_detached(ClutterTransition *inTransition,
+																ClutterAnimatable *inAnimatable)
+{
+	XfdashboardTransitionGroup				*self;
+	XfdashboardTransitionGroupPrivate		*priv;
+	GHashTableIter							iter;
+	gpointer								element;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(inTransition));
+	g_return_if_fail(CLUTTER_IS_ANIMATABLE(inAnimatable));
+
+	self=XFDASHBOARD_TRANSITION_GROUP(inTransition);
+	priv=self->priv;
+
+	/* Iterate through transitions and reset the animatable actor */
+	g_hash_table_iter_init(&iter, priv->transitions);
+	while(g_hash_table_iter_next(&iter, &element, NULL))
+	{
+		clutter_transition_set_animatable(CLUTTER_TRANSITION(element), NULL);
+	}
+
+	/* Restore "*-set" properties if requested */
+	if(priv->resetFlags)
+	{
+		XfdashboardTransitionActorSetFlags	newFlags;
+
+		/* Get current value of "*-set" properties */
+		memset(&newFlags, 0, sizeof(newFlags));
+		g_object_get(inAnimatable,
+						"fixed-position-set", &newFlags.fixedPosition,
+						"min-width-set", &newFlags.minWidth,
+						"min-height-set", &newFlags.minHeight,
+						"natural-width-set", &newFlags.naturalWidth,
+						"natural-height-set", &newFlags.naturalHeight,
+						"transform-set", &newFlags.transform,
+						"child-transform-set", &newFlags.childTransform,
+						"background-color-set", &newFlags.backgroundColor,
+						NULL);
+
+		/* Restore the values which have changed to their old value */
+		if(newFlags.fixedPosition!=priv->flags.fixedPosition)
+		{
+			g_object_set(inAnimatable, "fixed-position-set", priv->flags.fixedPosition, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'fixed-position-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.minWidth!=priv->flags.minWidth)
+		{
+			g_object_set(inAnimatable, "min-width-set", priv->flags.minWidth, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'min-width-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.minHeight!=priv->flags.minHeight)
+		{
+			g_object_set(inAnimatable, "min-height-set", priv->flags.minHeight, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'min-height-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.naturalWidth!=priv->flags.naturalWidth)
+		{
+			g_object_set(inAnimatable, "natural-width-set", priv->flags.naturalWidth, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'natural-width-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.naturalHeight!=priv->flags.naturalHeight)
+		{
+			g_object_set(inAnimatable, "natural-height-set", priv->flags.naturalHeight, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'natural-height-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.transform!=priv->flags.transform)
+		{
+			g_object_set(inAnimatable, "transform-set", priv->flags.transform, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'transform-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.childTransform!=priv->flags.childTransform)
+		{
+			g_object_set(inAnimatable, "child-transform-set", priv->flags.childTransform, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'child-transform-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+
+		if(newFlags.backgroundColor!=priv->flags.backgroundColor)
+		{
+			g_object_set(inAnimatable, "background-color-set", priv->flags.backgroundColor, NULL);
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Restoring property 'background-color-set' at actor %s@%p",
+								G_OBJECT_TYPE_NAME(inAnimatable), inAnimatable);
+		}
+	}
+}
+
+
+/* IMPLEMENTATION: GObject */
+
+/* Finalize this object */
+static void _xfdashboard_transition_group_finalize(GObject *inObject)
+{
+	XfdashboardTransitionGroup			*self=XFDASHBOARD_TRANSITION_GROUP(inObject);
+	XfdashboardTransitionGroupPrivate	*priv=self->priv;
+
+	/* Release our allocated variables */
+	if(priv->bindings)
+	{
+		GList							*iter;
+		GBinding						*binding;
+
+		for(iter=priv->bindings; iter; iter=g_list_next(iter))
+		{
+			/* Release valid bindings */
+			binding=(GBinding*)iter->data;
+			if(!binding) continue;
+
+			/* Unbind binding and release object */
+			g_object_unref(binding);
+		}
+		g_list_free(priv->bindings);
+		priv->bindings=NULL;
+	}
+
+	if(priv->transitions)
+	{
+		g_hash_table_unref(priv->transitions);
+		priv->transitions=NULL;
+	}
+
+	/* Call parent's class dispose method */
+	G_OBJECT_CLASS(xfdashboard_transition_group_parent_class)->finalize(inObject);
+}
+
+/* Set/get properties */
+static void _xfdashboard_transition_group_set_property(GObject *inObject,
+														guint inPropID,
+														const GValue *inValue,
+														GParamSpec *inSpec)
+{
+	XfdashboardTransitionGroup			*self=XFDASHBOARD_TRANSITION_GROUP(inObject);
+	XfdashboardTransitionGroupPrivate	*priv=self->priv;
+
+	switch(inPropID)
+	{
+		case PROP_RESET_FLAGS:
+			priv->resetFlags=g_value_get_boolean(inValue);
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
+			break;
+	}
+}
+
+static void _xfdashboard_transition_group_get_property(GObject *inObject,
+														guint inPropID,
+														GValue *outValue,
+														GParamSpec *inSpec)
+{
+	XfdashboardTransitionGroup			*self=XFDASHBOARD_TRANSITION_GROUP(inObject);
+	XfdashboardTransitionGroupPrivate	*priv=self->priv;
+
+	switch(inPropID)
+	{
+		case PROP_RESET_FLAGS:
+			g_value_set_boolean(outValue, priv->resetFlags);
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
+			break;
+	}
+}
+
+/* Class initialization
+ * Override functions in parent classes and define properties
+ * and signals
+ */
+void xfdashboard_transition_group_class_init(XfdashboardTransitionGroupClass *klass)
+{
+	GObjectClass			*gobjectClass=G_OBJECT_CLASS(klass);
+	ClutterTimelineClass	*timelineClass=CLUTTER_TIMELINE_CLASS(klass);
+	ClutterTransitionClass	*transitionClass=CLUTTER_TRANSITION_CLASS(klass);
+
+	/* Override functions */
+	gobjectClass->set_property=_xfdashboard_transition_group_set_property;
+	gobjectClass->get_property=_xfdashboard_transition_group_get_property;
+	gobjectClass->finalize=_xfdashboard_transition_group_finalize;
+
+	timelineClass->started=_xfdashboard_transition_group_timeline_started;
+	timelineClass->new_frame=_xfdashboard_transition_group_timeline_new_frame;
+
+	transitionClass->attached=_xfdashboard_transition_group_transition_attached;
+	transitionClass->detached=_xfdashboard_transition_group_transition_detached;
+
+	/* Define properties */
+	XfdashboardTransitionGroupProperties[PROP_RESET_FLAGS]=
+		g_param_spec_boolean("reset-flags",
+								_("Reset flags"),
+								_("If TRUE the flags for static sizes, transformations etc. at animatable actor is resetted to old state"),
+								TRUE,
+								G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardTransitionGroupProperties);
+}
+
+/* Object initialization
+ * Create private structure and set up default values
+ */
+void xfdashboard_transition_group_init(XfdashboardTransitionGroup *self)
+{
+	XfdashboardTransitionGroupPrivate		*priv;
+
+	priv=self->priv=xfdashboard_transition_group_get_instance_private(self);
+
+	/* Set up default values */
+	priv->transitions=g_hash_table_new_full(NULL,
+											NULL,
+											(GDestroyNotify)g_object_unref,
+											NULL);
+	priv->bindings=NULL;
+}
+
+
+/* IMPLEMENTATION: Public API */
+/**
+ * xfdashboard_transition_group_new:
+ *
+ * Creates a new #XfdashboardTransitionGroup instance.
+ *
+ * Return value: The newly created #XfdashboardTransitionGroup. Use
+ *   g_object_unref() when done to deallocate the resources it uses
+ */
+ClutterTransition* xfdashboard_transition_group_new(void)
+{
+	return(CLUTTER_TRANSITION(g_object_new(XFDASHBOARD_TYPE_TRANSITION_GROUP, NULL)));
+}
+
+/**
+ * xfdashboard_transition_group_add_transition:
+ * @self: A #XfdashboardTransitionGroup
+ * @inTransition: A #ClutterTransition
+ *
+ * Adds transition @inTransition to group @self.
+ *
+ * This function acquires a reference on @inTransition that will be released
+ * when calling xfdashboard_transition_group_remove_transition() or
+ * xfdashboard_transition_group_remove_all().
+ */
+void xfdashboard_transition_group_add_transition(XfdashboardTransitionGroup *self,
+													ClutterTransition *inTransition)
+{
+	XfdashboardTransitionGroupPrivate			*priv;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(self));
+	g_return_if_fail(CLUTTER_IS_TRANSITION(inTransition));
+
+	priv=self->priv;
+
+	/* Add and set up transition */
+	if(g_hash_table_add(priv->transitions, g_object_ref(inTransition)))
+	{
+		const gchar								**iter;
+		GBinding								*binding;
+		XfdashboardTransitionGroupBindingEntry	*entry;
+
+		/* Clone timeline configuration to transition and bind properties
+		 * to update them at added transition when this timeline changes
+		 * as this transition is new to this group.
+		 */
+		for(iter=_xfdashboard_transition_group_bindable_properties; *iter; iter++)
+		{
+			entry=g_new0(XfdashboardTransitionGroupBindingEntry, 1);
+			binding=g_object_bind_property_full(self, *iter,
+												inTransition, *iter,
+												G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
+												NULL,
+												NULL,
+												entry,
+												_xfdashboard_transition_group_binding_notified);
+
+			entry->self=self;
+			entry->binding=binding;
+
+			priv->bindings=g_list_prepend(priv->bindings, binding);
+
+			XFDASHBOARD_DEBUG(self, ANIMATION,
+								"Bind property '%s' from transition group %s@%p to actor %s@%p via %s@%p",
+								*iter,
+								G_OBJECT_TYPE_NAME(self),
+								self,
+								G_OBJECT_TYPE_NAME(inTransition),
+								inTransition,
+								G_OBJECT_TYPE_NAME(binding),
+								binding);
+		}
+	}
+}
+
+/**
+ * xfdashboard_transition_group_remove_transition:
+ * @self: A #XfdashboardTransitionGroup
+ * @inTransition: A #ClutterTransition
+ *
+ * Removes transition @inTransition from group @self.
+ *
+ * This function releases the reference acquired on @inTransition when
+ * calling xfdashboard_transition_group_add_transition().
+ */
+void xfdashboard_transition_group_remove_transition(XfdashboardTransitionGroup *self,
+													ClutterTransition *inTransition)
+{
+	XfdashboardTransitionGroupPrivate	*priv;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(self));
+	g_return_if_fail(CLUTTER_IS_TRANSITION(inTransition));
+
+	priv=self->priv;
+
+	/* First remove bindings to transition as long as a reference is held */
+	if(priv->bindings)
+	{
+		GList							*iter;
+		GList							*nextIter;
+
+		iter=priv->bindings;
+		while(iter)
+		{
+			GBinding					*binding;
+
+			/* Get next element to iterate */
+			nextIter=g_list_next(iter);
+
+			/* Get currently iterated binding */
+			binding=(GBinding*)(iter->data);
+			if(!binding)
+			{
+				iter=nextIter;
+				continue;
+			}
+
+			/* If binding's target refers to transition removed,
+			 * unbind it.
+			 */
+			if(g_binding_get_target(binding)==G_OBJECT(inTransition))
+			{
+				/* Remove binding from list */
+				priv->bindings=g_list_remove_link(priv->bindings, iter);
+
+				/* Unbind property binding */
+				XFDASHBOARD_DEBUG(self, ANIMATION,
+									"Unbinding property '%s' from transition group %s@%p at actor %s@%p via %s@%p",
+									g_binding_get_source_property(binding),
+									G_OBJECT_TYPE_NAME(self),
+									self,
+									G_OBJECT_TYPE_NAME(inTransition),
+									inTransition,
+									G_OBJECT_TYPE_NAME(binding),
+									binding);
+				g_binding_unbind(binding);
+
+				/* Free list element currently iterated */
+				g_list_free(iter);
+			}
+
+			/* Move to next element */
+			iter=nextIter;
+		}
+	}
+
+	/* Remove transition */
+	g_hash_table_remove(priv->transitions, inTransition);
+}
+
+/**
+ * xfdashboard_transition_group_remove_all:
+ * @self: A #XfdashboardTransitionGroup
+ *
+ * Removes all transitions from group @self.
+ *
+ * This function releases all references acquired when calling
+ * xfdashboard_transition_group_add_transition().
+ */
+void xfdashboard_transition_group_remove_all(XfdashboardTransitionGroup *self)
+{
+	XfdashboardTransitionGroupPrivate	*priv;
+
+	g_return_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(self));
+
+	priv=self->priv;
+
+	/* First remove all binding as long as a reference to transition is held */
+	if(priv->bindings)
+	{
+		g_list_free_full(priv->bindings, (GDestroyNotify)g_binding_unbind);
+		priv->bindings=NULL;
+	}
+
+	/* Remove all transitions */
+	g_hash_table_remove_all(priv->transitions);
+}
+
+/**
+ * xfdashboard_transition_group_get_transitions:
+ * @self: A #XfdashboardTransitionGroup
+ *
+ * Returns a list of all transitions of group @self. The list is a #GSList
+ * and each element contains a #ClutterTransition. When creating the list
+ * an additional reference is taken on the element, so the caller is responsible
+ * to release the reference.
+ *
+ * Return value: (element-type XfdashboardWindowTrackerWindow) (transfer none):
+ *   The list of #ClutterTransition added to this group.
+ *   Use g_slist_free_full(list, g_object_unref) when done to release the
+ *   additional reference taken.
+ */
+GSList* xfdashboard_transition_group_get_transitions(XfdashboardTransitionGroup *self)
+{
+	XfdashboardTransitionGroupPrivate	*priv;
+	GSList								*list;
+	GHashTableIter						iter;
+	gpointer							element;
+
+	g_return_val_if_fail(XFDASHBOARD_IS_TRANSITION_GROUP(self), NULL);
+
+	priv=self->priv;
+	list=NULL;
+
+	/* Iterate through transitions and add to list but take a extra reference */
+	g_hash_table_iter_init(&iter, priv->transitions);
+	while(g_hash_table_iter_next(&iter, &element, NULL))
+	{
+		list=g_slist_prepend(list, g_object_ref(element));
+	}
+	list=g_slist_reverse(list);
+
+	/* Return created list */
+	return(list);
+}
diff --git a/libxfdashboard/transition-group.h b/libxfdashboard/transition-group.h
new file mode 100644
index 0000000..7cdf65c
--- /dev/null
+++ b/libxfdashboard/transition-group.h
@@ -0,0 +1,95 @@
+/*
+ * transition-group: A grouping transition
+ * 
+ * Copyright 2012-2019 Stephan Haller <nomad at froevel.de>
+ * 
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ * 
+ * 
+ */
+
+#ifndef __LIBXFDASHBOARD_TRANSITION_GROUP__
+#define __LIBXFDASHBOARD_TRANSITION_GROUP__
+
+#if !defined(__LIBXFDASHBOARD_H_INSIDE__) && !defined(LIBXFDASHBOARD_COMPILATION)
+#error "Only <libxfdashboard/libxfdashboard.h> can be included directly."
+#endif
+
+#include <clutter/clutter.h>
+
+#include <libxfdashboard/actor.h>
+
+G_BEGIN_DECLS
+
+/* Object declaration */
+#define XFDASHBOARD_TYPE_TRANSITION_GROUP				(xfdashboard_transition_group_get_type())
+#define XFDASHBOARD_TRANSITION_GROUP(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), XFDASHBOARD_TYPE_TRANSITION_GROUP, XfdashboardTransitionGroup))
+#define XFDASHBOARD_IS_TRANSITION_GROUP(obj)			(G_TYPE_CHECK_INSTANCE_TYPE((obj), XFDASHBOARD_TYPE_TRANSITION_GROUP))
+#define XFDASHBOARD_TRANSITION_GROUP_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), XFDASHBOARD_TYPE_TRANSITION_GROUP, XfdashboardTransitionGroupClass))
+#define XFDASHBOARD_IS_TRANSITION_GROUP_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), XFDASHBOARD_TYPE_TRANSITION_GROUP))
+#define XFDASHBOARD_TRANSITION_GROUP_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), XFDASHBOARD_TYPE_TRANSITION_GROUP, XfdashboardTransitionGroupClass))
+
+typedef struct _XfdashboardTransitionGroup			XfdashboardTransitionGroup;
+typedef struct _XfdashboardTransitionGroupClass		XfdashboardTransitionGroupClass;
+typedef struct _XfdashboardTransitionGroupPrivate		XfdashboardTransitionGroupPrivate;
+
+/**
+ * XfdashboardTransitionGroup:
+ *
+ * The #XfdashboardTransitionGroup structure contains only private data and
+ * should be accessed using the provided API
+ */
+ struct _XfdashboardTransitionGroup
+{
+	/*< private >*/
+	/* Parent instance */
+	ClutterTransition					parent_instance;
+
+	/* Private structure */
+	XfdashboardTransitionGroupPrivate	*priv;
+};
+
+/**
+ * XfdashboardTransitionGroupClass:
+ *
+ * The #XfdashboardTransitionGroupClass structure contains only private data
+ */
+struct _XfdashboardTransitionGroupClass
+{
+	/*< private >*/
+	/* Parent class */
+	ClutterTransitionClass				parent_class;
+
+	/*< public >*/
+	/* Virtual functions */
+};
+
+/* Public API */
+GType xfdashboard_transition_group_get_type(void) G_GNUC_CONST;
+
+ClutterTransition* xfdashboard_transition_group_new(void);
+
+void xfdashboard_transition_group_add_transition(XfdashboardTransitionGroup *self,
+													ClutterTransition *inTransition);
+void xfdashboard_transition_group_remove_transition(XfdashboardTransitionGroup *self,
+													ClutterTransition *inTransition);
+void xfdashboard_transition_group_remove_all(XfdashboardTransitionGroup *self);
+
+GSList* xfdashboard_transition_group_get_transitions(XfdashboardTransitionGroup *self);
+
+G_END_DECLS
+
+#endif	/* __LIBXFDASHBOARD_TRANSITION_GROUP__ */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the Xfce4-commits mailing list