Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-actor.c
index 3dd40c1..5865d57 100644 (file)
@@ -6,7 +6,7 @@
  * Authored By Matthew Allum  <mallum@openedhand.com>
  *
  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd
- * Copyright (C) 2009, 2010 Intel Corp
+ * Copyright (C) 2009, 2010, 2011, 2012 Intel Corp
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -24,7 +24,7 @@
 
 /**
  * SECTION:clutter-actor
- * @short_description: Base abstract class for all visual stage actors.
+ * @short_description: The basic element of the scene graph 
  *
  * The ClutterActor class is the basic element of the scene graph in Clutter,
  * and it encapsulates the position, size, and transformations of a node in
  *   of children from a ClutterActor, use the #ClutterContainer::actor-removed
  *   signal.</para>
  *   <informalexample><programlisting>
- * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../tests/interactive/test-actor.c">
+ * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/basic-actor.c">
  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
  * </xi:include>
  *   </programlisting></informalexample>
  *   </figure>
  * </refsect2>
  *
+ * <refsect2 id="ClutterActor-painting">
+ *   <title>Painting an actor</title>
+ *   <para>There are three ways to paint an actor:</para>
+ *   <itemizedlist>
+ *     <listitem><para>set a delegate #ClutterContent as the value for the
+ *     #ClutterActor:content property of the actor;</para></listitem>
+ *     <listitem><para>subclass #ClutterActor and override the
+ *     #ClutterActorClass.paint_node() virtual function;</para></listitem>
+ *     <listitem><para>subclass #ClutterActor and override the
+ *     #ClutterActorClass.paint() virtual function.</para></listitem>
+ *   </itemizedlist>
+ *   <formalpara>
+ *     <title>Setting the Content property</title>
+ *     <para>A #ClutterContent is a delegate object that takes over the
+ *     painting operation of one, or more actors. The #ClutterContent
+ *     painting will be performed on top of the #ClutterActor:background-color
+ *     of the actor, and before calling the #ClutterActorClass.paint_node()
+ *     virtual function.</para>
+ *     <informalexample><programlisting>
+ * ClutterActor *actor = clutter_actor_new ();
+ *
+ * /&ast; set the bounding box &ast;/
+ * clutter_actor_set_position (actor, 50, 50);
+ * clutter_actor_set_size (actor, 100, 100);
+ *
+ * /&ast; set the content; the image_content variable is set elsewhere &ast;/
+ * clutter_actor_set_content (actor, image_content);
+ *     </programlisting></informalexample>
+ *   </formalpara>
+ *   <formalpara>
+ *     <title>Overriding the paint_node virtual function</title>
+ *     <para>The #ClutterActorClass.paint_node() virtual function is invoked
+ *     whenever an actor needs to be painted. The implementation of the
+ *     virtual function must only paint the contents of the actor itself,
+ *     and not the contents of its children, if the actor has any.</para>
+ *     <para>The #ClutterPaintNode passed to the virtual function is the
+ *     local root of the render tree; any node added to it will be
+ *     rendered at the correct position, as defined by the actor's
+ *     #ClutterActor:allocation.</para>
+ *     <informalexample><programlisting>
+ * static void
+ * my_actor_paint_node (ClutterActor     *actor,
+ *                      ClutterPaintNode *root)
+ * {
+ *   ClutterPaintNode *node;
+ *   ClutterActorBox box;
+ *
+ *   /&ast; where the content of the actor should be painted &ast;/
+ *   clutter_actor_get_allocation_box (actor, &box);
+ *
+ *   /&ast; the cogl_texture variable is set elsewhere &ast;/
+ *   node = clutter_texture_node_new (cogl_texture, CLUTTER_COLOR_White,
+ *                                    CLUTTER_SCALING_FILTER_TRILINEAR,
+ *                                    CLUTTER_SCALING_FILTER_LINEAR);
+ *
+ *   /&ast; paint the content of the node using the allocation &ast;/
+ *   clutter_paint_node_add_rectangle (node, &box);
+ *
+ *   /&ast; add the node, and transfer ownership &ast;/
+ *   clutter_paint_node_add_child (root, node);
+ *   clutter_paint_node_unref (node);
+ * }
+ *     </programlisting></informalexample>
+ *   </formalpara>
+ *   <formalpara>
+ *     <title>Overriding the paint virtual function</title>
+ *     <para>The #ClutterActorClass.paint() virtual function is invoked
+ *     when the #ClutterActor::paint signal is emitted, and after the other
+ *     signal handlers have been invoked. Overriding the paint virtual
+ *     function gives total control to the paint sequence of the actor
+ *     itself, including the children of the actor, if any.</para>
+ *     <warning><para>It is strongly discouraged to override the
+ *     #ClutterActorClass.paint() virtual function, as well as connecting
+ *     to the #ClutterActor::paint signal. These hooks into the paint
+ *     sequence are considered legacy, and will be removed when the Clutter
+ *     API changes.</para></warning>
+ *   </formalpara>
+ * </refsect2>
+ *
  * <refsect2 id="ClutterActor-events">
  *   <title>Handling events on an actor</title>
  *   <para>A #ClutterActor can receive and handle input device events, for
  *   returning %CLUTTER_EVENT_PROPAGATE.</para>
  * </refsect2>
  *
+ * <refsect2 id="ClutterActor-animation">
+ *   <title>Animation</title>
+ *   <para>Animation is a core concept of modern user interfaces; Clutter
+ *   provides a complete and powerful animation framework that automatically
+ *   tweens the actor's state without requiring direct, frame by frame
+ *   manipulation from your application code.</para>
+ *   <formalpara>
+ *     <title>Implicit animations</title>
+ *     <para>The implicit animation model of Clutter assumes that all the
+ *     changes in an actor state should be gradual and asynchronous; Clutter
+ *     will automatically transition an actor's property change between the
+ *     current state and the desired one without manual intervention.</para>
+ *     <para>By default, in the 1.0 API series, the transition happens with
+ *     a duration of zero milliseconds, and the implicit animation is an
+ *     opt in feature to retain backwards compatibility. In order to enable
+ *     implicit animations, it is necessary to change the easing state of
+ *     an actor by using clutter_actor_save_easing_state():</para>
+ *     <informalexample><programlisting>
+ * /&ast; assume that the actor is currently positioned at (100, 100) &ast;/
+ * clutter_actor_save_easing_state (actor);
+ * clutter_actor_set_position (actor, 500, 500);
+ * clutter_actor_restore_easing_state (actor);
+ *     </programlisting></informalexample>
+ *     <para>The example above will trigger an implicit animation of the
+ *     actor between its current position to a new position.</para>
+ *     <para>It is possible to animate multiple properties of an actor
+ *     at the same time, and you can animate multiple actors at the same
+ *     time as well, for instance:</para>
+ *     <informalexample><programlisting>
+ * /&ast; animate the actor's opacity and depth &ast;/
+ * clutter_actor_save_easing_state (actor);
+ * clutter_actor_set_opacity (actor, 0);
+ * clutter_actor_set_depth (actor, -100);
+ * clutter_actor_restore_easing_state (actor);
+ *
+ * /&ast; animate another actor's opacity &ast;/
+ * clutter_actor_save_easing_state (another_actor);
+ * clutter_actor_set_opacity (another_actor, 255);
+ * clutter_actor_set_depth (another_actor, 100);
+ * clutter_actor_restore_easing_state (another_actor);
+ *     </programlisting></informalexample>
+ *     <para>Implicit animations use a default duration of 250 milliseconds,
+ *     and a default easing mode of %CLUTTER_EASE_OUT_CUBIC, unless you call
+ *     clutter_actor_set_easing_mode() and clutter_actor_set_easing_duration()
+ *     after changing the easing state of the actor.</para>
+ *     <para>It is important to note that if you modify the state on an
+ *     animatable property while a transition is in flight, the transition's
+ *     final value will be updated, as well as its duration and progress
+ *     mode by using the current easing state; for instance, in the following
+ *     example:</para>
+ *     <informalexample><programlisting>
+ * clutter_actor_save_easing_state (actor);
+ * clutter_actor_set_x (actor, 200);
+ * clutter_actor_restore_easing_state (actor);
+ *
+ * clutter_actor_save_easing_state (actor);
+ * clutter_actor_set_x (actor, 100);
+ * clutter_actor_restore_easing_state (actor);
+ *     </programlisting></informalexample>
+ *     <para>the first call to clutter_actor_set_x() will begin a transition
+ *     of the #ClutterActor:x property to the value of 200; the second call
+ *     to clutter_actor_set_x() will change the transition's final value to
+ *     100.</para>
+ *     <para>It is possible to retrieve the #ClutterTransition used by the
+ *     animatable properties by using clutter_actor_get_transition() and using
+ *     the property name as the transition name.</para>
+ *   </formalpara>
+ *   <formalpara>
+ *     <title>Explicit animations</title>
+ *     <para>The explicit animation model supported by Clutter requires that
+ *     you create a #ClutterTransition object, and set the initial and
+ *     final values. The transition will not start unless you add it to the
+ *     #ClutterActor.</para>
+ *     <informalexample><programlisting>
+ * ClutterTransition *transition;
+ *
+ * transition = clutter_property_transition_new ("opacity");
+ * clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 3000);
+ * clutter_timeline_set_repeat_count (CLUTTER_TIMELINE (transition), 2);
+ * clutter_timeline_set_auto_reverse (CLUTTER_TIMELINE (transition), TRUE);
+ * clutter_transition_set_from (transition, G_TYPE_UINT, 255);
+ * clutter_transition_set_to (transition, G_TYPE_UINT, 0);
+ *
+ * clutter_actor_add_transition (actor, "animate-opacity", transition);
+ *     </programlisting></informalexample>
+ *     <para>The example above will animate the #ClutterActor:opacity property
+ *     of an actor between fully opaque and fully transparent, and back, over
+ *     a span of 3 seconds. The animation does not begin until it is added to
+ *     the actor.</para>
+ *     <para>The explicit animation API should also be used when using custom
+ *     animatable properties for #ClutterAction, #ClutterConstraint, and
+ *     #ClutterEffect instances associated to an actor; see the section on
+ *     <ulink linkend="ClutterActor-custom-animatable-properties">custom
+ *     animatable properties below</ulink> for an example.</para>
+ *     <para>Finally, explicit animations are useful for creating animations
+ *     that run continuously, for instance:</para>
+ *     <informalexample><programlisting>
+ * /&ast; this animation will pulse the actor's opacity continuously &ast;/
+ * ClutterTransition *transition;
+ * ClutterInterval *interval;
+ *
+ * transition = clutter_property_transition_new ("opacity");
+ *
+ * /&ast; we want to animate the opacity between 0 and 255 &ast;/
+ * clutter_transition_set_from (transition, G_TYPE_UINT, 0);
+ * clutter_transition_set_to (transition, G_TYPE_UINT, 255);
+ *
+ * /&ast; over a one second duration, running an infinite amount of times &ast;/
+ * clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 1000);
+ * clutter_timeline_set_repeat_count (CLUTTER_TIMELINE (transition), -1);
+ *
+ * /&ast; we want to fade in and out, so we need to auto-reverse the transition &ast;/
+ * clutter_timeline_set_auto_reverse (CLUTTER_TIMELINE (transition), TRUE);
+ *
+ * /&ast; and we want to use an easing function that eases both in and out &ast;/
+ * clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
+ *                                     CLUTTER_EASE_IN_OUT_CUBIC);
+ *
+ * /&ast; add the transition to the desired actor; this will
+ *  &ast; start the animation.
+ *  &ast;/
+ * clutter_actor_add_transition (actor, "opacityAnimation", transition);
+ *     </programlisting></informalexample>
+ *   </formalpara>
+ * </refsect2>
+ *
  * <refsect2 id="ClutterActor-subclassing">
  *   <title>Implementing an actor</title>
  *   <para>Careful consideration should be given when deciding to implement
  *   it must contain the center of rotation as described by two coordinates:
  *   Y and Z for "x-axis"; X and Z for "y-axis"; and X and Y for
  *   "z-axis".</para>
+ *   <para>#ClutterActor also defines a scriptable "margin" property which
+ *   follows the CSS "margin" shorthand.
+ *   <informalexample>
+ *     <programlisting>
+ * // 4 values
+ * "margin" : [ &lt;top&gt;, &lt;right&gt;, &lt;bottom&gt; &lt;left&gt; ]
+ * // 3 values
+ * "margin" : [ &lt;top&gt;, &lt;left/right&gt;, &lt;bottom&gt; ]
+ * // 2 values
+ * "margin" : [ &lt;top/bottom&gt;, &lt;left/right&gt; ]
+ * // 1 value
+ * "margin" : [ &lt;top/right/bottom/left&gt; ]
+ *     </programlisting>
+ *   </informalexample>
+ *   </para>
  *   <para>#ClutterActor will also parse every positional and dimensional
  *   property defined as a string through clutter_units_from_string(); you
  *   should read the documentation for the #ClutterUnits parser format for
  *   the valid units and syntax.</para>
  * </refsect2>
  *
- * <refsect2 id="ClutterActor-animating">
+ * <refsect2 id="ClutterActor-custom-animatable-properties">
  *   <title>Custom animatable properties</title>
- *   <para>#ClutterActor allows accessing properties of #ClutterAction
- *   and #ClutterConstraint instances associated to an actor instance
- *   for animation purposes.</para>
+ *   <para>#ClutterActor allows accessing properties of #ClutterAction,
+ *   #ClutterEffect, and #ClutterConstraint instances associated to an actor
+ *   instance for animation purposes.</para>
  *   <para>In order to access a specific #ClutterAction or a #ClutterConstraint
  *   property it is necessary to set the #ClutterActorMeta:name property on the
  *   given action or constraint.</para>
  *   <para>The example below animates a #ClutterBindConstraint applied to an
  *   actor using clutter_actor_animate(). The <emphasis>rect</emphasis> has
  *   a binding constraint for the <emphasis>origin</emphasis> actor, and in
- *   its initial state is fully transparent and overlapping the actor to
- *   which is bound to. </para>
+ *   its initial state is overlapping the actor to which is bound to.</para>
  *   <informalexample><programlisting>
  * constraint = clutter_bind_constraint_new (origin, CLUTTER_BIND_X, 0.0);
  * clutter_actor_meta_set_name (CLUTTER_ACTOR_META (constraint), "bind-x");
  * clutter_actor_meta_set_name (CLUTTER_ACTOR_META (constraint), "bind-y");
  * clutter_actor_add_constraint (rect, constraint);
  *
- * clutter_actor_set_reactive (rect, TRUE);
- * clutter_actor_set_opacity (rect, 0);
+ * clutter_actor_set_reactive (origin, TRUE);
  *
- * g_signal_connect (rect, "button-press-event",
+ * g_signal_connect (origin, "button-press-event",
  *                   G_CALLBACK (on_button_press),
- *                   NULL);
+ *                   rect);
  *   </programlisting></informalexample>
  *   <para>On button press, the rectangle "slides" from behind the actor to
- *   which is bound to, using the #ClutterBindConstraint:offset property and
- *   the #ClutterActor:opacity property.</para>
+ *   which is bound to, using the #ClutterBindConstraint:offset property to
+ *   achieve the effect:</para>
  *   <informalexample><programlisting>
- * float new_offset = clutter_actor_get_width (origin) + h_padding;
- *
- * clutter_actor_animate (rect, CLUTTER_EASE_OUT_CUBIC, 500,
- *                        "opacity", 255,
- *                        "@constraints.bind-x.offset", new_offset,
- *                        NULL);
+ * gboolean
+ * on_button_press (ClutterActor *origin,
+ *                  ClutterEvent *event,
+ *                  ClutterActor *rect)
+ * {
+ *   ClutterTransition *transition;
+ *   ClutterInterval *interval;
+ *
+ *   /&ast; the offset that we want to apply; this will make the actor
+ *    &ast; slide in from behind the origin and rest at the right of
+ *    &ast; the origin, plus a padding value.
+ *    &ast;/
+ *   float new_offset = clutter_actor_get_width (origin) + h_padding;
+ *
+ *   /&ast; the property we wish to animate; the "@constraints" section
+ *    &ast; tells Clutter to check inside the constraints associated
+ *    &ast; with the actor; the "bind-x" section is the name of the
+ *    &ast; constraint; and the "offset" is the name of the property
+ *    &ast; on the constraint.
+ *    &ast;/
+ *   const char *prop = "@constraints.bind-x.offset";
+ *
+ *   /&ast; create a new transition for the given property &ast;/
+ *   transition = clutter_property_transition_new (prop);
+ *
+ *   /&ast; set the easing mode and duration &ast;/
+ *   clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
+ *                                       CLUTTER_EASE_OUT_CUBIC);
+ *   clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 500);
+ *
+ *   /&ast; create the interval with the initial and final values &ast;/
+ *   interval = clutter_interval_new (G_TYPE_FLOAT, 0, new_offset);
+ *   clutter_transition_set_interval (transition, interval);
+ *
+ *   /&ast; add the transition to the actor; this causes the animation
+ *    &ast; to start. the name "offsetAnimation" can be used to retrieve
+ *    &ast; the transition later.
+ *    &ast;/
+ *   clutter_actor_add_transition (rect, "offsetAnimation", transition);
+ *
+ *   /&ast; we handled the event &ast;/
+ *   return CLUTTER_EVENT_STOP;
+ * }
  *   </programlisting></informalexample>
  * </refsect2>
  */
 
 #include <math.h>
 
-#include "cogl/cogl.h"
+#include <gobject/gvaluecollector.h>
+
+#include <cogl/cogl.h>
 
 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
 #define CLUTTER_ENABLE_EXPERIMENTAL_API
 #include "clutter-color.h"
 #include "clutter-constraint.h"
 #include "clutter-container.h"
+#include "clutter-content-private.h"
 #include "clutter-debug.h"
 #include "clutter-effect-private.h"
 #include "clutter-enum-types.h"
 #include "clutter-interval.h"
 #include "clutter-main.h"
 #include "clutter-marshal.h"
+#include "clutter-paint-nodes.h"
+#include "clutter-paint-node-private.h"
 #include "clutter-paint-volume-private.h"
 #include "clutter-private.h"
 #include "clutter-profile.h"
+#include "clutter-property-transition.h"
 #include "clutter-scriptable.h"
 #include "clutter-script-private.h"
 #include "clutter-stage-private.h"
+#include "clutter-timeline.h"
+#include "clutter-transition.h"
 #include "clutter-units.h"
 
+#include "deprecated/clutter-actor.h"
 #include "deprecated/clutter-behaviour.h"
 #include "deprecated/clutter-container.h"
 
@@ -396,9 +660,6 @@ struct _ClutterActorPrivate
   ClutterActorBox allocation;
   ClutterAllocationFlags allocation_flags;
 
-  /* depth */
-  gfloat z;
-
   /* clip, in actor coordinates */
   cairo_rectangle_t clip;
 
@@ -457,6 +718,14 @@ struct _ClutterActorPrivate
   /* delegate object used to allocate the children of this actor */
   ClutterLayoutManager *layout_manager;
 
+  /* delegate object used to paint the contents of this actor */
+  ClutterContent *content;
+
+  ClutterActorBox content_box;
+  ClutterContentGravity content_gravity;
+  ClutterScalingFilter min_filter;
+  ClutterScalingFilter mag_filter;
+
   /* used when painting, to update the paint volume */
   ClutterEffect *current_effect;
 
@@ -516,6 +785,12 @@ struct _ClutterActorPrivate
      queued without an effect. */
   guint is_dirty                    : 1;
   guint bg_color_set                : 1;
+  guint content_box_valid           : 1;
+  guint x_expand_set                : 1;
+  guint y_expand_set                : 1;
+  guint needs_compute_expand        : 1;
+  guint needs_x_expand              : 1;
+  guint needs_y_expand              : 1;
 };
 
 enum
@@ -534,6 +809,9 @@ enum
   PROP_WIDTH,
   PROP_HEIGHT,
 
+  PROP_POSITION,
+  PROP_SIZE,
+
   /* Then the rest of these size-related properties are the "actual"
    * underlying properties set or gotten by X, Y, WIDTH, HEIGHT
    */
@@ -606,6 +884,8 @@ enum
 
   PROP_LAYOUT_MANAGER,
 
+  PROP_X_EXPAND,
+  PROP_Y_EXPAND,
   PROP_X_ALIGN,
   PROP_Y_ALIGN,
   PROP_MARGIN_TOP,
@@ -619,6 +899,12 @@ enum
   PROP_FIRST_CHILD,
   PROP_LAST_CHILD,
 
+  PROP_CONTENT,
+  PROP_CONTENT_GRAVITY,
+  PROP_CONTENT_BOX,
+  PROP_MINIFICATION_FILTER,
+  PROP_MAGNIFICATION_FILTER,
+
   PROP_LAST
 };
 
@@ -649,12 +935,21 @@ enum
   ENTER_EVENT,
   LEAVE_EVENT,
   ALLOCATION_CHANGED,
+  TRANSITIONS_COMPLETED,
 
   LAST_SIGNAL
 };
 
 static guint actor_signals[LAST_SIGNAL] = { 0, };
 
+typedef struct _TransitionClosure
+{
+  ClutterActor *actor;
+  ClutterTransition *transition;
+  gchar *name;
+  gulong completed_id;
+} TransitionClosure;
+
 static void clutter_container_iface_init  (ClutterContainerIface  *iface);
 static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
 static void clutter_animatable_iface_init (ClutterAnimatableIface *iface);
@@ -711,9 +1006,14 @@ static ClutterPaintVolume *_clutter_actor_get_paint_volume_mutable (ClutterActor
 
 static guint8   clutter_actor_get_paint_opacity_internal        (ClutterActor *self);
 
+static inline void clutter_actor_set_background_color_internal (ClutterActor *self,
+                                                                const ClutterColor *color);
+
 static void on_layout_manager_changed (ClutterLayoutManager *manager,
                                        ClutterActor         *self);
 
+static inline void clutter_actor_queue_compute_expand (ClutterActor *self);
+
 /* Helper macro which translates by the anchor coord, applies the
    given transformation and then translates back */
 #define TRANSFORM_ABOUT_ANCHOR_COORD(a,m,c,_transform)  G_STMT_START { \
@@ -726,6 +1026,7 @@ static void on_layout_manager_changed (ClutterLayoutManager *manager,
 static GQuark quark_shader_data = 0;
 static GQuark quark_actor_layout_info = 0;
 static GQuark quark_actor_transform_info = 0;
+static GQuark quark_actor_animation_info = 0;
 
 G_DEFINE_TYPE_WITH_CODE (ClutterActor,
                          clutter_actor,
@@ -1337,6 +1638,17 @@ clutter_actor_show (ClutterActor *self)
 
   set_show_on_set_parent (self, TRUE);
 
+  /* if we're showing a child that needs to expand, or may
+   * expand, then we need to recompute the expand flags for
+   * its parent as well
+   */
+  if (priv->needs_compute_expand ||
+      priv->needs_x_expand ||
+      priv->needs_y_expand)
+    {
+      clutter_actor_queue_compute_expand (self);
+    }
+
   g_signal_emit (self, actor_signals[SHOW], 0);
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]);
 
@@ -1432,6 +1744,17 @@ clutter_actor_hide (ClutterActor *self)
 
   set_show_on_set_parent (self, FALSE);
 
+  /* if we're hiding a child that needs to expand, or may
+   * expand, then we need to recompute the expand flags for
+   * its parent as well
+   */
+  if (priv->needs_compute_expand ||
+      priv->needs_x_expand ||
+      priv->needs_y_expand)
+    {
+      clutter_actor_queue_compute_expand (self);
+    }
+
   g_signal_emit (self, actor_signals[HIDE], 0);
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]);
 
@@ -1900,35 +2223,50 @@ clutter_actor_notify_if_geometry_changed (ClutterActor          *self,
     {
       g_object_notify_by_pspec (obj, obj_props[PROP_X]);
       g_object_notify_by_pspec (obj, obj_props[PROP_Y]);
+      g_object_notify_by_pspec (obj, obj_props[PROP_POSITION]);
       g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
       g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
+      g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
     }
   else if (priv->needs_width_request || priv->needs_height_request)
     {
       g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
       g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
+      g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
     }
   else
     {
-      gfloat xu, yu;
-      gfloat widthu, heightu;
+      gfloat x, y;
+      gfloat width, height;
 
-      xu = priv->allocation.x1;
-      yu = priv->allocation.y1;
-      widthu = priv->allocation.x2 - priv->allocation.x1;
-      heightu = priv->allocation.y2 - priv->allocation.y1;
+      x = priv->allocation.x1;
+      y = priv->allocation.y1;
+      width = priv->allocation.x2 - priv->allocation.x1;
+      height = priv->allocation.y2 - priv->allocation.y1;
 
-      if (xu != old->x1)
-        g_object_notify_by_pspec (obj, obj_props[PROP_X]);
+      if (x != old->x1)
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_X]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_POSITION]);
+        }
 
-      if (yu != old->y1)
-        g_object_notify_by_pspec (obj, obj_props[PROP_Y]);
+      if (y != old->y1)
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_Y]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_POSITION]);
+        }
 
-      if (widthu != (old->x2 - old->x1))
-        g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
+      if (width != (old->x2 - old->x1))
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
+        }
 
-      if (heightu != (old->y2 - old->y1))
-        g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
+      if (height != (old->y2 - old->y1))
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
+        }
     }
 
   g_object_thaw_notify (obj);
@@ -1958,7 +2296,6 @@ clutter_actor_set_allocation_internal (ClutterActor           *self,
   ClutterActorPrivate *priv = self->priv;
   GObject *obj;
   gboolean x1_changed, y1_changed, x2_changed, y2_changed;
-  gboolean flags_changed;
   gboolean retval;
   ClutterActorBox old_alloc = { 0, };
 
@@ -1973,8 +2310,6 @@ clutter_actor_set_allocation_internal (ClutterActor           *self,
   x2_changed = priv->allocation.x2 != box->x2;
   y2_changed = priv->allocation.y2 != box->y2;
 
-  flags_changed = priv->allocation_flags != flags;
-
   priv->allocation = *box;
   priv->allocation_flags = flags;
 
@@ -1983,7 +2318,10 @@ clutter_actor_set_allocation_internal (ClutterActor           *self,
   priv->needs_height_request = FALSE;
   priv->needs_allocation = FALSE;
 
-  if (x1_changed || y1_changed || x2_changed || y2_changed || flags_changed)
+  if (x1_changed ||
+      y1_changed ||
+      x2_changed ||
+      y2_changed)
     {
       CLUTTER_NOTE (LAYOUT, "Allocation for '%s' changed",
                     _clutter_actor_get_debug_name (self));
@@ -1992,6 +2330,13 @@ clutter_actor_set_allocation_internal (ClutterActor           *self,
 
       g_object_notify_by_pspec (obj, obj_props[PROP_ALLOCATION]);
 
+      /* if the allocation changes, so does the content box */
+      if (priv->content != NULL)
+        {
+          priv->content_box_valid = FALSE;
+          g_object_notify_by_pspec (obj, obj_props[PROP_CONTENT_BOX]);
+        }
+
       retval = TRUE;
     }
   else
@@ -2113,9 +2458,14 @@ clutter_actor_real_allocate (ClutterActor           *self,
   clutter_actor_maybe_layout_children (self, box, flags);
 
   if (changed)
-    g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0,
-                   &priv->allocation,
-                   priv->allocation_flags);
+    {
+      ClutterActorBox signal_box = priv->allocation;
+      ClutterAllocationFlags signal_flags = priv->allocation_flags;
+
+      g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0,
+                     &signal_box,
+                     signal_flags);
+    }
 
   g_object_thaw_notify (G_OBJECT (self));
 }
@@ -2563,8 +2913,8 @@ clutter_actor_real_apply_transform (ClutterActor *self,
                              priv->allocation.y1,
                              0.0);
 
-      if (priv->z)
-        cogl_matrix_translate (transform, 0, 0, priv->z);
+      if (info->depth)
+        cogl_matrix_translate (transform, 0, 0, info->depth);
 
       /*
        * because the rotation involves translations, we must scale
@@ -2962,6 +3312,9 @@ _clutter_actor_remove_effect_internal (ClutterActor  *self,
     return;
 
   _clutter_meta_group_remove_meta (priv->effects, CLUTTER_ACTOR_META (effect));
+
+  if (_clutter_meta_group_peek_metas (priv->effects) == NULL)
+    g_clear_object (&priv->effects);
 }
 
 static gboolean
@@ -3022,8 +3375,7 @@ add_or_remove_flatten_effect (ClutterActor *self)
           /* Destroy the effect so that it will lose its fbo cache of
              the actor */
           _clutter_actor_remove_effect_internal (self, priv->flatten_effect);
-          g_object_unref (priv->flatten_effect);
-          priv->flatten_effect = NULL;
+          g_clear_object (&priv->flatten_effect);
         }
     }
 }
@@ -3034,26 +3386,6 @@ clutter_actor_real_paint (ClutterActor *actor)
   ClutterActorPrivate *priv = actor->priv;
   ClutterActor *iter;
 
-  /* paint the background color, if set */
-  if (priv->bg_color_set)
-    {
-      float width, height;
-      guint8 real_alpha;
-
-      clutter_actor_box_get_size (&priv->allocation, &width, &height);
-
-      real_alpha = clutter_actor_get_paint_opacity_internal (actor)
-                 * priv->bg_color.alpha
-                 / 255;
-
-      cogl_set_source_color4ub (priv->bg_color.red,
-                                priv->bg_color.green,
-                                priv->bg_color.blue,
-                                real_alpha);
-
-      cogl_rectangle (0, 0, width, height);
-    }
-
   for (iter = priv->first_child;
        iter != NULL;
        iter = iter->priv->next_sibling)
@@ -3070,6 +3402,66 @@ clutter_actor_real_paint (ClutterActor *actor)
     }
 }
 
+static gboolean
+clutter_actor_paint_node (ClutterActor     *actor,
+                          ClutterPaintNode *root)
+{
+  ClutterActorPrivate *priv = actor->priv;
+
+  if (root == NULL)
+    return FALSE;
+
+  if (priv->bg_color_set &&
+      !clutter_color_equal (&priv->bg_color, CLUTTER_COLOR_Transparent))
+    {
+      ClutterPaintNode *node;
+      ClutterColor bg_color;
+      ClutterActorBox box;
+
+      box.x1 = 0.f;
+      box.y1 = 0.f;
+      box.x2 = clutter_actor_box_get_width (&priv->allocation);
+      box.y2 = clutter_actor_box_get_height (&priv->allocation);
+
+      bg_color = priv->bg_color;
+      bg_color.alpha = clutter_actor_get_paint_opacity_internal (actor)
+                     * priv->bg_color.alpha
+                     / 255;
+
+      node = clutter_color_node_new (&bg_color);
+      clutter_paint_node_set_name (node, "backgroundColor");
+      clutter_paint_node_add_rectangle (node, &box);
+      clutter_paint_node_add_child (root, node);
+      clutter_paint_node_unref (node);
+    }
+
+  if (priv->content != NULL)
+    _clutter_content_paint_content (priv->content, actor, root);
+
+  if (CLUTTER_ACTOR_GET_CLASS (actor)->paint_node != NULL)
+    CLUTTER_ACTOR_GET_CLASS (actor)->paint_node (actor, root);
+
+  if (clutter_paint_node_get_n_children (root) == 0)
+    return FALSE;
+
+#ifdef CLUTTER_ENABLE_DEBUG
+  if (CLUTTER_HAS_DEBUG (PAINT))
+    {
+      /* dump the tree only if we have one */
+      _clutter_paint_node_dump_tree (root);
+    }
+#endif /* CLUTTER_ENABLE_DEBUG */
+
+  _clutter_paint_node_paint (root);
+
+#if 0
+  /* XXX: Uncomment this when we disable emitting the paint signal */
+  CLUTTER_ACTOR_GET_CLASS (actor)->paint (actor);
+#endif
+
+  return TRUE;
+}
+
 /**
  * clutter_actor_paint:
  * @self: A #ClutterActor
@@ -3345,6 +3737,22 @@ clutter_actor_continue_paint (ClutterActor *self)
     {
       if (_clutter_context_get_pick_mode () == CLUTTER_PICK_NONE)
         {
+          ClutterPaintNode *dummy;
+
+          /* XXX - this will go away in 2.0, when we can get rid of this
+           * stuff and switch to a pure retained render tree of PaintNodes
+           * for the entire frame, starting from the Stage; the paint()
+           * virtual function can then be called directly.
+           */
+          dummy = _clutter_dummy_node_new (self);
+          clutter_paint_node_set_name (dummy, "Root");
+
+          /* XXX - for 1.12, we use the return value of paint_node() to
+           * decide whether we should emit the ::paint signal.
+           */
+          clutter_actor_paint_node (self, dummy);
+          clutter_paint_node_unref (dummy);
+
           g_signal_emit (self, actor_signals[PAINT], 0);
         }
       else
@@ -3402,6 +3810,25 @@ clutter_actor_continue_paint (ClutterActor *self)
     }
 }
 
+static void
+_clutter_actor_stop_transitions (ClutterActor *self)
+{
+  const ClutterAnimationInfo *info;
+  GHashTableIter iter;
+  gpointer value;
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+  if (info->transitions == NULL)
+    return;
+
+  g_hash_table_iter_init (&iter, info->transitions);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    {
+      TransitionClosure *closure = value;
+      clutter_timeline_stop (CLUTTER_TIMELINE (closure->transition));
+    }
+}
+
 static ClutterActorTraverseVisitFlags
 invalidate_queue_redraw_entry (ClutterActor *self,
                                int           depth,
@@ -3451,9 +3878,11 @@ typedef enum {
   REMOVE_CHILD_CHECK_STATE        = 1 << 3,
   REMOVE_CHILD_FLUSH_QUEUE        = 1 << 4,
   REMOVE_CHILD_NOTIFY_FIRST_LAST  = 1 << 5,
+  REMOVE_CHILD_STOP_TRANSITIONS   = 1 << 6,
 
   /* default flags for public API */
-  REMOVE_CHILD_DEFAULT_FLAGS      = REMOVE_CHILD_DESTROY_META |
+  REMOVE_CHILD_DEFAULT_FLAGS      = REMOVE_CHILD_STOP_TRANSITIONS |
+                                    REMOVE_CHILD_DESTROY_META |
                                     REMOVE_CHILD_EMIT_PARENT_SET |
                                     REMOVE_CHILD_EMIT_ACTOR_REMOVED |
                                     REMOVE_CHILD_CHECK_STATE |
@@ -3461,7 +3890,8 @@ typedef enum {
                                     REMOVE_CHILD_NOTIFY_FIRST_LAST,
 
   /* flags for legacy/deprecated API */
-  REMOVE_CHILD_LEGACY_FLAGS       = REMOVE_CHILD_CHECK_STATE |
+  REMOVE_CHILD_LEGACY_FLAGS       = REMOVE_CHILD_STOP_TRANSITIONS |
+                                    REMOVE_CHILD_CHECK_STATE |
                                     REMOVE_CHILD_FLUSH_QUEUE |
                                     REMOVE_CHILD_EMIT_PARENT_SET |
                                     REMOVE_CHILD_NOTIFY_FIRST_LAST
@@ -3485,6 +3915,7 @@ clutter_actor_remove_child_internal (ClutterActor                 *self,
   gboolean flush_queue;
   gboolean notify_first_last;
   gboolean was_mapped;
+  gboolean stop_transitions;
 
   destroy_meta = (flags & REMOVE_CHILD_DESTROY_META) != 0;
   emit_parent_set = (flags & REMOVE_CHILD_EMIT_PARENT_SET) != 0;
@@ -3492,9 +3923,13 @@ clutter_actor_remove_child_internal (ClutterActor                 *self,
   check_state = (flags & REMOVE_CHILD_CHECK_STATE) != 0;
   flush_queue = (flags & REMOVE_CHILD_FLUSH_QUEUE) != 0;
   notify_first_last = (flags & REMOVE_CHILD_NOTIFY_FIRST_LAST) != 0;
+  stop_transitions = (flags & REMOVE_CHILD_STOP_TRANSITIONS) != 0;
 
   g_object_freeze_notify (G_OBJECT (self));
 
+  if (stop_transitions)
+    _clutter_actor_stop_transitions (child);
+
   if (destroy_meta)
     clutter_container_destroy_child_meta (CLUTTER_CONTAINER (self), child);
 
@@ -3545,6 +3980,19 @@ clutter_actor_remove_child_internal (ClutterActor                 *self,
 
   self->priv->age += 1;
 
+  /* if the child that got removed was visible and set to
+   * expand then we want to reset the parent's state in
+   * case the child was the only thing that was making it
+   * expand.
+   */
+  if (CLUTTER_ACTOR_IS_VISIBLE (child) &&
+      (child->priv->needs_compute_expand ||
+       child->priv->needs_x_expand ||
+       child->priv->needs_y_expand))
+    {
+      clutter_actor_queue_compute_expand (self);
+    }
+
   /* clutter_actor_reparent() will emit ::parent-set for us */
   if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
     g_signal_emit (child, actor_signals[PARENT_SET], 0, self);
@@ -3582,6 +4030,8 @@ static const ClutterTransformInfo default_transform_info = {
   1.0, 1.0, { 0, },     /* scale */
 
   { 0, },               /* anchor */
+
+  0.0,                  /* depth */
 };
 
 /*< private >
@@ -3699,6 +4149,46 @@ clutter_actor_set_rotation_angle_internal (ClutterActor      *self,
   clutter_actor_queue_redraw (self);
 }
 
+static inline void
+clutter_actor_set_rotation_angle (ClutterActor      *self,
+                                  ClutterRotateAxis  axis,
+                                  gdouble            angle)
+{
+  const ClutterTransformInfo *info;
+  const double *cur_angle_p = NULL;
+  GParamSpec *pspec = NULL;
+
+  info = _clutter_actor_get_transform_info_or_defaults (self);
+
+  switch (axis)
+    {
+    case CLUTTER_X_AXIS:
+      cur_angle_p = &info->rx_angle;
+      pspec = obj_props[PROP_ROTATION_ANGLE_X];
+      break;
+
+    case CLUTTER_Y_AXIS:
+      cur_angle_p = &info->ry_angle;
+      pspec = obj_props[PROP_ROTATION_ANGLE_Y];
+      break;
+
+    case CLUTTER_Z_AXIS:
+      cur_angle_p = &info->rz_angle;
+      pspec = obj_props[PROP_ROTATION_ANGLE_Z];
+      break;
+    }
+
+  g_assert (pspec != NULL);
+  g_assert (cur_angle_p != NULL);
+
+  if (_clutter_actor_get_transition (self, pspec) == NULL)
+    _clutter_actor_create_transition (self, pspec, *cur_angle_p, angle);
+  else
+    _clutter_actor_update_transition (self, pspec, angle);
+
+  clutter_actor_queue_redraw (self);
+}
+
 /*< private >
  * clutter_actor_set_rotation_center_internal:
  * @self: a #ClutterActor
@@ -3756,39 +4246,62 @@ clutter_actor_set_rotation_center_internal (ClutterActor        *self,
   clutter_actor_queue_redraw (self);
 }
 
-static inline void
-clutter_actor_set_scale_factor (ClutterActor      *self,
-                                ClutterRotateAxis  axis,
-                                gdouble            factor)
+static void
+clutter_actor_set_scale_factor_internal (ClutterActor *self,
+                                         double factor,
+                                         GParamSpec *pspec)
 {
   GObject *obj = G_OBJECT (self);
   ClutterTransformInfo *info;
 
   info = _clutter_actor_get_transform_info (self);
 
-  g_object_freeze_notify (obj);
+  if (pspec == obj_props[PROP_SCALE_X])
+    info->scale_x = factor;
+  else
+    info->scale_y = factor;
+
+  self->priv->transform_valid = FALSE;
+  clutter_actor_queue_redraw (self);
+  g_object_notify_by_pspec (obj, pspec);
+}
+
+static inline void
+clutter_actor_set_scale_factor (ClutterActor      *self,
+                                ClutterRotateAxis  axis,
+                                gdouble            factor)
+{
+  const ClutterTransformInfo *info;
+  const double *scale_p = NULL;
+  GParamSpec *pspec = NULL;
+
+  info = _clutter_actor_get_transform_info_or_defaults (self);
 
   switch (axis)
     {
     case CLUTTER_X_AXIS:
-      info->scale_x = factor;
-      g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_X]);
+      pspec = obj_props[PROP_SCALE_X];
+      scale_p = &info->scale_x;
       break;
 
     case CLUTTER_Y_AXIS:
-      info->scale_y = factor;
-      g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_Y]);
+      pspec = obj_props[PROP_SCALE_Y];
+      scale_p = &info->scale_y;
       break;
 
-    default:
-      g_assert_not_reached ();
+    case CLUTTER_Z_AXIS:
+      break;
     }
 
-  self->priv->transform_valid = FALSE;
+  g_assert (pspec != NULL);
+  g_assert (scale_p != NULL);
 
-  clutter_actor_queue_redraw (self);
+  if (_clutter_actor_get_transition (self, pspec) == NULL)
+    _clutter_actor_create_transition (self, pspec, *scale_p, factor);
+  else
+    _clutter_actor_update_transition (self, pspec, factor);
 
-  g_object_thaw_notify (obj);
+  clutter_actor_queue_redraw (self);
 }
 
 static inline void
@@ -3840,6 +4353,30 @@ clutter_actor_set_scale_center (ClutterActor      *self,
 }
 
 static inline void
+clutter_actor_set_scale_gravity (ClutterActor   *self,
+                                 ClutterGravity  gravity)
+{
+  ClutterTransformInfo *info;
+  GObject *obj;
+
+  info = _clutter_actor_get_transform_info (self);
+  obj = G_OBJECT (self);
+
+  if (gravity == CLUTTER_GRAVITY_NONE)
+    clutter_anchor_coord_set_units (&info->scale_center, 0, 0, 0);
+  else
+    clutter_anchor_coord_set_gravity (&info->scale_center, gravity);
+
+  self->priv->transform_valid = FALSE;
+
+  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_CENTER_X]);
+  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_CENTER_Y]);
+  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_GRAVITY]);
+
+  clutter_actor_queue_redraw (self);
+}
+
+static inline void
 clutter_actor_set_anchor_coord (ClutterActor      *self,
                                 ClutterRotateAxis  axis,
                                 gfloat             coord)
@@ -3908,6 +4445,17 @@ clutter_actor_set_property (GObject      *object,
       clutter_actor_set_y (actor, g_value_get_float (value));
       break;
 
+    case PROP_POSITION:
+      {
+        const ClutterPoint *pos = g_value_get_boxed (value);
+
+        if (pos != NULL)
+          clutter_actor_set_position (actor, pos->x, pos->y);
+        else
+          clutter_actor_set_fixed_position_set (actor, FALSE);
+      }
+      break;
+
     case PROP_WIDTH:
       clutter_actor_set_width (actor, g_value_get_float (value));
       break;
@@ -3916,6 +4464,17 @@ clutter_actor_set_property (GObject      *object,
       clutter_actor_set_height (actor, g_value_get_float (value));
       break;
 
+    case PROP_SIZE:
+      {
+        const ClutterSize *size = g_value_get_boxed (value);
+
+        if (size != NULL)
+          clutter_actor_set_size (actor, size->width, size->height);
+        else
+          clutter_actor_set_size (actor, -1, -1);
+      }
+      break;
+
     case PROP_FIXED_X:
       clutter_actor_set_x (actor, g_value_get_float (value));
       break;
@@ -4008,18 +4567,7 @@ clutter_actor_set_property (GObject      *object,
       break;
 
     case PROP_SCALE_GRAVITY:
-      {
-        const ClutterTransformInfo *info;
-        ClutterGravity gravity;
-
-        info = _clutter_actor_get_transform_info_or_defaults (actor);
-        gravity = g_value_get_enum (value);
-
-        clutter_actor_set_scale_with_gravity (actor,
-                                              info->scale_x,
-                                              info->scale_y,
-                                              gravity);
-      }
+      clutter_actor_set_scale_gravity (actor, g_value_get_enum (value));
       break;
 
     case PROP_CLIP:
@@ -4041,21 +4589,21 @@ clutter_actor_set_property (GObject      *object,
       break;
 
     case PROP_ROTATION_ANGLE_X:
-      clutter_actor_set_rotation_angle_internal (actor,
-                                                 CLUTTER_X_AXIS,
-                                                 g_value_get_double (value));
+      clutter_actor_set_rotation_angle (actor,
+                                        CLUTTER_X_AXIS,
+                                        g_value_get_double (value));
       break;
 
     case PROP_ROTATION_ANGLE_Y:
-      clutter_actor_set_rotation_angle_internal (actor,
-                                                 CLUTTER_Y_AXIS,
-                                                 g_value_get_double (value));
+      clutter_actor_set_rotation_angle (actor,
+                                        CLUTTER_Y_AXIS,
+                                        g_value_get_double (value));
       break;
 
     case PROP_ROTATION_ANGLE_Z:
-      clutter_actor_set_rotation_angle_internal (actor,
-                                                 CLUTTER_Z_AXIS,
-                                                 g_value_get_double (value));
+      clutter_actor_set_rotation_angle (actor,
+                                        CLUTTER_Z_AXIS,
+                                        g_value_get_double (value));
       break;
 
     case PROP_ROTATION_CENTER_X:
@@ -4125,6 +4673,14 @@ clutter_actor_set_property (GObject      *object,
       clutter_actor_set_layout_manager (actor, g_value_get_object (value));
       break;
 
+    case PROP_X_EXPAND:
+      clutter_actor_set_x_expand (actor, g_value_get_boolean (value));
+      break;
+
+    case PROP_Y_EXPAND:
+      clutter_actor_set_y_expand (actor, g_value_get_boolean (value));
+      break;
+
     case PROP_X_ALIGN:
       clutter_actor_set_x_align (actor, g_value_get_enum (value));
       break;
@@ -4153,6 +4709,26 @@ clutter_actor_set_property (GObject      *object,
       clutter_actor_set_background_color (actor, g_value_get_boxed (value));
       break;
 
+    case PROP_CONTENT:
+      clutter_actor_set_content (actor, g_value_get_object (value));
+      break;
+
+    case PROP_CONTENT_GRAVITY:
+      clutter_actor_set_content_gravity (actor, g_value_get_enum (value));
+      break;
+
+    case PROP_MINIFICATION_FILTER:
+      clutter_actor_set_content_scaling_filters (actor,
+                                                 g_value_get_enum (value),
+                                                 actor->priv->mag_filter);
+      break;
+
+    case PROP_MAGNIFICATION_FILTER:
+      clutter_actor_set_content_scaling_filters (actor,
+                                                 actor->priv->min_filter,
+                                                 g_value_get_enum (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -4178,6 +4754,17 @@ clutter_actor_get_property (GObject    *object,
       g_value_set_float (value, clutter_actor_get_y (actor));
       break;
 
+    case PROP_POSITION:
+      {
+        ClutterPoint position;
+
+        clutter_point_init (&position,
+                            clutter_actor_get_x (actor),
+                            clutter_actor_get_y (actor));
+        g_value_set_boxed (value, &position);
+      }
+      break;
+
     case PROP_WIDTH:
       g_value_set_float (value, clutter_actor_get_width (actor));
       break;
@@ -4186,12 +4773,23 @@ clutter_actor_get_property (GObject    *object,
       g_value_set_float (value, clutter_actor_get_height (actor));
       break;
 
+    case PROP_SIZE:
+      {
+        ClutterSize size;
+
+        clutter_size_init (&size,
+                           clutter_actor_get_width (actor),
+                           clutter_actor_get_height (actor));
+        g_value_set_boxed (value, &size);
+      }
+      break;
+
     case PROP_FIXED_X:
       {
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->fixed_x);
+        g_value_set_float (value, info->fixed_pos.x);
       }
       break;
 
@@ -4200,7 +4798,7 @@ clutter_actor_get_property (GObject    *object,
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->fixed_y);
+        g_value_set_float (value, info->fixed_pos.y);
       }
       break;
 
@@ -4213,7 +4811,7 @@ clutter_actor_get_property (GObject    *object,
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->min_width);
+        g_value_set_float (value, info->minimum.width);
       }
       break;
 
@@ -4222,7 +4820,7 @@ clutter_actor_get_property (GObject    *object,
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->min_height);
+        g_value_set_float (value, info->minimum.height);
       }
       break;
 
@@ -4231,7 +4829,7 @@ clutter_actor_get_property (GObject    *object,
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->natural_width);
+        g_value_set_float (value, info->natural.width);
       }
       break;
 
@@ -4240,7 +4838,7 @@ clutter_actor_get_property (GObject    *object,
         const ClutterLayoutInfo *info;
 
         info = _clutter_actor_get_layout_info_or_defaults (actor);
-        g_value_set_float (value, info->natural_height);
+        g_value_set_float (value, info->natural.height);
       }
       break;
 
@@ -4481,6 +5079,24 @@ clutter_actor_get_property (GObject    *object,
       g_value_set_object (value, priv->layout_manager);
       break;
 
+    case PROP_X_EXPAND:
+      {
+        const ClutterLayoutInfo *info;
+
+        info = _clutter_actor_get_layout_info_or_defaults (actor);
+        g_value_set_boolean (value, info->x_expand);
+      }
+      break;
+
+    case PROP_Y_EXPAND:
+      {
+        const ClutterLayoutInfo *info;
+
+        info = _clutter_actor_get_layout_info_or_defaults (actor);
+        g_value_set_boolean (value, info->y_expand);
+      }
+      break;
+
     case PROP_X_ALIGN:
       {
         const ClutterLayoutInfo *info;
@@ -4551,14 +5167,39 @@ clutter_actor_get_property (GObject    *object,
       g_value_set_object (value, priv->last_child);
       break;
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    case PROP_CONTENT:
+      g_value_set_object (value, priv->content);
       break;
-    }
-}
 
-static void
-clutter_actor_dispose (GObject *object)
+    case PROP_CONTENT_GRAVITY:
+      g_value_set_enum (value, priv->content_gravity);
+      break;
+
+    case PROP_CONTENT_BOX:
+      {
+        ClutterActorBox box = { 0, };
+
+        clutter_actor_get_content_box (actor, &box);
+        g_value_set_boxed (value, &box);
+      }
+      break;
+
+    case PROP_MINIFICATION_FILTER:
+      g_value_set_enum (value, priv->min_filter);
+      break;
+
+    case PROP_MAGNIFICATION_FILTER:
+      g_value_set_enum (value, priv->mag_filter);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_actor_dispose (GObject *object)
 {
   ClutterActor *self = CLUTTER_ACTOR (object);
   ClutterActorPrivate *priv = self->priv;
@@ -4607,8 +5248,13 @@ clutter_actor_dispose (GObject *object)
   if (priv->layout_manager != NULL)
     {
       clutter_layout_manager_set_container (priv->layout_manager, NULL);
-      g_object_unref (priv->layout_manager);
-      priv->layout_manager = NULL;
+      g_clear_object (&priv->layout_manager);
+    }
+
+  if (priv->content != NULL)
+    {
+      _clutter_content_detached (priv->content, self);
+      g_clear_object (&priv->content);
     }
 
   G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object);
@@ -4688,7 +5334,7 @@ clutter_actor_update_default_paint_volume (ClutterActor       *self,
                                            ClutterPaintVolume *volume)
 {
   ClutterActorPrivate *priv = self->priv;
-  gboolean res = FALSE;
+  gboolean res = TRUE;
 
   /* we start from the allocation */
   clutter_paint_volume_set_width (volume,
@@ -4742,6 +5388,9 @@ clutter_actor_update_default_paint_volume (ClutterActor       *self,
         {
           const ClutterPaintVolume *child_volume;
 
+          if (!CLUTTER_ACTOR_IS_MAPPED (child))
+            continue;
+
           child_volume = clutter_actor_get_transformed_paint_volume (child, self);
           if (child_volume == NULL)
             {
@@ -4785,10 +5434,13 @@ clutter_actor_real_get_paint_volume (ClutterActor       *self,
       res = FALSE;
     }
 
-  if (clutter_actor_update_default_paint_volume (self, volume))
-    return res;
+  /* update_default_paint_volume() should only fail if one of the children
+   * reported an invalid, or no, paint volume
+   */
+  if (!clutter_actor_update_default_paint_volume (self, volume))
+    return FALSE;
 
-  return FALSE;
+  return res;
 }
 
 /**
@@ -4854,9 +5506,13 @@ clutter_actor_real_destroy (ClutterActor *actor)
 {
   ClutterActorIter iter;
 
+  g_object_freeze_notify (G_OBJECT (actor));
+
   clutter_actor_iter_init (&iter, actor);
   while (clutter_actor_iter_next (&iter, NULL))
     clutter_actor_iter_destroy (&iter);
+
+  g_object_thaw_notify (G_OBJECT (actor));
 }
 
 static GObject *
@@ -4893,6 +5549,7 @@ clutter_actor_class_init (ClutterActorClass *klass)
   quark_shader_data = g_quark_from_static_string ("-clutter-actor-shader-data");
   quark_actor_layout_info = g_quark_from_static_string ("-clutter-actor-layout-info");
   quark_actor_transform_info = g_quark_from_static_string ("-clutter-actor-transform-info");
+  quark_actor_animation_info = g_quark_from_static_string ("-clutter-actor-animation-info");
 
   object_class->constructor = clutter_actor_constructor;
   object_class->set_property = clutter_actor_set_property;
@@ -4928,6 +5585,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
    * X coordinate of the actor in pixels. If written, forces a fixed
    * position for the actor. If read, returns the fixed position if any,
    * otherwise the allocation if available, otherwise 0.
+   *
+   * The #ClutterActor:x property is animatable.
    */
   obj_props[PROP_X] =
     g_param_spec_float ("x",
@@ -4935,7 +5594,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("X coordinate of the actor"),
                         -G_MAXFLOAT, G_MAXFLOAT,
                         0.0,
-                        CLUTTER_PARAM_READWRITE);
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:y:
@@ -4943,6 +5604,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
    * Y coordinate of the actor in pixels. If written, forces a fixed
    * position for the actor.  If read, returns the fixed position if
    * any, otherwise the allocation if available, otherwise 0.
+   *
+   * The #ClutterActor:y property is animatable.
    */
   obj_props[PROP_Y] =
     g_param_spec_float ("y",
@@ -4950,7 +5613,31 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("Y coordinate of the actor"),
                         -G_MAXFLOAT, G_MAXFLOAT,
                         0.0,
-                        CLUTTER_PARAM_READWRITE);
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
+
+  /**
+   * ClutterActor:position:
+   *
+   * The position of the origin of the actor.
+   *
+   * This property is a shorthand for setting and getting the
+   * #ClutterActor:x and #ClutterActor:y properties at the same
+   * time.
+   *
+   * The #ClutterActor:position property is animatable.
+   *
+   * Since: 1.12
+   */
+  obj_props[PROP_POSITION] =
+    g_param_spec_boxed ("position",
+                        P_("Position"),
+                        P_("The position of the origin of the actor"),
+                        CLUTTER_TYPE_POINT,
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:width:
@@ -4958,6 +5645,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
    * Width of the actor (in pixels). If written, forces the minimum and
    * natural size request of the actor to the given width. If read, returns
    * the allocated width if available, otherwise the width request.
+   *
+   * The #ClutterActor:width property is animatable.
    */
   obj_props[PROP_WIDTH] =
     g_param_spec_float ("width",
@@ -4965,7 +5654,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("Width of the actor"),
                         0.0, G_MAXFLOAT,
                         0.0,
-                        CLUTTER_PARAM_READWRITE);
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:height:
@@ -4973,6 +5664,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
    * Height of the actor (in pixels).  If written, forces the minimum and
    * natural size request of the actor to the given height. If read, returns
    * the allocated height if available, otherwise the height request.
+   *
+   * The #ClutterActor:height property is animatable.
    */
   obj_props[PROP_HEIGHT] =
     g_param_spec_float ("height",
@@ -4980,7 +5673,30 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("Height of the actor"),
                         0.0, G_MAXFLOAT,
                         0.0,
-                        CLUTTER_PARAM_READWRITE);
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
+
+  /**
+   * ClutterActor:size:
+   *
+   * The size of the actor.
+   *
+   * This property is a shorthand for setting and getting the
+   * #ClutterActor:width and #ClutterActor:height at the same time.
+   *
+   * The #ClutterActor:size property is animatable.
+   *
+   * Since: 1.12
+   */
+  obj_props[PROP_SIZE] =
+    g_param_spec_boxed ("size",
+                        P_("Size"),
+                        P_("The size of the actor"),
+                        CLUTTER_TYPE_SIZE,
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:fixed-x:
@@ -5185,7 +5901,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("Allocation"),
                         P_("The actor's allocation"),
                         CLUTTER_TYPE_ACTOR_BOX,
-                        CLUTTER_PARAM_READABLE);
+                        G_PARAM_READABLE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:request-mode:
@@ -5247,7 +5965,12 @@ clutter_actor_class_init (ClutterActorClass *klass)
   /**
    * ClutterActor:depth:
    *
-   * The position of the actor on the Z axis
+   * The position of the actor on the Z axis.
+   *
+   * The #ClutterActor:depth property is relative to the parent's
+   * modelview matrix.
+   *
+   * The #ClutterActor:depth property is animatable.
    *
    * Since: 0.6
    */
@@ -5257,13 +5980,17 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         P_("Position on the Z axis"),
                         -G_MAXFLOAT, G_MAXFLOAT,
                         0.0,
-                        CLUTTER_PARAM_READWRITE);
+                        G_PARAM_READWRITE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:opacity:
    *
    * Opacity of an actor, between 0 (fully transparent) and
    * 255 (fully opaque)
+   *
+   * The #ClutterActor:opacity property is animatable.
    */
   obj_props[PROP_OPACITY] =
     g_param_spec_uint ("opacity",
@@ -5271,7 +5998,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                        P_("Opacity of an actor"),
                        0, 255,
                        255,
-                       CLUTTER_PARAM_READWRITE);
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS |
+                       CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:offscreen-redirect:
@@ -5395,7 +6124,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
   /**
    * ClutterActor:scale-x:
    *
-   * The horizontal scale of the actor
+   * The horizontal scale of the actor.
+   *
+   * The #ClutterActor:scale-x property is animatable.
    *
    * Since: 0.6
    */
@@ -5405,12 +6136,16 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          P_("Scale factor on the X axis"),
                          0.0, G_MAXDOUBLE,
                          1.0,
-                         CLUTTER_PARAM_READWRITE);
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:scale-y:
    *
-   * The vertical scale of the actor
+   * The vertical scale of the actor.
+   *
+   * The #ClutterActor:scale-y property is animatable.
    *
    * Since: 0.6
    */
@@ -5420,7 +6155,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          P_("Scale factor on the Y axis"),
                          0.0, G_MAXDOUBLE,
                          1.0,
-                         CLUTTER_PARAM_READWRITE);
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:scale-center-x:
@@ -5470,7 +6207,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
   /**
    * ClutterActor:rotation-angle-x:
    *
-   * The rotation angle on the X axis
+   * The rotation angle on the X axis.
+   *
+   * The #ClutterActor:rotation-angle-x property is animatable.
    *
    * Since: 0.6
    */
@@ -5480,13 +6219,17 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          P_("The rotation angle on the X axis"),
                          -G_MAXDOUBLE, G_MAXDOUBLE,
                          0.0,
-                         CLUTTER_PARAM_READWRITE);
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:rotation-angle-y:
    *
    * The rotation angle on the Y axis
    *
+   * The #ClutterActor:rotation-angle-y property is animatable.
+   *
    * Since: 0.6
    */
   obj_props[PROP_ROTATION_ANGLE_Y] =
@@ -5495,13 +6238,17 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          P_("The rotation angle on the Y axis"),
                          -G_MAXDOUBLE, G_MAXDOUBLE,
                          0.0,
-                         CLUTTER_PARAM_READWRITE);
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:rotation-angle-z:
    *
    * The rotation angle on the Z axis
    *
+   * The #ClutterActor:rotation-angle-z property is animatable.
+   *
    * Since: 0.6
    */
   obj_props[PROP_ROTATION_ANGLE_Z] =
@@ -5510,7 +6257,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          P_("The rotation angle on the Z axis"),
                          -G_MAXDOUBLE, G_MAXDOUBLE,
                          0.0,
-                         CLUTTER_PARAM_READWRITE);
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:rotation-center-x:
@@ -5738,12 +6487,44 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          CLUTTER_TYPE_LAYOUT_MANAGER,
                          CLUTTER_PARAM_READWRITE);
 
+  /**
+   * ClutterActor:x-expand:
+   *
+   * Whether a layout manager should assign more space to the actor on
+   * the X axis.
+   *
+   * Since: 1.12
+   */
+  obj_props[PROP_X_EXPAND] =
+    g_param_spec_boolean ("x-expand",
+                          P_("X Expand"),
+                          P_("Whether extra horizontal space should be assigned to the actor"),
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS);
+
+  /**
+   * ClutterActor:y-expand:
+   *
+   * Whether a layout manager should assign more space to the actor on
+   * the Y axis.
+   *
+   * Since: 1.12
+   */
+  obj_props[PROP_Y_EXPAND] =
+    g_param_spec_boolean ("y-expand",
+                          P_("Y Expand"),
+                          P_("Whether extra vertical space should be assigned to the actor"),
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS);
 
   /**
    * ClutterActor:x-align:
    *
    * The alignment of an actor on the X axis, if the actor has been given
-   * extra space for its allocation.
+   * extra space for its allocation. See also the #ClutterActor:x-expand
+   * property.
    *
    * Since: 1.10
    */
@@ -5863,6 +6644,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
    * Paints a solid fill of the actor's allocation using the specified
    * color.
    *
+   * The #ClutterActor:background-color property is animatable.
+   *
    * Since: 1.10
    */
   obj_props[PROP_BACKGROUND_COLOR] =
@@ -5870,7 +6653,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
                               P_("Background color"),
                               P_("The actor's background color"),
                               CLUTTER_COLOR_Transparent,
-                              CLUTTER_PARAM_READWRITE);
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS |
+                              CLUTTER_PARAM_ANIMATABLE);
 
   /**
    * ClutterActor:first-child:
@@ -5900,6 +6685,86 @@ clutter_actor_class_init (ClutterActorClass *klass)
                          CLUTTER_TYPE_ACTOR,
                          CLUTTER_PARAM_READABLE);
 
+  /**
+   * ClutterActor:content:
+   *
+   * The #ClutterContent implementation that controls the content
+   * of the actor.
+   *
+   * Since: 1.10
+   */
+  obj_props[PROP_CONTENT] =
+    g_param_spec_object ("content",
+                         P_("Content"),
+                         P_("Delegate object for painting the actor's content"),
+                         CLUTTER_TYPE_CONTENT,
+                         CLUTTER_PARAM_READWRITE);
+
+  /**
+   * ClutterActor:content-gravity:
+   *
+   * The alignment that should be honoured by the #ClutterContent
+   * set with the #ClutterActor:content property.
+   *
+   * Changing the value of this property will change the bounding box of
+   * the content; you can use the #ClutterActor:content-box property to
+   * get the position and size of the content within the actor's
+   * allocation.
+   *
+   * This property is meaningful only for #ClutterContent implementations
+   * that have a preferred size, and if the preferred size is smaller than
+   * the actor's allocation.
+   *
+   * The #ClutterActor:content-gravity property is animatable.
+   *
+   * Since: 1.10
+   */
+  obj_props[PROP_CONTENT_GRAVITY] =
+    g_param_spec_enum ("content-gravity",
+                       P_("Content Gravity"),
+                       P_("Alignment of the actor's content"),
+                       CLUTTER_TYPE_CONTENT_GRAVITY,
+                       CLUTTER_CONTENT_GRAVITY_RESIZE_FILL,
+                       CLUTTER_PARAM_READWRITE);
+
+  /**
+   * ClutterActor:content-box:
+   *
+   * The bounding box for the #ClutterContent used by the actor.
+   *
+   * The value of this property is controlled by the #ClutterActor:allocation
+   * and #ClutterActor:content-gravity properties of #ClutterActor.
+   *
+   * The bounding box for the content is guaranteed to never exceed the
+   * allocation's of the actor.
+   *
+   * Since: 1.10
+   */
+  obj_props[PROP_CONTENT_BOX] =
+    g_param_spec_boxed ("content-box",
+                        P_("Content Box"),
+                        P_("The bounding box of the actor's content"),
+                        CLUTTER_TYPE_ACTOR_BOX,
+                        G_PARAM_READABLE |
+                        G_PARAM_STATIC_STRINGS |
+                        CLUTTER_PARAM_ANIMATABLE);
+
+  obj_props[PROP_MINIFICATION_FILTER] =
+    g_param_spec_enum ("minification-filter",
+                       P_("Minification Filter"),
+                       P_("The filter used when reducing the size of the content"),
+                       CLUTTER_TYPE_SCALING_FILTER,
+                       CLUTTER_SCALING_FILTER_LINEAR,
+                       CLUTTER_PARAM_READWRITE);
+
+  obj_props[PROP_MAGNIFICATION_FILTER] =
+    g_param_spec_enum ("magnification-filter",
+                       P_("Magnification Filter"),
+                       P_("The filter used when increasing the size of the content"),
+                       CLUTTER_TYPE_SCALING_FILTER,
+                       CLUTTER_SCALING_FILTER_LINEAR,
+                       CLUTTER_PARAM_READWRITE);
+
   g_object_class_install_properties (object_class, PROP_LAST, obj_props);
 
   /**
@@ -6039,7 +6904,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
   actor_signals[QUEUE_REDRAW] =
     g_signal_new (I_("queue-redraw"),
                  G_TYPE_FROM_CLASS (object_class),
-                 G_SIGNAL_RUN_LAST,
+                 G_SIGNAL_RUN_LAST |
+                  G_SIGNAL_NO_HOOKS,
                  G_STRUCT_OFFSET (ClutterActorClass, queue_redraw),
                  NULL, NULL,
                  _clutter_marshal_VOID__OBJECT,
@@ -6047,7 +6913,7 @@ clutter_actor_class_init (ClutterActorClass *klass)
                   CLUTTER_TYPE_ACTOR);
 
   /**
-   * ClutterActor::queue-relayout
+   * ClutterActor::queue-relayout:
    * @actor: the actor being queued for relayout
    *
    * The ::queue_layout signal is emitted when clutter_actor_queue_relayout()
@@ -6066,7 +6932,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
   actor_signals[QUEUE_RELAYOUT] =
     g_signal_new (I_("queue-relayout"),
                  G_TYPE_FROM_CLASS (object_class),
-                 G_SIGNAL_RUN_LAST,
+                 G_SIGNAL_RUN_LAST |
+                  G_SIGNAL_NO_HOOKS,
                  G_STRUCT_OFFSET (ClutterActorClass, queue_relayout),
                  NULL, NULL,
                  _clutter_marshal_VOID__VOID,
@@ -6353,7 +7220,8 @@ clutter_actor_class_init (ClutterActorClass *klass)
   actor_signals[PAINT] =
     g_signal_new (I_("paint"),
                   G_TYPE_FROM_CLASS (object_class),
-                  G_SIGNAL_RUN_LAST,
+                  G_SIGNAL_RUN_LAST |
+                  G_SIGNAL_NO_HOOKS,
                   G_STRUCT_OFFSET (ClutterActorClass, paint),
                   NULL, NULL,
                   _clutter_marshal_VOID__VOID,
@@ -6444,8 +7312,26 @@ clutter_actor_class_init (ClutterActorClass *klass)
                   NULL, NULL,
                   _clutter_marshal_VOID__BOXED_FLAGS,
                   G_TYPE_NONE, 2,
-                  CLUTTER_TYPE_ACTOR_BOX,
+                  CLUTTER_TYPE_ACTOR_BOX | G_SIGNAL_TYPE_STATIC_SCOPE,
                   CLUTTER_TYPE_ALLOCATION_FLAGS);
+
+  /**
+   * ClutterActor::transitions-completed:
+   * @actor: a #ClutterActor
+   *
+   * The ::transitions-completed signal is emitted once all transitions
+   * involving @actor are complete.
+   *
+   * Since: 1.10
+   */
+  actor_signals[TRANSITIONS_COMPLETED] =
+    g_signal_new (I_("transitions-completed"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  _clutter_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
 }
 
 static void
@@ -6476,6 +7362,23 @@ clutter_actor_init (ClutterActor *self)
   priv->last_paint_volume_valid = TRUE;
 
   priv->transform_valid = FALSE;
+
+  /* the default is to stretch the content, to match the
+   * current behaviour of basically all actors. also, it's
+   * the easiest thing to compute.
+   */
+  priv->content_gravity = CLUTTER_CONTENT_GRAVITY_RESIZE_FILL;
+  priv->min_filter = CLUTTER_SCALING_FILTER_LINEAR;
+  priv->mag_filter = CLUTTER_SCALING_FILTER_LINEAR;
+
+  /* this flag will be set to TRUE if the actor gets a child
+   * or if the [xy]-expand flags are explicitly set; until
+   * then, the actor does not need to expand.
+   *
+   * this also allows us to avoid computing the expand flag
+   * when building up a scene.
+   */
+  priv->needs_compute_expand = FALSE;
 }
 
 /**
@@ -7099,6 +8002,22 @@ effective_align (ClutterActorAlign    align,
   return res;
 }
 
+/*< private >
+ * _clutter_actor_get_effective_x_align:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the effective horizontal alignment, taking into
+ * consideration the text direction of @self.
+ *
+ * Return value: the effective horizontal alignment
+ */
+ClutterActorAlign
+_clutter_actor_get_effective_x_align (ClutterActor *self)
+{
+  return effective_align (clutter_actor_get_x_align (self),
+                          clutter_actor_get_text_direction (self));
+}
+
 static inline void
 adjust_for_margin (float  margin_start,
                    float  margin_end,
@@ -7313,10 +8232,10 @@ clutter_actor_get_preferred_width (ClutterActor *self,
   if (priv->min_width_set && priv->natural_width_set)
     {
       if (min_width_p != NULL)
-        *min_width_p = info->min_width + (info->margin.left + info->margin.right);
+        *min_width_p = info->minimum.width + (info->margin.left + info->margin.right);
 
       if (natural_width_p != NULL)
-        *natural_width_p = info->natural_width + (info->margin.left + info->margin.right);
+        *natural_width_p = info->natural.width + (info->margin.left + info->margin.right);
 
       return;
     }
@@ -7389,12 +8308,16 @@ clutter_actor_get_preferred_width (ClutterActor *self,
   if (!priv->min_width_set)
     request_min_width = cached_size_request->min_size;
   else
-    request_min_width = info->min_width;
+    request_min_width = info->margin.left
+                      + info->minimum.width
+                      + info->margin.right;
 
   if (!priv->natural_width_set)
     request_natural_width = cached_size_request->natural_size;
   else
-    request_natural_width = info->natural_width;
+    request_natural_width = info->margin.left
+                          + info->natural.width
+                          + info->margin.right;
 
   if (min_width_p)
     *min_width_p = request_min_width;
@@ -7446,10 +8369,10 @@ clutter_actor_get_preferred_height (ClutterActor *self,
   if (priv->min_height_set && priv->natural_height_set)
     {
       if (min_height_p != NULL)
-        *min_height_p = info->min_height + (info->margin.top + info->margin.bottom);
+        *min_height_p = info->minimum.height + (info->margin.top + info->margin.bottom);
 
       if (natural_height_p != NULL)
-        *natural_height_p = info->natural_height + (info->margin.top + info->margin.bottom);
+        *natural_height_p = info->natural.height + (info->margin.top + info->margin.bottom);
 
       return;
     }
@@ -7521,12 +8444,16 @@ clutter_actor_get_preferred_height (ClutterActor *self,
   if (!priv->min_height_set)
     request_min_height = cached_size_request->min_size;
   else
-    request_min_height = info->min_height;
+    request_min_height = info->margin.top
+                       + info->minimum.height
+                       + info->margin.bottom;
 
   if (!priv->natural_height_set)
     request_natural_height = cached_size_request->natural_size;
   else
-    request_natural_height = info->natural_height;
+    request_natural_height = info->margin.top
+                           + info->natural.height
+                           + info->margin.bottom;
 
   if (min_height_p)
     *min_height_p = request_min_height;
@@ -7641,6 +8568,16 @@ clutter_actor_update_constraints (ClutterActor    *self,
           _clutter_constraint_update_allocation (constraint,
                                                  self,
                                                  allocation);
+
+          CLUTTER_NOTE (LAYOUT,
+                        "Allocation of '%s' after constraint '%s': "
+                        "{ %.2f, %.2f, %.2f, %.2f }",
+                        _clutter_actor_get_debug_name (self),
+                        _clutter_actor_meta_get_debug_name (meta),
+                        allocation->x1,
+                        allocation->y1,
+                        allocation->x2,
+                        allocation->y2);
         }
     }
 }
@@ -7684,9 +8621,9 @@ clutter_actor_adjust_allocation (ClutterActor    *self,
       clutter_actor_get_preferred_height (self, -1,
                                           &min_height,
                                           &nat_height);
-      clutter_actor_get_preferred_height (self, alloc_height,
-                                          &min_width,
-                                          &nat_width);
+      clutter_actor_get_preferred_width (self, alloc_height,
+                                         &min_width,
+                                         &nat_width);
     }
 
 #ifdef CLUTTER_ENABLE_DEBUG
@@ -7752,6 +8689,26 @@ clutter_actor_adjust_allocation (ClutterActor    *self,
   *allocation = adj_allocation;
 }
 
+static void
+clutter_actor_allocate_internal (ClutterActor           *self,
+                                 const ClutterActorBox  *allocation,
+                                 ClutterAllocationFlags  flags)
+{
+  ClutterActorClass *klass;
+
+  CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_IN_RELAYOUT);
+
+  CLUTTER_NOTE (LAYOUT, "Calling %s::allocate()",
+                _clutter_actor_get_debug_name (self));
+
+  klass = CLUTTER_ACTOR_GET_CLASS (self);
+  klass->allocate (self, allocation, flags);
+
+  CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_IN_RELAYOUT);
+
+  clutter_actor_queue_redraw (self);
+}
+
 /**
  * clutter_actor_allocate:
  * @self: A #ClutterActor
@@ -7775,11 +8732,10 @@ clutter_actor_allocate (ClutterActor           *self,
                         const ClutterActorBox  *box,
                         ClutterAllocationFlags  flags)
 {
-  ClutterActorPrivate *priv;
-  ClutterActorClass *klass;
   ClutterActorBox old_allocation, real_allocation;
   gboolean origin_changed, child_moved, size_changed;
   gboolean stage_allocation_changed;
+  ClutterActorPrivate *priv;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
   if (G_UNLIKELY (_clutter_actor_get_stage_internal (self) == NULL))
@@ -7857,15 +8813,20 @@ clutter_actor_allocate (ClutterActor           *self,
   if (child_moved)
     flags |= CLUTTER_ABSOLUTE_ORIGIN_CHANGED;
 
-  CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_IN_RELAYOUT);
-
-  klass = CLUTTER_ACTOR_GET_CLASS (self);
-  klass->allocate (self, &real_allocation, flags);
-
-  CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_IN_RELAYOUT);
+  /* store the flags here, so that they can be propagated by the
+   * transition code
+   */
+  self->priv->allocation_flags = flags;
 
-  if (stage_allocation_changed)
-    clutter_actor_queue_redraw (self);
+  if (_clutter_actor_get_transition (self, obj_props[PROP_ALLOCATION]) == NULL)
+    {
+      _clutter_actor_create_transition (self, obj_props[PROP_ALLOCATION],
+                                        &priv->allocation,
+                                        &real_allocation);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_ALLOCATION],
+                                      &real_allocation);
 }
 
 /**
@@ -7981,9 +8942,14 @@ clutter_actor_set_allocation (ClutterActor           *self,
   clutter_actor_maybe_layout_children (self, box, flags);
 
   if (changed)
-    g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0,
-                   &priv->allocation,
-                   priv->allocation_flags);
+    {
+      ClutterActorBox signal_box = priv->allocation;
+      ClutterAllocationFlags signal_flags = priv->allocation_flags;
+
+      g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0,
+                     &signal_box,
+                     signal_flags);
+    }
 
   g_object_thaw_notify (G_OBJECT (self));
 }
@@ -8062,14 +9028,29 @@ clutter_actor_set_position (ClutterActor *self,
                            gfloat        x,
                            gfloat        y)
 {
+  ClutterPoint new_position;
+
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  g_object_freeze_notify (G_OBJECT (self));
+  clutter_point_init (&new_position, x, y);
+
+  if (_clutter_actor_get_transition (self, obj_props[PROP_POSITION]) == NULL)
+    {
+      ClutterPoint cur_position;
 
-  clutter_actor_set_x (self, x);
-  clutter_actor_set_y (self, y);
+      cur_position.x = clutter_actor_get_x (self);
+      cur_position.y = clutter_actor_get_y (self);
 
-  g_object_thaw_notify (G_OBJECT (self));
+      _clutter_actor_create_transition (self, obj_props[PROP_POSITION],
+                                        &cur_position,
+                                        &new_position);
+    }
+  else
+    _clutter_actor_update_transition (self,
+                                      obj_props[PROP_POSITION],
+                                      &new_position);
+
+  clutter_actor_queue_relayout (self);
 }
 
 /**
@@ -8142,8 +9123,8 @@ clutter_actor_move_by (ClutterActor *self,
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
   info = _clutter_actor_get_layout_info_or_defaults (self);
-  x = info->fixed_x;
-  y = info->fixed_y;
+  x = info->fixed_pos.x;
+  y = info->fixed_pos.y;
 
   clutter_actor_set_position (self, x + dx, y + dy);
 }
@@ -8167,14 +9148,14 @@ clutter_actor_set_min_width (ClutterActor *self,
 
   info = _clutter_actor_get_layout_info (self);
 
-  if (priv->min_width_set && min_width == info->min_width)
+  if (priv->min_width_set && min_width == info->minimum.width)
     return;
 
   g_object_freeze_notify (G_OBJECT (self));
 
   clutter_actor_store_old_geometry (self, &old);
 
-  info->min_width = min_width;
+  info->minimum.width = min_width;
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MIN_WIDTH]);
   clutter_actor_set_min_width_set (self, TRUE);
 
@@ -8205,14 +9186,14 @@ clutter_actor_set_min_height (ClutterActor *self,
 
   info = _clutter_actor_get_layout_info (self);
 
-  if (priv->min_height_set && min_height == info->min_height)
+  if (priv->min_height_set && min_height == info->minimum.height)
     return;
 
   g_object_freeze_notify (G_OBJECT (self));
 
   clutter_actor_store_old_geometry (self, &old);
 
-  info->min_height = min_height;
+  info->minimum.height = min_height;
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MIN_HEIGHT]);
   clutter_actor_set_min_height_set (self, TRUE);
 
@@ -8242,14 +9223,14 @@ clutter_actor_set_natural_width (ClutterActor *self,
 
   info = _clutter_actor_get_layout_info (self);
 
-  if (priv->natural_width_set && natural_width == info->natural_width)
+  if (priv->natural_width_set && natural_width == info->natural.width)
     return;
 
   g_object_freeze_notify (G_OBJECT (self));
 
   clutter_actor_store_old_geometry (self, &old);
 
-  info->natural_width = natural_width;
+  info->natural.width = natural_width;
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_NATURAL_WIDTH]);
   clutter_actor_set_natural_width_set (self, TRUE);
 
@@ -8279,14 +9260,14 @@ clutter_actor_set_natural_height (ClutterActor *self,
 
   info = _clutter_actor_get_layout_info (self);
 
-  if (priv->natural_height_set && natural_height == info->natural_height)
+  if (priv->natural_height_set && natural_height == info->natural.height)
     return;
 
   g_object_freeze_notify (G_OBJECT (self));
 
   clutter_actor_store_old_geometry (self, &old);
 
-  info->natural_height = natural_height;
+  info->natural.height = natural_height;
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_NATURAL_HEIGHT]);
   clutter_actor_set_natural_height_set (self, TRUE);
 
@@ -8485,6 +9466,22 @@ clutter_actor_set_height_internal (ClutterActor *self,
     }
 }
 
+static void
+clutter_actor_set_size_internal (ClutterActor      *self,
+                                 const ClutterSize *size)
+{
+  if (size != NULL)
+    {
+      clutter_actor_set_width_internal (self, size->width);
+      clutter_actor_set_height_internal (self, size->height);
+    }
+  else
+    {
+      clutter_actor_set_width_internal (self, -1);
+      clutter_actor_set_height_internal (self, -1);
+    }
+}
+
 /**
  * clutter_actor_set_size:
  * @self: A #ClutterActor
@@ -8507,14 +9504,47 @@ clutter_actor_set_size (ClutterActor *self,
                        gfloat        width,
                        gfloat        height)
 {
+  ClutterSize new_size;
+
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  g_object_freeze_notify (G_OBJECT (self));
+  clutter_size_init (&new_size, width, height);
+
+  if (_clutter_actor_get_transition (self, obj_props[PROP_SIZE]) == NULL)
+    {
+      /* minor optimization: if we don't have a duration then we can
+       * skip the get_size() below, to avoid the chance of going through
+       * get_preferred_width() and get_preferred_height() just to jump to
+       * a new desired size
+       */
+      if (clutter_actor_get_easing_duration (self) == 0)
+        {
+          g_object_freeze_notify (G_OBJECT (self));
 
-  clutter_actor_set_width_internal (self, width);
-  clutter_actor_set_height_internal (self, height);
+          clutter_actor_set_size_internal (self, &new_size);
 
-  g_object_thaw_notify (G_OBJECT (self));
+          g_object_thaw_notify (G_OBJECT (self));
+
+          return;
+        }
+      else
+        {
+          ClutterSize cur_size;
+
+          clutter_size_init (&cur_size,
+                             clutter_actor_get_width (self),
+                             clutter_actor_get_height (self));
+
+         _clutter_actor_create_transition (self,
+                                           obj_props[PROP_SIZE],
+                                           &cur_size,
+                                           &new_size);
+        }
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_SIZE], &new_size);
+
+  clutter_actor_queue_relayout (self);
 }
 
 /**
@@ -8835,11 +9865,35 @@ clutter_actor_set_width (ClutterActor *self,
 {
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  g_object_freeze_notify (G_OBJECT (self));
+  if (_clutter_actor_get_transition (self, obj_props[PROP_WIDTH]) == NULL)
+    {
+      float cur_size;
 
-  clutter_actor_set_width_internal (self, width);
+      /* minor optimization: if we don't have a duration
+       * then we can skip the get_width() below, to avoid
+       * the chance of going through get_preferred_width()
+       * just to jump to a new desired width.
+       */
+      if (clutter_actor_get_easing_duration (self) == 0)
+        {
+          g_object_freeze_notify (G_OBJECT (self));
 
-  g_object_thaw_notify (G_OBJECT (self));
+          clutter_actor_set_width_internal (self, width);
+
+          g_object_thaw_notify (G_OBJECT (self));
+
+          return;
+        }
+      else
+        cur_size = clutter_actor_get_width (self);
+
+      _clutter_actor_create_transition (self,
+                                        obj_props[PROP_WIDTH],
+                                        cur_size,
+                                        width);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_WIDTH], width);
 }
 
 /**
@@ -8863,45 +9917,72 @@ clutter_actor_set_height (ClutterActor *self,
 {
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  g_object_freeze_notify (G_OBJECT (self));
+  if (_clutter_actor_get_transition (self, obj_props[PROP_HEIGHT]) == NULL)
+    {
+      float cur_size;
+
+      /* see the comment in clutter_actor_set_width() above */
+      if (clutter_actor_get_easing_duration (self) == 0)
+        {
+          g_object_freeze_notify (G_OBJECT (self));
 
-  clutter_actor_set_height_internal (self, height);
+          clutter_actor_set_height_internal (self, height);
 
-  g_object_thaw_notify (G_OBJECT (self));
+          g_object_thaw_notify (G_OBJECT (self));
+
+          return;
+        }
+      else
+        cur_size = clutter_actor_get_height (self);
+
+      _clutter_actor_create_transition (self,
+                                        obj_props[PROP_HEIGHT],
+                                        cur_size,
+                                        height);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_HEIGHT], height);
 }
 
-/**
- * clutter_actor_set_x:
- * @self: a #ClutterActor
- * @x: the actor's position on the X axis
- *
- * Sets the actor's X coordinate, relative to its parent, in pixels.
- *
- * Overrides any layout manager and forces a fixed position for
- * the actor.
- *
- * Since: 0.6
- */
-void
-clutter_actor_set_x (ClutterActor *self,
-                     gfloat        x)
+static inline void
+clutter_actor_set_x_internal (ClutterActor *self,
+                              float         x)
 {
+  ClutterActorPrivate *priv = self->priv;
+  ClutterLayoutInfo *linfo;
   ClutterActorBox old = { 0, };
-  ClutterActorPrivate *priv;
-  ClutterLayoutInfo *info;
 
-  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  linfo = _clutter_actor_get_layout_info (self);
 
-  priv = self->priv;
+  if (priv->position_set && linfo->fixed_pos.x == x)
+    return;
 
-  info = _clutter_actor_get_layout_info (self);
+  clutter_actor_store_old_geometry (self, &old);
+
+  linfo->fixed_pos.x = x;
+  clutter_actor_set_fixed_position_set (self, TRUE);
+
+  clutter_actor_notify_if_geometry_changed (self, &old);
+
+  clutter_actor_queue_relayout (self);
+}
+
+static inline void
+clutter_actor_set_y_internal (ClutterActor *self,
+                              float         y)
+{
+  ClutterActorPrivate *priv = self->priv;
+  ClutterLayoutInfo *linfo;
+  ClutterActorBox old = { 0, };
+
+  linfo = _clutter_actor_get_layout_info (self);
 
-  if (priv->position_set && info->fixed_x == x)
+  if (priv->position_set && linfo->fixed_pos.y == y)
     return;
 
   clutter_actor_store_old_geometry (self, &old);
 
-  info->fixed_x = x;
+  linfo->fixed_pos.y = y;
   clutter_actor_set_fixed_position_set (self, TRUE);
 
   clutter_actor_notify_if_geometry_changed (self, &old);
@@ -8909,6 +9990,67 @@ clutter_actor_set_x (ClutterActor *self,
   clutter_actor_queue_relayout (self);
 }
 
+static void
+clutter_actor_set_position_internal (ClutterActor       *self,
+                                     const ClutterPoint *position)
+{
+  ClutterActorPrivate *priv = self->priv;
+  ClutterLayoutInfo *linfo;
+  ClutterActorBox old = { 0, };
+
+  linfo = _clutter_actor_get_layout_info (self);
+
+  if (priv->position_set &&
+      clutter_point_equals (position, &linfo->fixed_pos))
+    return;
+
+  clutter_actor_store_old_geometry (self, &old);
+
+  if (position != NULL)
+    {
+      linfo->fixed_pos = *position;
+      clutter_actor_set_fixed_position_set (self, TRUE);
+    }
+  else
+    clutter_actor_set_fixed_position_set (self, FALSE);
+
+  clutter_actor_notify_if_geometry_changed (self, &old);
+
+  clutter_actor_queue_relayout (self);
+}
+
+/**
+ * clutter_actor_set_x:
+ * @self: a #ClutterActor
+ * @x: the actor's position on the X axis
+ *
+ * Sets the actor's X coordinate, relative to its parent, in pixels.
+ *
+ * Overrides any layout manager and forces a fixed position for
+ * the actor.
+ *
+ * The #ClutterActor:x property is animatable.
+ *
+ * Since: 0.6
+ */
+void
+clutter_actor_set_x (ClutterActor *self,
+                     gfloat        x)
+{
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  if (_clutter_actor_get_transition (self, obj_props[PROP_X]) == NULL)
+    {
+      float cur_position = clutter_actor_get_x (self);
+
+      _clutter_actor_create_transition (self, obj_props[PROP_X],
+                                        cur_position,
+                                        x);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_X], x);
+}
+
 /**
  * clutter_actor_set_y:
  * @self: a #ClutterActor
@@ -8919,33 +10061,26 @@ clutter_actor_set_x (ClutterActor *self,
  * Overrides any layout manager and forces a fixed position for
  * the actor.
  *
+ * The #ClutterActor:y property is animatable.
+ *
  * Since: 0.6
  */
 void
 clutter_actor_set_y (ClutterActor *self,
                      gfloat        y)
 {
-  ClutterActorBox old = { 0, };
-  ClutterActorPrivate *priv;
-  ClutterLayoutInfo *info;
-
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  priv = self->priv;
-
-  info = _clutter_actor_get_layout_info (self);
-
-  if (priv->position_set && info->fixed_y == y)
-    return;
-
-  clutter_actor_store_old_geometry (self, &old);
-
-  info->fixed_y = y;
-  clutter_actor_set_fixed_position_set (self, TRUE);
-
-  clutter_actor_notify_if_geometry_changed (self, &old);
+  if (_clutter_actor_get_transition (self, obj_props[PROP_Y]) == NULL)
+    {
+      float cur_position = clutter_actor_get_y (self);
 
-  clutter_actor_queue_relayout (self);
+      _clutter_actor_create_transition (self, obj_props[PROP_Y],
+                                        cur_position,
+                                        y);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_Y], y);
 }
 
 /**
@@ -8987,7 +10122,7 @@ clutter_actor_get_x (ClutterActor *self)
 
           info = _clutter_actor_get_layout_info_or_defaults (self);
 
-          return info->fixed_x;
+          return info->fixed_pos.x;
         }
       else
         return 0;
@@ -9035,7 +10170,7 @@ clutter_actor_get_y (ClutterActor *self)
 
           info = _clutter_actor_get_layout_info_or_defaults (self);
 
-          return info->fixed_y;
+          return info->fixed_pos.y;
         }
       else
         return 0;
@@ -9054,6 +10189,9 @@ clutter_actor_get_y (ClutterActor *self)
  * the scale center and the anchor point. The scale center is
  * unchanged by this function and defaults to 0,0.
  *
+ * The #ClutterActor:scale-x and #ClutterActor:scale-y properties are
+ * animatable.
+ *
  * Since: 0.2
  */
 void
@@ -9083,6 +10221,9 @@ clutter_actor_set_scale (ClutterActor *self,
  * point. The center point is specified in pixels relative to the
  * anchor point (usually the top left corner of the actor).
  *
+ * The #ClutterActor:scale-x and #ClutterActor:scale-y properties
+ * are animatable.
+ *
  * Since: 1.0
  */
 void
@@ -9118,6 +10259,9 @@ clutter_actor_set_scale_full (ClutterActor *self,
  * will cause the top of the actor to remain unchanged and the rest of
  * the actor to expand left, right and downwards.
  *
+ * The #ClutterActor:scale-x and #ClutterActor:scale-y properties are
+ * animatable.
+ *
  * Since: 1.0
  */
 void
@@ -9126,35 +10270,15 @@ clutter_actor_set_scale_with_gravity (ClutterActor   *self,
                                       gdouble         scale_y,
                                       ClutterGravity  gravity)
 {
-  ClutterTransformInfo *info;
-  GObject *obj;
-
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  obj = G_OBJECT (self);
-
-  g_object_freeze_notify (obj);
-
-  info = _clutter_actor_get_transform_info (self);
-  info->scale_x = scale_x;
-  info->scale_y = scale_y;
-
-  if (gravity == CLUTTER_GRAVITY_NONE)
-    clutter_anchor_coord_set_units (&info->scale_center, 0, 0, 0);
-  else
-    clutter_anchor_coord_set_gravity (&info->scale_center, gravity);
-
-  self->priv->transform_valid = FALSE;
-
-  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_X]);
-  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_Y]);
-  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_CENTER_X]);
-  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_CENTER_Y]);
-  g_object_notify_by_pspec (obj, obj_props[PROP_SCALE_GRAVITY]);
+  g_object_freeze_notify (G_OBJECT (self));
 
-  clutter_actor_queue_redraw (self);
+  clutter_actor_set_scale_factor (self, CLUTTER_X_AXIS, scale_x);
+  clutter_actor_set_scale_factor (self, CLUTTER_Y_AXIS, scale_y);
+  clutter_actor_set_scale_gravity (self, gravity);
 
-  g_object_thaw_notify (obj);
+  g_object_thaw_notify (G_OBJECT (self));
 }
 
 /**
@@ -9243,23 +10367,11 @@ clutter_actor_get_scale_gravity (ClutterActor *self)
   return clutter_anchor_coord_get_gravity (&info->scale_center);
 }
 
-/**
- * clutter_actor_set_opacity:
- * @self: A #ClutterActor
- * @opacity: New opacity value for the actor.
- *
- * Sets the actor's opacity, with zero being completely transparent and
- * 255 (0xff) being fully opaque.
- */
-void
-clutter_actor_set_opacity (ClutterActor *self,
-                          guint8        opacity)
+static inline void
+clutter_actor_set_opacity_internal (ClutterActor *self,
+                                    guint8        opacity)
 {
-  ClutterActorPrivate *priv;
-
-  g_return_if_fail (CLUTTER_IS_ACTOR (self));
-
-  priv = self->priv;
+  ClutterActorPrivate *priv = self->priv;
 
   if (priv->opacity != opacity)
     {
@@ -9280,6 +10392,32 @@ clutter_actor_set_opacity (ClutterActor *self,
     }
 }
 
+/**
+ * clutter_actor_set_opacity:
+ * @self: A #ClutterActor
+ * @opacity: New opacity value for the actor.
+ *
+ * Sets the actor's opacity, with zero being completely transparent and
+ * 255 (0xff) being fully opaque.
+ *
+ * The #ClutterActor:opacity property is animatable.
+ */
+void
+clutter_actor_set_opacity (ClutterActor *self,
+                          guint8        opacity)
+{
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  if (_clutter_actor_get_transition (self, obj_props[PROP_OPACITY]) == NULL)
+    {
+      _clutter_actor_create_transition (self, obj_props[PROP_OPACITY],
+                                        self->priv->opacity,
+                                        opacity);
+    }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_OPACITY], opacity);
+}
+
 /*
  * clutter_actor_get_paint_opacity_internal:
  * @self: a #ClutterActor
@@ -9535,6 +10673,32 @@ clutter_actor_get_gid (ClutterActor *self)
   return self->priv->id;
 }
 
+static inline void
+clutter_actor_set_depth_internal (ClutterActor *self,
+                                  float         depth)
+{
+  ClutterTransformInfo *info;
+
+  info = _clutter_actor_get_transform_info (self);
+
+  if (info->depth != depth)
+    {
+      /* Sets Z value - XXX 2.0: should we invert? */
+      info->depth = depth;
+
+      self->priv->transform_valid = FALSE;
+
+      /* FIXME - remove this crap; sadly, there are still containers
+       * in Clutter that depend on this utter brain damage
+       */
+      clutter_container_sort_depth_order (CLUTTER_CONTAINER (self));
+
+      clutter_actor_queue_redraw (self);
+
+      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DEPTH]);
+    }
+}
+
 /**
  * clutter_actor_set_depth:
  * @self: a #ClutterActor
@@ -9549,28 +10713,22 @@ void
 clutter_actor_set_depth (ClutterActor *self,
                          gfloat        depth)
 {
-  ClutterActorPrivate *priv;
-
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  priv = self->priv;
-
-  if (priv->z != depth)
+  if (_clutter_actor_get_transition (self, obj_props[PROP_DEPTH]) == NULL)
     {
-      /* Sets Z value - XXX 2.0: should we invert? */
-      priv->z = depth;
-
-      priv->transform_valid = FALSE;
-
-      /* FIXME - remove this crap; sadly, there are still containers
-       * in Clutter that depend on this utter brain damage
-       */
-      clutter_container_sort_depth_order (CLUTTER_CONTAINER (self));
+      const ClutterTransformInfo *info;
 
-      clutter_actor_queue_redraw (self);
+      info = _clutter_actor_get_transform_info_or_defaults (self);
 
-      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DEPTH]);
+      _clutter_actor_create_transition (self, obj_props[PROP_DEPTH],
+                                        info->depth,
+                                        depth);
     }
+  else
+    _clutter_actor_update_transition (self, obj_props[PROP_DEPTH], depth);
+
+  clutter_actor_queue_redraw (self);
 }
 
 /**
@@ -9584,9 +10742,9 @@ clutter_actor_set_depth (ClutterActor *self,
 gfloat
 clutter_actor_get_depth (ClutterActor *self)
 {
-  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), -1);
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), 0.0);
 
-  return self->priv->z;
+  return _clutter_actor_get_transform_info_or_defaults (self)->depth;
 }
 
 /**
@@ -9631,7 +10789,7 @@ clutter_actor_set_rotation (ClutterActor      *self,
 
   g_object_freeze_notify (G_OBJECT (self));
 
-  clutter_actor_set_rotation_angle_internal (self, axis, angle);
+  clutter_actor_set_rotation_angle (self, axis, angle);
   clutter_actor_set_rotation_center_internal (self, axis, &v);
 
   g_object_thaw_notify (G_OBJECT (self));
@@ -9943,9 +11101,13 @@ insert_child_at_depth (ClutterActor *self,
                        gpointer      dummy G_GNUC_UNUSED)
 {
   ClutterActor *iter;
+  float child_depth;
 
   child->priv->parent = self;
 
+  child_depth =
+    _clutter_actor_get_transform_info_or_defaults (child)->depth;
+
   /* special-case the first child */
   if (self->priv->n_children == 0)
     {
@@ -9965,7 +11127,12 @@ insert_child_at_depth (ClutterActor *self,
        iter != NULL;
        iter = iter->priv->next_sibling)
     {
-      if (iter->priv->z > child->priv->z)
+      float iter_depth;
+
+      iter_depth =
+        _clutter_actor_get_transform_info_or_defaults (iter)->depth;
+
+      if (iter_depth > child_depth)
         break;
     }
 
@@ -10137,23 +11304,26 @@ typedef void (* ClutterActorAddChildFunc) (ClutterActor *parent,
                                            gpointer      data);
 
 typedef enum {
-  ADD_CHILD_CREATE_META       = 1 << 0,
-  ADD_CHILD_EMIT_PARENT_SET   = 1 << 1,
-  ADD_CHILD_EMIT_ACTOR_ADDED  = 1 << 2,
-  ADD_CHILD_CHECK_STATE       = 1 << 3,
-  ADD_CHILD_NOTIFY_FIRST_LAST = 1 << 4,
+  ADD_CHILD_CREATE_META        = 1 << 0,
+  ADD_CHILD_EMIT_PARENT_SET    = 1 << 1,
+  ADD_CHILD_EMIT_ACTOR_ADDED   = 1 << 2,
+  ADD_CHILD_CHECK_STATE        = 1 << 3,
+  ADD_CHILD_NOTIFY_FIRST_LAST  = 1 << 4,
+  ADD_CHILD_SHOW_ON_SET_PARENT = 1 << 5,
 
   /* default flags for public API */
   ADD_CHILD_DEFAULT_FLAGS    = ADD_CHILD_CREATE_META |
                                ADD_CHILD_EMIT_PARENT_SET |
                                ADD_CHILD_EMIT_ACTOR_ADDED |
                                ADD_CHILD_CHECK_STATE |
-                               ADD_CHILD_NOTIFY_FIRST_LAST,
+                               ADD_CHILD_NOTIFY_FIRST_LAST |
+                               ADD_CHILD_SHOW_ON_SET_PARENT,
 
   /* flags for legacy/deprecated API */
   ADD_CHILD_LEGACY_FLAGS     = ADD_CHILD_EMIT_PARENT_SET |
                                ADD_CHILD_CHECK_STATE |
-                               ADD_CHILD_NOTIFY_FIRST_LAST
+                               ADD_CHILD_NOTIFY_FIRST_LAST |
+                               ADD_CHILD_SHOW_ON_SET_PARENT
 } ClutterActorAddChildFlags;
 
 /*< private >
@@ -10184,6 +11354,7 @@ clutter_actor_add_child_internal (ClutterActor              *self,
   gboolean emit_parent_set, emit_actor_added;
   gboolean check_state;
   gboolean notify_first_last;
+  gboolean show_on_set_parent;
   ClutterActor *old_first_child, *old_last_child;
 
   if (child->priv->parent != NULL)
@@ -10260,6 +11431,7 @@ clutter_actor_add_child_internal (ClutterActor              *self,
   emit_actor_added = (flags & ADD_CHILD_EMIT_ACTOR_ADDED) != 0;
   check_state = (flags & ADD_CHILD_CHECK_STATE) != 0;
   notify_first_last = (flags & ADD_CHILD_NOTIFY_FIRST_LAST) != 0;
+  show_on_set_parent = (flags & ADD_CHILD_SHOW_ON_SET_PARENT) != 0;
 
   old_first_child = self->priv->first_child;
   old_last_child = self->priv->last_child;
@@ -10289,6 +11461,23 @@ clutter_actor_add_child_internal (ClutterActor              *self,
   if (self->priv->internal_child)
     CLUTTER_SET_PRIVATE_FLAGS (child, CLUTTER_INTERNAL_CHILD);
 
+  /* children may cause their parent to expand, if they are set
+   * to expand; if a child is not expanded then it cannot change
+   * its parent's state. any further change later on will queue
+   * an expand state check.
+   *
+   * this check, with the initial state of the needs_compute_expand
+   * flag set to FALSE, should avoid recomputing the expand flags
+   * state while building the actor tree.
+   */
+  if (CLUTTER_ACTOR_IS_VISIBLE (child) &&
+      (child->priv->needs_compute_expand ||
+       child->priv->needs_x_expand ||
+       child->priv->needs_y_expand))
+    {
+      clutter_actor_queue_compute_expand (self);
+    }
+
   /* clutter_actor_reparent() will emit ::parent-set for us */
   if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
     g_signal_emit (child, actor_signals[PARENT_SET], 0, NULL);
@@ -10305,7 +11494,7 @@ clutter_actor_add_child_internal (ClutterActor              *self,
       clutter_actor_set_text_direction (child, text_dir);
     }
 
-  if (child->priv->show_on_set_parent)
+  if (show_on_set_parent && child->priv->show_on_set_parent)
     clutter_actor_show (child);
 
   if (CLUTTER_ACTOR_IS_MAPPED (child))
@@ -10949,6 +12138,11 @@ clutter_actor_set_child_above_sibling (ClutterActor *self,
   if (sibling != NULL)
     g_return_if_fail (sibling->priv->parent == self);
 
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (self) ||
+      CLUTTER_ACTOR_IN_DESTRUCTION (child) ||
+      (sibling != NULL && CLUTTER_ACTOR_IN_DESTRUCTION (sibling)))
+    return;
+
   /* we don't want to change the state of child, or emit signals, or
    * regenerate ChildMeta instances here, but we still want to follow
    * the correct sequence of steps encoded in remove_child() and
@@ -10995,6 +12189,11 @@ clutter_actor_set_child_below_sibling (ClutterActor *self,
   if (sibling != NULL)
     g_return_if_fail (sibling->priv->parent == self);
 
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (self) ||
+      CLUTTER_ACTOR_IN_DESTRUCTION (child) ||
+      (sibling != NULL && CLUTTER_ACTOR_IN_DESTRUCTION (sibling)))
+    return;
+
   /* see the comment in set_child_above_sibling() */
   g_object_ref (child);
   clutter_actor_remove_child_internal (self, child, 0);
@@ -11030,6 +12229,10 @@ clutter_actor_set_child_at_index (ClutterActor *self,
   g_return_if_fail (child->priv->parent == self);
   g_return_if_fail (index_ <= self->priv->n_children);
 
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (self) ||
+      CLUTTER_ACTOR_IN_DESTRUCTION (child))
+    return;
+
   g_object_ref (child);
   clutter_actor_remove_child_internal (self, child, 0);
   clutter_actor_add_child_internal (self, child,
@@ -11553,6 +12756,23 @@ clutter_actor_set_anchor_point_from_gravity (ClutterActor   *self,
 }
 
 static void
+clutter_actor_store_content_box (ClutterActor *self,
+                                 const ClutterActorBox *box)
+{
+  if (box != NULL)
+    {
+      self->priv->content_box = *box;
+      self->priv->content_box_valid = TRUE;
+    }
+  else
+    self->priv->content_box_valid = FALSE;
+
+  clutter_actor_queue_redraw (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CONTENT_BOX]);
+}
+
+static void
 clutter_container_iface_init (ClutterContainerIface *iface)
 {
   /* we don't override anything, as ClutterContainer already has a default
@@ -11575,7 +12795,7 @@ parse_units (ClutterActor   *self,
              ParseDimension  dimension,
              JsonNode       *node)
 {
-  GValue value = { 0, };
+  GValue value = G_VALUE_INIT;
   gfloat retval = 0;
 
   if (JSON_NODE_TYPE (node) != JSON_NODE_VALUE)
@@ -11847,6 +13067,63 @@ parse_behaviours (ClutterScript *script,
   return g_slist_reverse (retval);
 }
 
+static ClutterMargin *
+parse_margin (ClutterActor *self,
+              JsonNode     *node)
+{
+  ClutterMargin *margin;
+  JsonArray *array;
+
+  if (!JSON_NODE_HOLDS_ARRAY (node))
+    {
+      g_warning ("The margin property must be an array of 1 to 4 elements");
+      return NULL;
+    }
+
+  margin = clutter_margin_new ();
+  array = json_node_get_array (node);
+  switch (json_array_get_length (array))
+    {
+    case 1:
+      margin->top = margin->right = margin->bottom = margin->left =
+        parse_units (self, 0, json_array_get_element (array, 0));
+      break;
+
+    case 2:
+      margin->top = margin->bottom =
+        parse_units (self, 0, json_array_get_element (array, 0));
+      margin->right = margin->left =
+        parse_units (self, 0, json_array_get_element (array, 1));
+      break;
+
+    case 3:
+      margin->top =
+        parse_units (self, 0, json_array_get_element (array, 0));
+      margin->right = margin->left =
+        parse_units (self, 0, json_array_get_element (array, 1));
+      margin->bottom =
+        parse_units (self, 0, json_array_get_element (array, 2));
+      break;
+
+    case 4:
+      margin->top =
+        parse_units (self, 0, json_array_get_element (array, 0));
+      margin->right =
+        parse_units (self, 0, json_array_get_element (array, 1));
+      margin->bottom =
+        parse_units (self, 0, json_array_get_element (array, 2));
+      margin->left =
+        parse_units (self, 0, json_array_get_element (array, 3));
+      break;
+
+    default:
+      g_warning ("The margin property must be an array of 1 to 4 elements");
+      clutter_margin_free (margin);
+      return NULL;
+    }
+  return margin;
+}
+
 static gboolean
 clutter_actor_parse_custom_node (ClutterScriptable *scriptable,
                                  ClutterScript     *script,
@@ -11936,6 +13213,17 @@ clutter_actor_parse_custom_node (ClutterScriptable *scriptable,
 
       retval = TRUE;
     }
+  else if (strcmp (name, "margin") == 0)
+    {
+      ClutterMargin *margin = parse_margin (actor, node);
+
+      if (margin)
+        {
+          g_value_init (value, CLUTTER_TYPE_MARGIN);
+          g_value_set_boxed (value, margin);
+          retval = TRUE;
+        }
+    }
 
   return retval;
 }
@@ -12028,6 +13316,11 @@ clutter_actor_set_custom_property (ClutterScriptable *scriptable,
 
       return;
     }
+  if (strcmp (name, "margin") == 0)
+    {
+      clutter_actor_set_margin (actor, g_value_get_boxed (value));
+      return;
+    }
 
   g_object_set_property (G_OBJECT (scriptable), name, value);
 }
@@ -12148,21 +13441,150 @@ clutter_actor_get_initial_state (ClutterAnimatable *animatable,
   g_free (p_name);
 }
 
+/*
+ * clutter_actor_set_animatable_property:
+ * @actor: a #ClutterActor
+ * @prop_id: the paramspec id
+ * @value: the value to set
+ * @pspec: the paramspec
+ *
+ * Sets values of animatable properties.
+ *
+ * This is a variant of clutter_actor_set_property() that gets called
+ * by the #ClutterAnimatable implementation of #ClutterActor for the
+ * properties with the %CLUTTER_PARAM_ANIMATABLE flag set on their
+ * #GParamSpec.
+ *
+ * Unlike the implementation of #GObjectClass.set_property(), this
+ * function will not update the interval if a transition involving an
+ * animatable property is in progress - this avoids cycles with the
+ * transition API calling the public API.
+ */
 static void
-clutter_actor_set_final_state (ClutterAnimatable *animatable,
-                               const gchar       *property_name,
-                               const GValue      *final)
+clutter_actor_set_animatable_property (ClutterActor *actor,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
 {
-  ClutterActorMeta *meta = NULL;
-  gchar *p_name = NULL;
+  GObject *obj = G_OBJECT (actor);
 
-  meta = get_meta_from_animation_property (CLUTTER_ACTOR (animatable),
+  g_object_freeze_notify (obj);
+
+  switch (prop_id)
+    {
+    case PROP_X:
+      clutter_actor_set_x_internal (actor, g_value_get_float (value));
+      break;
+
+    case PROP_Y:
+      clutter_actor_set_y_internal (actor, g_value_get_float (value));
+      break;
+
+    case PROP_POSITION:
+      clutter_actor_set_position_internal (actor, g_value_get_boxed (value));
+      break;
+
+    case PROP_WIDTH:
+      clutter_actor_set_width_internal (actor, g_value_get_float (value));
+      break;
+
+    case PROP_HEIGHT:
+      clutter_actor_set_height_internal (actor, g_value_get_float (value));
+      break;
+
+    case PROP_SIZE:
+      clutter_actor_set_size_internal (actor, g_value_get_boxed (value));
+      break;
+
+    case PROP_ALLOCATION:
+      clutter_actor_allocate_internal (actor,
+                                       g_value_get_boxed (value),
+                                       actor->priv->allocation_flags);
+      break;
+
+    case PROP_DEPTH:
+      clutter_actor_set_depth_internal (actor, g_value_get_float (value));
+      break;
+
+    case PROP_OPACITY:
+      clutter_actor_set_opacity_internal (actor, g_value_get_uint (value));
+      break;
+
+    case PROP_BACKGROUND_COLOR:
+      clutter_actor_set_background_color_internal (actor, clutter_value_get_color (value));
+      break;
+
+    case PROP_SCALE_X:
+      clutter_actor_set_scale_factor_internal (actor,
+                                               g_value_get_double (value),
+                                               pspec);
+      break;
+
+    case PROP_SCALE_Y:
+      clutter_actor_set_scale_factor_internal (actor,
+                                               g_value_get_double (value),
+                                               pspec);
+      break;
+
+    case PROP_ROTATION_ANGLE_X:
+      clutter_actor_set_rotation_angle_internal (actor,
+                                                 CLUTTER_X_AXIS,
+                                                 g_value_get_double (value));
+      break;
+
+    case PROP_ROTATION_ANGLE_Y:
+      clutter_actor_set_rotation_angle_internal (actor,
+                                                 CLUTTER_Y_AXIS,
+                                                 g_value_get_double (value));
+      break;
+
+    case PROP_ROTATION_ANGLE_Z:
+      clutter_actor_set_rotation_angle_internal (actor,
+                                                 CLUTTER_Z_AXIS,
+                                                 g_value_get_double (value));
+      break;
+
+    case PROP_CONTENT_BOX:
+      clutter_actor_store_content_box (actor, g_value_get_boxed (value));
+      break;
+
+    default:
+      g_object_set_property (obj, pspec->name, value);
+      break;
+    }
+
+  g_object_thaw_notify (obj);
+}
+
+static void
+clutter_actor_set_final_state (ClutterAnimatable *animatable,
+                               const gchar       *property_name,
+                               const GValue      *final)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (animatable);
+  ClutterActorMeta *meta = NULL;
+  gchar *p_name = NULL;
+
+  meta = get_meta_from_animation_property (actor,
                                            property_name,
                                            &p_name);
   if (meta != NULL)
     g_object_set_property (G_OBJECT (meta), p_name, final);
   else
-    g_object_set_property (G_OBJECT (animatable), property_name, final);
+    {
+      GObjectClass *obj_class = G_OBJECT_GET_CLASS (animatable);
+      GParamSpec *pspec;
+
+      pspec = g_object_class_find_property (obj_class, property_name);
+
+      if ((pspec->flags & CLUTTER_PARAM_ANIMATABLE) != 0)
+        {
+          /* XXX - I'm going to the special hell for this */
+          clutter_actor_set_animatable_property (actor, pspec->param_id, final, pspec);
+        }
+      else
+        g_object_set_property (G_OBJECT (animatable), pspec->name, final);
+    }
 
   g_free (p_name);
 }
@@ -12359,225 +13781,6 @@ clutter_actor_transform_stage_point (ClutterActor *self,
   return TRUE;
 }
 
-/*
- * ClutterGeometry
- */
-
-static ClutterGeometry*
-clutter_geometry_copy (const ClutterGeometry *geometry)
-{
-  return g_slice_dup (ClutterGeometry, geometry);
-}
-
-static void
-clutter_geometry_free (ClutterGeometry *geometry)
-{
-  if (G_LIKELY (geometry != NULL))
-    g_slice_free (ClutterGeometry, geometry);
-}
-
-/**
- * clutter_geometry_union:
- * @geometry_a: a #ClutterGeometry
- * @geometry_b: another #ClutterGeometry
- * @result: (out): location to store the result
- *
- * Find the union of two rectangles represented as #ClutterGeometry.
- *
- * Since: 1.4
- */
-void
-clutter_geometry_union (const ClutterGeometry *geometry_a,
-                        const ClutterGeometry *geometry_b,
-                        ClutterGeometry       *result)
-{
-  /* We don't try to handle rectangles that can't be represented
-   * as a signed integer box */
-  gint x_1 = MIN (geometry_a->x, geometry_b->x);
-  gint y_1 = MIN (geometry_a->y, geometry_b->y);
-  gint x_2 = MAX (geometry_a->x + (gint)geometry_a->width,
-                  geometry_b->x + (gint)geometry_b->width);
-  gint y_2 = MAX (geometry_a->y + (gint)geometry_a->height,
-                  geometry_b->y + (gint)geometry_b->height);
-  result->x = x_1;
-  result->y = y_1;
-  result->width = x_2 - x_1;
-  result->height = y_2 - y_1;
-}
-
-/**
- * clutter_geometry_intersects:
- * @geometry0: The first geometry to test
- * @geometry1: The second geometry to test
- *
- * Determines if @geometry0 and geometry1 intersect returning %TRUE if
- * they do else %FALSE.
- *
- * Return value: %TRUE of @geometry0 and geometry1 intersect else
- * %FALSE.
- *
- * Since: 1.4
- */
-gboolean
-clutter_geometry_intersects (const ClutterGeometry *geometry0,
-                             const ClutterGeometry *geometry1)
-{
-  if (geometry1->x >= (geometry0->x + (gint)geometry0->width) ||
-      geometry1->y >= (geometry0->y + (gint)geometry0->height) ||
-      (geometry1->x + (gint)geometry1->width) <= geometry0->x ||
-      (geometry1->y + (gint)geometry1->height) <= geometry0->y)
-    return FALSE;
-  else
-    return TRUE;
-}
-
-static gboolean
-clutter_geometry_progress (const GValue *a,
-                           const GValue *b,
-                           gdouble       progress,
-                           GValue       *retval)
-{
-  const ClutterGeometry *a_geom = g_value_get_boxed (a);
-  const ClutterGeometry *b_geom = g_value_get_boxed (b);
-  ClutterGeometry res = { 0, };
-  gint a_width = a_geom->width;
-  gint b_width = b_geom->width;
-  gint a_height = a_geom->height;
-  gint b_height = b_geom->height;
-
-  res.x = a_geom->x + (b_geom->x - a_geom->x) * progress;
-  res.y = a_geom->y + (b_geom->y - a_geom->y) * progress;
-
-  res.width = a_width + (b_width - a_width) * progress;
-  res.height = a_height + (b_height - a_height) * progress;
-
-  g_value_set_boxed (retval, &res);
-
-  return TRUE;
-}
-
-G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterGeometry, clutter_geometry,
-                               clutter_geometry_copy,
-                               clutter_geometry_free,
-                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_geometry_progress));
-
-/*
- * ClutterVertices
- */
-
-/**
- * clutter_vertex_new:
- * @x: X coordinate
- * @y: Y coordinate
- * @z: Z coordinate
- *
- * Creates a new #ClutterVertex for the point in 3D space
- * identified by the 3 coordinates @x, @y, @z
- *
- * Return value: the newly allocate #ClutterVertex. Use
- *   clutter_vertex_free() to free the resources
- *
- * Since: 1.0
- */
-ClutterVertex *
-clutter_vertex_new (gfloat x,
-                    gfloat y,
-                    gfloat z)
-{
-  ClutterVertex *vertex;
-
-  vertex = g_slice_new (ClutterVertex);
-  vertex->x = x;
-  vertex->y = y;
-  vertex->z = z;
-
-  return vertex;
-}
-
-/**
- * clutter_vertex_copy:
- * @vertex: a #ClutterVertex
- *
- * Copies @vertex
- *
- * Return value: a newly allocated copy of #ClutterVertex. Use
- *   clutter_vertex_free() to free the allocated resources
- *
- * Since: 1.0
- */
-ClutterVertex *
-clutter_vertex_copy (const ClutterVertex *vertex)
-{
-  if (G_LIKELY (vertex != NULL))
-    return g_slice_dup (ClutterVertex, vertex);
-
-  return NULL;
-}
-
-/**
- * clutter_vertex_free:
- * @vertex: a #ClutterVertex
- *
- * Frees a #ClutterVertex allocated using clutter_vertex_copy()
- *
- * Since: 1.0
- */
-void
-clutter_vertex_free (ClutterVertex *vertex)
-{
-  if (G_UNLIKELY (vertex != NULL))
-    g_slice_free (ClutterVertex, vertex);
-}
-
-/**
- * clutter_vertex_equal:
- * @vertex_a: a #ClutterVertex
- * @vertex_b: a #ClutterVertex
- *
- * Compares @vertex_a and @vertex_b for equality
- *
- * Return value: %TRUE if the passed #ClutterVertex are equal
- *
- * Since: 1.0
- */
-gboolean
-clutter_vertex_equal (const ClutterVertex *vertex_a,
-                      const ClutterVertex *vertex_b)
-{
-  g_return_val_if_fail (vertex_a != NULL && vertex_b != NULL, FALSE);
-
-  if (vertex_a == vertex_b)
-    return TRUE;
-
-  return vertex_a->x == vertex_b->x &&
-         vertex_a->y == vertex_b->y &&
-         vertex_a->z == vertex_b->z;
-}
-
-static gboolean
-clutter_vertex_progress (const GValue *a,
-                         const GValue *b,
-                         gdouble       progress,
-                         GValue       *retval)
-{
-  const ClutterVertex *av = g_value_get_boxed (a);
-  const ClutterVertex *bv = g_value_get_boxed (b);
-  ClutterVertex res = { 0, };
-
-  res.x = av->x + (bv->x - av->x) * progress;
-  res.y = av->y + (bv->y - av->y) * progress;
-  res.z = av->z + (bv->z - av->z) * progress;
-
-  g_value_set_boxed (retval, &res);
-
-  return TRUE;
-}
-
-G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterVertex, clutter_vertex,
-                               clutter_vertex_copy,
-                               clutter_vertex_free,
-                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_vertex_progress));
-
 /**
  * clutter_actor_is_rotated:
  * @self: a #ClutterActor
@@ -13858,6 +15061,9 @@ clutter_actor_remove_action (ClutterActor  *self,
 
   _clutter_meta_group_remove_meta (priv->actions, CLUTTER_ACTOR_META (action));
 
+  if (_clutter_meta_group_peek_metas (priv->actions) == NULL)
+    g_clear_object (&priv->actions);
+
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIONS]);
 }
 
@@ -14061,6 +15267,10 @@ clutter_actor_remove_constraint (ClutterActor      *self,
 
   _clutter_meta_group_remove_meta (priv->constraints,
                                    CLUTTER_ACTOR_META (constraint));
+
+  if (_clutter_meta_group_peek_metas (priv->constraints) == NULL)
+    g_clear_object (&priv->constraints);
+
   clutter_actor_queue_relayout (self);
 
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CONSTRAINTS]);
@@ -14759,7 +15969,7 @@ clutter_actor_has_overlaps (ClutterActor *self)
 gboolean
 clutter_actor_has_effects (ClutterActor *self)
 {
-  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), TRUE);
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
 
   if (self->priv->effects == NULL)
     return FALSE;
@@ -14781,7 +15991,7 @@ clutter_actor_has_effects (ClutterActor *self)
 gboolean
 clutter_actor_has_constraints (ClutterActor *self)
 {
-  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), TRUE);
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
 
   return self->priv->constraints != NULL;
 }
@@ -14800,7 +16010,7 @@ clutter_actor_has_constraints (ClutterActor *self)
 gboolean
 clutter_actor_has_actions (ClutterActor *self)
 {
-  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), TRUE);
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
 
   return self->priv->actions != NULL;
 }
@@ -14870,34 +16080,45 @@ _clutter_actor_foreach_child (ClutterActor           *self,
                               ClutterForeachCallback  callback,
                               gpointer                user_data)
 {
-  ClutterActorPrivate *priv = self->priv;
   ClutterActor *iter;
   gboolean cont;
 
-  for (cont = TRUE, iter = priv->first_child;
-       cont && iter != NULL;
-       iter = iter->priv->next_sibling)
+  if (self->priv->first_child == NULL)
+    return TRUE;
+
+  cont = TRUE;
+  iter = self->priv->first_child;
+
+  /* we use this form so that it's safe to change the children
+   * list while iterating it
+   */
+  while (cont && iter != NULL)
     {
+      ClutterActor *next = iter->priv->next_sibling;
+
       cont = callback (iter, user_data);
+
+      iter = next;
     }
 
   return cont;
 }
 
+#if 0
 /* For debugging purposes this gives us a simple way to print out
  * the scenegraph e.g in gdb using:
  * [|
  *   _clutter_actor_traverse (stage,
  *                            0,
- *                            _clutter_debug_print_actor_cb,
+ *                            clutter_debug_print_actor_cb,
  *                            NULL,
  *                            NULL);
  * |]
  */
-ClutterActorTraverseVisitFlags
-_clutter_debug_print_actor_cb (ClutterActor *actor,
-                               int depth,
-                               void *user_data)
+static ClutterActorTraverseVisitFlags
+clutter_debug_print_actor_cb (ClutterActor *actor,
+                              int depth,
+                              void *user_data)
 {
   g_print ("%*s%s:%p\n",
            depth * 2, "",
@@ -14906,6 +16127,7 @@ _clutter_debug_print_actor_cb (ClutterActor *actor,
 
   return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
 }
+#endif
 
 static void
 _clutter_actor_traverse_breadth (ClutterActor           *actor,
@@ -15064,7 +16286,7 @@ clutter_actor_set_layout_manager (ClutterActor         *self,
                                             G_CALLBACK (on_layout_manager_changed),
                                             self);
       clutter_layout_manager_set_container (priv->layout_manager, NULL);
-      g_object_unref (priv->layout_manager);
+      g_clear_object (&priv->layout_manager);
     }
 
   priv->layout_manager = manager;
@@ -15104,13 +16326,13 @@ clutter_actor_get_layout_manager (ClutterActor *self)
 }
 
 static const ClutterLayoutInfo default_layout_info = {
-  0.f,                          /* fixed-x */
-  0.f,                          /* fixed-y */
+  CLUTTER_POINT_INIT_ZERO,      /* fixed-pos */
   { 0, 0, 0, 0 },               /* margin */
   CLUTTER_ACTOR_ALIGN_FILL,     /* x-align */
   CLUTTER_ACTOR_ALIGN_FILL,     /* y-align */
-  0.f, 0.f,                     /* min_width, natural_width */
-  0.f, 0.f,                     /* natual_width, natural_height */
+  FALSE, FALSE,                 /* expand */
+  CLUTTER_SIZE_INIT_ZERO,       /* minimum */
+  CLUTTER_SIZE_INIT_ZERO,       /* natural */
 };
 
 static void
@@ -15283,64 +16505,6 @@ clutter_actor_get_y_align (ClutterActor *self)
   return _clutter_actor_get_layout_info_or_defaults (self)->y_align;
 }
 
-
-/**
- * clutter_margin_new:
- *
- * Creates a new #ClutterMargin.
- *
- * Return value: (transfer full): a newly allocated #ClutterMargin. Use
- *   clutter_margin_free() to free the resources associated with it when
- *   done.
- *
- * Since: 1.10
- */
-ClutterMargin *
-clutter_margin_new (void)
-{
-  return g_slice_new0 (ClutterMargin);
-}
-
-/**
- * clutter_margin_copy:
- * @margin_: a #ClutterMargin
- *
- * Creates a new #ClutterMargin and copies the contents of @margin_ into
- * the newly created structure.
- *
- * Return value: (transfer full): a copy of the #ClutterMargin.
- *
- * Since: 1.10
- */
-ClutterMargin *
-clutter_margin_copy (const ClutterMargin *margin_)
-{
-  if (G_LIKELY (margin_ != NULL))
-    return g_slice_dup (ClutterMargin, margin_);
-
-  return NULL;
-}
-
-/**
- * clutter_margin_free:
- * @margin_: a #ClutterMargin
- *
- * Frees the resources allocated by clutter_margin_new() and
- * clutter_margin_copy().
- *
- * Since: 1.10
- */
-void
-clutter_margin_free (ClutterMargin *margin_)
-{
-  if (G_LIKELY (margin_ != NULL))
-    g_slice_free (ClutterMargin, margin_);
-}
-
-G_DEFINE_BOXED_TYPE (ClutterMargin, clutter_margin,
-                     clutter_margin_copy,
-                     clutter_margin_free)
-
 /**
  * clutter_actor_set_margin:
  * @self: a #ClutterActor
@@ -15617,6 +16781,27 @@ clutter_actor_get_margin_right (ClutterActor *self)
   return _clutter_actor_get_layout_info_or_defaults (self)->margin.right;
 }
 
+static inline void
+clutter_actor_set_background_color_internal (ClutterActor *self,
+                                             const ClutterColor *color)
+{
+  ClutterActorPrivate *priv = self->priv;
+  GObject *obj;
+
+  if (priv->bg_color_set && clutter_color_equal (color, &priv->bg_color))
+    return;
+
+  obj = G_OBJECT (self);
+
+  priv->bg_color = *color;
+  priv->bg_color_set = TRUE;
+
+  clutter_actor_queue_redraw (self);
+
+  g_object_notify_by_pspec (obj, obj_props[PROP_BACKGROUND_COLOR_SET]);
+  g_object_notify_by_pspec (obj, obj_props[PROP_BACKGROUND_COLOR]);
+}
+
 /**
  * clutter_actor_set_background_color:
  * @self: a #ClutterActor
@@ -15631,6 +16816,8 @@ clutter_actor_get_margin_right (ClutterActor *self)
  * To check whether an actor has a background color, you can use the
  * #ClutterActor:background-color-set actor property.
  *
+ * The #ClutterActor:background-color property is animatable.
+ *
  * Since: 1.10
  */
 void
@@ -15638,31 +16825,34 @@ clutter_actor_set_background_color (ClutterActor       *self,
                                     const ClutterColor *color)
 {
   ClutterActorPrivate *priv;
+  GObject *obj;
+  GParamSpec *bg_color_pspec;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  obj = G_OBJECT (self);
+
   priv = self->priv;
 
   if (color == NULL)
     {
       priv->bg_color_set = FALSE;
-      g_object_notify_by_pspec (G_OBJECT (self),
-                                obj_props[PROP_BACKGROUND_COLOR_SET]);
+      g_object_notify_by_pspec (obj, obj_props[PROP_BACKGROUND_COLOR_SET]);
+      clutter_actor_queue_redraw (self);
       return;
     }
 
-  if (priv->bg_color_set && clutter_color_equal (color, &priv->bg_color))
-    return;
-
-  priv->bg_color = *color;
-  priv->bg_color_set = TRUE;
+  bg_color_pspec = obj_props[PROP_BACKGROUND_COLOR];
+  if (_clutter_actor_get_transition (self, bg_color_pspec) == NULL)
+    {
+      _clutter_actor_create_transition (self, bg_color_pspec,
+                                        &priv->bg_color,
+                                        color);
+    }
+  else
+    _clutter_actor_update_transition (self, bg_color_pspec, color);
 
   clutter_actor_queue_redraw (self);
-
-  g_object_notify_by_pspec (G_OBJECT (self),
-                            obj_props[PROP_BACKGROUND_COLOR_SET]);
-  g_object_notify_by_pspec (G_OBJECT (self),
-                            obj_props[PROP_BACKGROUND_COLOR]);
 }
 
 /**
@@ -15987,3 +17177,1482 @@ clutter_actor_iter_destroy (ClutterActorIter *iter)
       ri->age += 1;
     }
 }
+
+static const ClutterAnimationInfo default_animation_info = {
+  NULL,         /* transitions */
+  NULL,         /* states */
+  NULL,         /* cur_state */
+};
+
+static void
+clutter_animation_info_free (gpointer data)
+{
+  if (data != NULL)
+    {
+      ClutterAnimationInfo *info = data;
+
+      if (info->transitions != NULL)
+        g_hash_table_unref (info->transitions);
+
+      if (info->states != NULL)
+        g_array_unref (info->states);
+
+      g_slice_free (ClutterAnimationInfo, info);
+    }
+}
+
+const ClutterAnimationInfo *
+_clutter_actor_get_animation_info_or_defaults (ClutterActor *self)
+{
+  const ClutterAnimationInfo *res;
+  GObject *obj = G_OBJECT (self);
+
+  res = g_object_get_qdata (obj, quark_actor_animation_info);
+  if (res != NULL)
+    return res;
+
+  return &default_animation_info;
+}
+
+ClutterAnimationInfo *
+_clutter_actor_get_animation_info (ClutterActor *self)
+{
+  GObject *obj = G_OBJECT (self);
+  ClutterAnimationInfo *res;
+
+  res = g_object_get_qdata (obj, quark_actor_animation_info);
+  if (res == NULL)
+    {
+      res = g_slice_new (ClutterAnimationInfo);
+
+      *res = default_animation_info;
+
+      g_object_set_qdata_full (obj, quark_actor_animation_info,
+                               res,
+                               clutter_animation_info_free);
+    }
+
+  return res;
+}
+
+ClutterTransition *
+_clutter_actor_get_transition (ClutterActor *actor,
+                               GParamSpec   *pspec)
+{
+  const ClutterAnimationInfo *info;
+
+  info = _clutter_actor_get_animation_info_or_defaults (actor);
+
+  if (info->transitions == NULL)
+    return NULL;
+
+  return g_hash_table_lookup (info->transitions, pspec->name);
+}
+
+static void
+transition_closure_free (gpointer data)
+{
+  if (G_LIKELY (data != NULL))
+    {
+      TransitionClosure *clos = data;
+      ClutterTimeline *timeline;
+
+      timeline = CLUTTER_TIMELINE (clos->transition);
+
+      if (clutter_timeline_is_playing (timeline))
+        clutter_timeline_stop (timeline);
+
+      g_signal_handler_disconnect (clos->transition, clos->completed_id);
+
+      g_object_unref (clos->transition);
+      g_free (clos->name);
+
+      g_slice_free (TransitionClosure, clos);
+    }
+}
+
+static void
+on_transition_stopped (ClutterTransition *transition,
+                       gboolean           is_finished,
+                       TransitionClosure *clos)
+{
+  ClutterActor *actor = clos->actor;
+  ClutterAnimationInfo *info;
+
+  /* reset the caches used by animations */
+  clutter_actor_store_content_box (actor, NULL);
+
+  if (!is_finished)
+    return;
+
+  info = _clutter_actor_get_animation_info (actor);
+
+  /* we take a reference here because removing the closure
+   * will release the reference on the transition, and we
+   * want the transition to survive the signal emission;
+   * the master clock will release the last reference at
+   * the end of the frame processing.
+   */
+  g_object_ref (transition);
+  g_hash_table_remove (info->transitions, clos->name);
+
+  /* if it's the last transition then we clean up */
+  if (g_hash_table_size (info->transitions) == 0)
+    {
+      g_hash_table_unref (info->transitions);
+      info->transitions = NULL;
+
+      CLUTTER_NOTE (ANIMATION, "Transitions for '%s' completed",
+                    _clutter_actor_get_debug_name (actor));
+
+      g_signal_emit (actor, actor_signals[TRANSITIONS_COMPLETED], 0);
+    }
+}
+
+void
+_clutter_actor_update_transition (ClutterActor *actor,
+                                  GParamSpec   *pspec,
+                                  ...)
+{
+  TransitionClosure *clos;
+  ClutterTimeline *timeline;
+  ClutterInterval *interval;
+  const ClutterAnimationInfo *info;
+  va_list var_args;
+  GType ptype;
+  GValue initial = G_VALUE_INIT;
+  GValue final = G_VALUE_INIT;
+  char *error = NULL;
+
+  info = _clutter_actor_get_animation_info_or_defaults (actor);
+
+  if (info->transitions == NULL)
+    return;
+
+  clos = g_hash_table_lookup (info->transitions, pspec->name);
+  if (clos == NULL)
+    return;
+
+  timeline = CLUTTER_TIMELINE (clos->transition);
+
+  va_start (var_args, pspec);
+
+  ptype = G_PARAM_SPEC_VALUE_TYPE (pspec);
+
+  g_value_init (&initial, ptype);
+  clutter_animatable_get_initial_state (CLUTTER_ANIMATABLE (actor),
+                                        pspec->name,
+                                        &initial);
+
+  G_VALUE_COLLECT_INIT (&final, ptype, var_args, 0, &error);
+  if (error != NULL)
+    {
+      g_critical ("%s: %s", G_STRLOC, error);
+      g_free (error);
+      goto out;
+    }
+
+  interval = clutter_transition_get_interval (clos->transition);
+  clutter_interval_set_initial_value (interval, &initial);
+  clutter_interval_set_final_value (interval, &final);
+
+  /* if we're updating with an easing duration of zero milliseconds,
+   * we just jump the timeline to the end and let it run its course
+   */
+  if (info->cur_state != NULL &&
+      info->cur_state->easing_duration != 0)
+    {
+      guint cur_duration = clutter_timeline_get_duration (timeline);
+      ClutterAnimationMode cur_mode =
+        clutter_timeline_get_progress_mode (timeline);
+
+      if (cur_duration != info->cur_state->easing_duration)
+        clutter_timeline_set_duration (timeline, info->cur_state->easing_duration);
+
+      if (cur_mode != info->cur_state->easing_mode)
+        clutter_timeline_set_progress_mode (timeline, info->cur_state->easing_mode);
+
+      clutter_timeline_rewind (timeline);
+    }
+  else
+    {
+      guint duration = clutter_timeline_get_duration (timeline);
+
+      clutter_timeline_advance (timeline, duration);
+    }
+
+out:
+  g_value_unset (&initial);
+  g_value_unset (&final);
+
+  va_end (var_args);
+}
+
+/*< private >*
+ * _clutter_actor_create_transition:
+ * @actor: a #ClutterActor
+ * @pspec: the property used for the transition
+ * @...: initial and final state
+ *
+ * Creates a #ClutterTransition for the property represented by @pspec.
+ *
+ * Return value: a #ClutterTransition
+ */
+ClutterTransition *
+_clutter_actor_create_transition (ClutterActor *actor,
+                                  GParamSpec   *pspec,
+                                  ...)
+{
+  ClutterAnimationInfo *info;
+  ClutterTransition *res = NULL;
+  gboolean call_restore = FALSE;
+  TransitionClosure *clos;
+  va_list var_args;
+
+  info = _clutter_actor_get_animation_info (actor);
+
+  /* XXX - this will go away in 2.0
+   *
+   * if no state has been pushed, we assume that the easing state is
+   * in "compatibility mode": all transitions have a duration of 0
+   * msecs, which means that they happen immediately. in Clutter 2.0
+   * this will turn into a g_assert(info->states != NULL), as every
+   * actor will start with a predefined easing state
+   */
+  if (info->states == NULL)
+    {
+      clutter_actor_save_easing_state (actor);
+      clutter_actor_set_easing_duration (actor, 0);
+      call_restore = TRUE;
+    }
+
+  if (info->transitions == NULL)
+    info->transitions = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                               NULL,
+                                               transition_closure_free);
+
+  va_start (var_args, pspec);
+
+  clos = g_hash_table_lookup (info->transitions, pspec->name);
+  if (clos == NULL)
+    {
+      ClutterTimeline *timeline;
+      ClutterInterval *interval;
+      GValue initial = G_VALUE_INIT;
+      GValue final = G_VALUE_INIT;
+      GType ptype;
+      char *error;
+
+      ptype = G_PARAM_SPEC_VALUE_TYPE (pspec);
+
+      G_VALUE_COLLECT_INIT (&initial, ptype,
+                            var_args, 0,
+                            &error);
+      if (error != NULL)
+        {
+          g_critical ("%s: %s", G_STRLOC, error);
+          g_free (error);
+          goto out;
+        }
+
+      G_VALUE_COLLECT_INIT (&final, ptype,
+                            var_args, 0,
+                            &error);
+
+      if (error != NULL)
+        {
+          g_critical ("%s: %s", G_STRLOC, error);
+          g_value_unset (&initial);
+          g_free (error);
+          goto out;
+        }
+
+      /* if the current easing state has a duration of 0, then we don't
+       * bother to create the transition, and we just set the final value
+       * directly on the actor; we don't go through the Animatable
+       * interface because we know we got here through an animatable
+       * property.
+       */
+      if (info->cur_state->easing_duration == 0)
+        {
+          clutter_actor_set_animatable_property (actor,
+                                                 pspec->param_id,
+                                                 &final,
+                                                 pspec);
+          g_value_unset (&initial);
+          g_value_unset (&final);
+
+          goto out;
+        }
+
+      interval = clutter_interval_new_with_values (ptype, &initial, &final);
+
+      g_value_unset (&initial);
+      g_value_unset (&final);
+
+      res = clutter_property_transition_new (pspec->name);
+
+      clutter_transition_set_interval (res, interval);
+      clutter_transition_set_remove_on_complete (res, TRUE);
+
+      timeline = CLUTTER_TIMELINE (res);
+      clutter_timeline_set_delay (timeline, info->cur_state->easing_delay);
+      clutter_timeline_set_duration (timeline, info->cur_state->easing_duration);
+      clutter_timeline_set_progress_mode (timeline, info->cur_state->easing_mode);
+
+      /* this will start the transition as well */
+      clutter_actor_add_transition (actor, pspec->name, res);
+
+      /* the actor now owns the transition */
+      g_object_unref (res);
+    }
+  else
+    res = clos->transition;
+
+out:
+  if (call_restore)
+    clutter_actor_restore_easing_state (actor);
+
+  va_end (var_args);
+
+  return res;
+}
+
+/**
+ * clutter_actor_add_transition:
+ * @self: a #ClutterActor
+ * @name: the name of the transition to add
+ * @transition: the #ClutterTransition to add
+ *
+ * Adds a @transition to the #ClutterActor's list of animations.
+ *
+ * The @name string is a per-actor unique identifier of the @transition: only
+ * one #ClutterTransition can be associated to the specified @name.
+ *
+ * The @transition will be started once added.
+ *
+ * This function will take a reference on the @transition.
+ *
+ * This function is usually called implicitly when modifying an animatable
+ * property.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_add_transition (ClutterActor      *self,
+                              const char        *name,
+                              ClutterTransition *transition)
+{
+  ClutterTimeline *timeline;
+  TransitionClosure *clos;
+  ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (name != NULL);
+  g_return_if_fail (CLUTTER_IS_TRANSITION (transition));
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->transitions == NULL)
+    info->transitions = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                               NULL,
+                                               transition_closure_free);
+
+  if (g_hash_table_lookup (info->transitions, name) != NULL)
+    {
+      g_warning ("A transition with name '%s' already exists for "
+                 "the actor '%s'",
+                 name,
+                 _clutter_actor_get_debug_name (self));
+      return;
+    }
+
+  clutter_transition_set_animatable (transition, CLUTTER_ANIMATABLE (self));
+
+  timeline = CLUTTER_TIMELINE (transition);
+
+  clos = g_slice_new (TransitionClosure);
+  clos->actor = self;
+  clos->transition = g_object_ref (transition);
+  clos->name = g_strdup (name);
+  clos->completed_id = g_signal_connect (timeline, "stopped",
+                                         G_CALLBACK (on_transition_stopped),
+                                         clos);
+
+  CLUTTER_NOTE (ANIMATION,
+                "Adding transition '%s' [%p] to actor '%s'",
+                clos->name,
+                clos->transition,
+                _clutter_actor_get_debug_name (self));
+
+  g_hash_table_insert (info->transitions, clos->name, clos);
+  clutter_timeline_start (timeline);
+}
+
+/**
+ * clutter_actor_remove_transition:
+ * @self: a #ClutterActor
+ * @name: the name of the transition to remove
+ *
+ * Removes the transition stored inside a #ClutterActor using @name
+ * identifier.
+ *
+ * If the transition is currently in progress, it will be stopped.
+ *
+ * This function releases the reference acquired when the transition
+ * was added to the #ClutterActor.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_remove_transition (ClutterActor *self,
+                                 const char   *name)
+{
+  const ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (name != NULL);
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+
+  if (info->transitions == NULL)
+    return;
+
+  g_hash_table_remove (info->transitions, name);
+}
+
+/**
+ * clutter_actor_remove_all_transitions:
+ * @self: a #ClutterActor
+ *
+ * Removes all transitions associated to @self.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_remove_all_transitions (ClutterActor *self)
+{
+  const ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+  if (info->transitions == NULL)
+    return;
+
+  g_hash_table_remove_all (info->transitions);
+}
+
+/**
+ * clutter_actor_set_easing_duration:
+ * @self: a #ClutterActor
+ * @msecs: the duration of the easing, or %NULL
+ *
+ * Sets the duration of the tweening for animatable properties
+ * of @self for the current easing state.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_easing_duration (ClutterActor *self,
+                                   guint         msecs)
+{
+  ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->cur_state == NULL)
+    {
+      g_warning ("You must call clutter_actor_save_easing_state() prior "
+                 "to calling clutter_actor_set_easing_duration().");
+      return;
+    }
+
+  if (info->cur_state->easing_duration != msecs)
+    info->cur_state->easing_duration = msecs;
+}
+
+/**
+ * clutter_actor_get_easing_duration:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the duration of the tweening for animatable
+ * properties of @self for the current easing state.
+ *
+ * Return value: the duration of the tweening, in milliseconds
+ *
+ * Since: 1.10
+ */
+guint
+clutter_actor_get_easing_duration (ClutterActor *self)
+{
+  const ClutterAnimationInfo *info;
+
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), 0);
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+
+  if (info->cur_state != NULL)
+    return info->cur_state->easing_duration;
+
+  return 0;
+}
+
+/**
+ * clutter_actor_set_easing_mode:
+ * @self: a #ClutterActor
+ * @mode: an easing mode, excluding %CLUTTER_CUSTOM_MODE
+ *
+ * Sets the easing mode for the tweening of animatable properties
+ * of @self.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_easing_mode (ClutterActor         *self,
+                               ClutterAnimationMode  mode)
+{
+  ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (mode != CLUTTER_CUSTOM_MODE);
+  g_return_if_fail (mode < CLUTTER_ANIMATION_LAST);
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->cur_state == NULL)
+    {
+      g_warning ("You must call clutter_actor_save_easing_state() prior "
+                 "to calling clutter_actor_set_easing_mode().");
+      return;
+    }
+
+  if (info->cur_state->easing_mode != mode)
+    info->cur_state->easing_mode = mode;
+}
+
+/**
+ * clutter_actor_get_easing_mode:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the easing mode for the tweening of animatable properties
+ * of @self for the current easing state.
+ *
+ * Return value: an easing mode
+ *
+ * Since: 1.10
+ */
+ClutterAnimationMode
+clutter_actor_get_easing_mode (ClutterActor *self)
+{
+  const ClutterAnimationInfo *info;
+
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), CLUTTER_EASE_OUT_CUBIC);
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+
+  if (info->cur_state != NULL)
+    return info->cur_state->easing_mode;
+
+  return CLUTTER_EASE_OUT_CUBIC;
+}
+
+/**
+ * clutter_actor_set_easing_delay:
+ * @self: a #ClutterActor
+ * @msecs: the delay before the start of the tweening, in milliseconds
+ *
+ * Sets the delay that should be applied before tweening animatable
+ * properties.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_easing_delay (ClutterActor *self,
+                                guint         msecs)
+{
+  ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->cur_state == NULL)
+    {
+      g_warning ("You must call clutter_actor_save_easing_state() prior "
+                 "to calling clutter_actor_set_easing_delay().");
+      return;
+    }
+
+  if (info->cur_state->easing_delay != msecs)
+    info->cur_state->easing_delay = msecs;
+}
+
+/**
+ * clutter_actor_get_easing_delay:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the delay that should be applied when tweening animatable
+ * properties.
+ *
+ * Return value: a delay, in milliseconds
+ *
+ * Since: 1.10
+ */
+guint
+clutter_actor_get_easing_delay (ClutterActor *self)
+{
+  const ClutterAnimationInfo *info;
+
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), 0);
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+
+  if (info->cur_state != NULL)
+    return info->cur_state->easing_delay;
+
+  return 0;
+}
+
+/**
+ * clutter_actor_get_transition:
+ * @self: a #ClutterActor
+ * @name: the name of the transition
+ *
+ * Retrieves the #ClutterTransition of a #ClutterActor by using the
+ * transition @name.
+ *
+ * Transitions created for animatable properties use the name of the
+ * property itself, for instance the code below:
+ *
+ * |[
+ *   clutter_actor_set_easing_duration (actor, 1000);
+ *   clutter_actor_set_rotation (actor, CLUTTER_Y_AXIS, 360.0, x, y, z);
+ *
+ *   transition = clutter_actor_get_transition (actor, "rotation-angle-y");
+ *   g_signal_connect (transition, "stopped",
+ *                     G_CALLBACK (on_transition_stopped),
+ *                     actor);
+ * ]|
+ *
+ * will call the <function>on_transition_stopped</function> callback when
+ * the transition is finished.
+ *
+ * Return value: (transfer none): a #ClutterTransition, or %NULL is none
+ *   was found to match the passed name; the returned instance is owned
+ *   by Clutter and it should not be freed
+ *
+ * Since: 1.10
+ */
+ClutterTransition *
+clutter_actor_get_transition (ClutterActor *self,
+                              const char   *name)
+{
+  TransitionClosure *clos;
+  const ClutterAnimationInfo *info;
+
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  info = _clutter_actor_get_animation_info_or_defaults (self);
+  if (info->transitions == NULL)
+    return NULL;
+
+  clos = g_hash_table_lookup (info->transitions, name);
+  if (clos == NULL)
+    return NULL;
+
+  return clos->transition;
+}
+
+/**
+ * clutter_actor_save_easing_state:
+ * @self: a #ClutterActor
+ *
+ * Saves the current easing state for animatable properties, and creates
+ * a new state with the default values for easing mode and duration.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_save_easing_state (ClutterActor *self)
+{
+  ClutterAnimationInfo *info;
+  AState new_state;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->states == NULL)
+    info->states = g_array_new (FALSE, FALSE, sizeof (AState));
+
+  new_state.easing_mode = CLUTTER_EASE_OUT_CUBIC;
+  new_state.easing_duration = 250;
+  new_state.easing_delay = 0;
+
+  g_array_append_val (info->states, new_state);
+
+  info->cur_state = &g_array_index (info->states, AState, info->states->len - 1);
+}
+
+/**
+ * clutter_actor_restore_easing_state:
+ * @self: a #ClutterActor
+ *
+ * Restores the easing state as it was prior to a call to
+ * clutter_actor_save_easing_state().
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_restore_easing_state (ClutterActor *self)
+{
+  ClutterAnimationInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  info = _clutter_actor_get_animation_info (self);
+
+  if (info->states == NULL)
+    {
+      g_critical ("The function clutter_actor_restore_easing_state() has "
+                  "called without a previous call to "
+                  "clutter_actor_save_easing_state().");
+      return;
+    }
+
+  g_array_remove_index (info->states, info->states->len - 1);
+
+  if (info->states->len > 0)
+    info->cur_state = &g_array_index (info->states, AState, info->states->len - 1);
+  else
+    {
+      g_array_unref (info->states);
+      info->states = NULL;
+      info->cur_state = NULL;
+    }
+}
+
+/**
+ * clutter_actor_set_content:
+ * @self: a #ClutterActor
+ * @content: (allow-none): a #ClutterContent, or %NULL
+ *
+ * Sets the contents of a #ClutterActor.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_content (ClutterActor   *self,
+                           ClutterContent *content)
+{
+  ClutterActorPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (content == NULL || CLUTTER_IS_CONTENT (content));
+
+  priv = self->priv;
+
+  if (priv->content != NULL)
+    {
+      _clutter_content_detached (priv->content, self);
+      g_clear_object (&priv->content);
+    }
+
+  priv->content = content;
+
+  if (priv->content != NULL)
+    {
+      g_object_ref (priv->content);
+      _clutter_content_attached (priv->content, self);
+    }
+
+  /* given that the content is always painted within the allocation,
+   * we only need to queue a redraw here
+   */
+  clutter_actor_queue_redraw (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CONTENT]);
+
+  /* if the content gravity is not resize-fill, and the new content has a
+   * different preferred size than the previous one, then the content box
+   * may have been changed. since we compute that lazily, we just notify
+   * here, and let whomever watches :content-box do whatever they need to
+   * do.
+   */
+  if (priv->content_gravity != CLUTTER_CONTENT_GRAVITY_RESIZE_FILL)
+    g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CONTENT_BOX]);
+}
+
+/**
+ * clutter_actor_get_content:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the contents of @self.
+ *
+ * Return value: (transfer none): a pointer to the #ClutterContent instance,
+ *   or %NULL if none was set
+ *
+ * Since: 1.10
+ */
+ClutterContent *
+clutter_actor_get_content (ClutterActor *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
+
+  return self->priv->content;
+}
+
+/**
+ * clutter_actor_set_content_gravity:
+ * @self: a #ClutterActor
+ * @gravity: the #ClutterContentGravity
+ *
+ * Sets the gravity of the #ClutterContent used by @self.
+ *
+ * See the description of the #ClutterActor:content-gravity property for
+ * more information.
+ *
+ * The #ClutterActor:content-gravity property is animatable.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_content_gravity (ClutterActor *self,
+                                   ClutterContentGravity  gravity)
+{
+  ClutterActorPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  priv = self->priv;
+
+  if (priv->content_gravity == gravity)
+    return;
+
+  priv->content_box_valid = FALSE;
+
+  if (_clutter_actor_get_transition (self, obj_props[PROP_CONTENT_BOX]) == NULL)
+    {
+      ClutterActorBox from_box, to_box;
+
+      clutter_actor_get_content_box (self, &from_box);
+
+      priv->content_gravity = gravity;
+
+      clutter_actor_get_content_box (self, &to_box);
+
+      _clutter_actor_create_transition (self, obj_props[PROP_CONTENT_BOX],
+                                        &from_box,
+                                        &to_box);
+    }
+  else
+    {
+      ClutterActorBox to_box;
+
+      priv->content_gravity = gravity;
+
+      clutter_actor_get_content_box (self, &to_box);
+
+      _clutter_actor_update_transition (self, obj_props[PROP_CONTENT_BOX],
+                                        &to_box);
+    }
+
+  clutter_actor_queue_redraw (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CONTENT_GRAVITY]);
+}
+
+/**
+ * clutter_actor_get_content_gravity:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the content gravity as set using
+ * clutter_actor_get_content_gravity().
+ *
+ * Return value: the content gravity
+ *
+ * Since: 1.10
+ */
+ClutterContentGravity
+clutter_actor_get_content_gravity (ClutterActor *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self),
+                        CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
+
+  return self->priv->content_gravity;
+}
+
+/**
+ * clutter_actor_get_content_box:
+ * @self: a #ClutterActor
+ * @box: (out caller-allocates): the return location for the bounding
+ *   box for the #ClutterContent
+ *
+ * Retrieves the bounding box for the #ClutterContent of @self.
+ *
+ * The bounding box is relative to the actor's allocation.
+ *
+ * If no #ClutterContent is set for @self, or if @self has not been
+ * allocated yet, then the result is undefined.
+ *
+ * The content box is guaranteed to be, at most, as big as the allocation
+ * of the #ClutterActor.
+ *
+ * If the #ClutterContent used by the actor has a preferred size, then
+ * it is possible to modify the content box by using the
+ * #ClutterActor:content-gravity property.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_get_content_box (ClutterActor    *self,
+                               ClutterActorBox *box)
+{
+  ClutterActorPrivate *priv;
+  gfloat content_w, content_h;
+  gfloat alloc_w, alloc_h;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (box != NULL);
+
+  priv = self->priv;
+
+  box->x1 = 0.f;
+  box->y1 = 0.f;
+  box->x2 = priv->allocation.x2 - priv->allocation.x1;
+  box->y2 = priv->allocation.y2 - priv->allocation.y1;
+
+  if (priv->content_box_valid)
+    {
+      *box = priv->content_box;
+      return;
+    }
+
+  /* no need to do any more work */
+  if (priv->content_gravity == CLUTTER_CONTENT_GRAVITY_RESIZE_FILL)
+    return;
+
+  if (priv->content == NULL)
+    return;
+
+  /* if the content does not have a preferred size then there is
+   * no point in computing the content box
+   */
+  if (!clutter_content_get_preferred_size (priv->content,
+                                           &content_w,
+                                           &content_h))
+    return;
+
+  alloc_w = box->x2;
+  alloc_h = box->y2;
+
+  switch (priv->content_gravity)
+    {
+    case CLUTTER_CONTENT_GRAVITY_TOP_LEFT:
+      box->x2 = box->x1 + MIN (content_w, alloc_w);
+      box->y2 = box->y1 + MIN (content_h, alloc_h);
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_TOP:
+      if (alloc_w > content_w)
+        {
+          box->x1 += ceilf ((alloc_w - content_w) / 2.0);
+          box->x2 = box->x1 + content_w;
+        }
+      box->y2 = box->y1 + MIN (content_h, alloc_h);
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_TOP_RIGHT:
+      if (alloc_w > content_w)
+        {
+          box->x1 += (alloc_w - content_w);
+          box->x2 = box->x1 + content_w;
+        }
+      box->y2 = box->y1 + MIN (content_h, alloc_h);
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_LEFT:
+      box->x2 = box->x1 + MIN (content_w, alloc_w);
+      if (alloc_h > content_h)
+        {
+          box->y1 += ceilf ((alloc_h - content_h) / 2.0);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_CENTER:
+      if (alloc_w > content_w)
+        {
+          box->x1 += ceilf ((alloc_w - content_w) / 2.0);
+          box->x2 = box->x1 + content_w;
+        }
+      if (alloc_h > content_h)
+        {
+          box->y1 += ceilf ((alloc_h - content_h) / 2.0);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_RIGHT:
+      if (alloc_w > content_w)
+        {
+          box->x1 += (alloc_w - content_w);
+          box->x2 = box->x1 + content_w;
+        }
+      if (alloc_h > content_h)
+        {
+          box->y1 += ceilf ((alloc_h - content_h) / 2.0);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_BOTTOM_LEFT:
+      box->x2 = box->x1 + MIN (content_w, alloc_w);
+      if (alloc_h > content_h)
+        {
+          box->y1 += (alloc_h - content_h);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_BOTTOM:
+      if (alloc_w > content_w)
+        {
+          box->x1 += ceilf ((alloc_w - content_w) / 2.0);
+          box->x2 = box->x1 + content_w;
+        }
+      if (alloc_h > content_h)
+        {
+          box->y1 += (alloc_h - content_h);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_BOTTOM_RIGHT:
+      if (alloc_w > content_w)
+        {
+          box->x1 += (alloc_w - content_w);
+          box->x2 = box->x1 + content_w;
+        }
+      if (alloc_h > content_h)
+        {
+          box->y1 += (alloc_h - content_h);
+          box->y2 = box->y1 + content_h;
+        }
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_RESIZE_FILL:
+      g_assert_not_reached ();
+      break;
+
+    case CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT:
+      {
+        double r_c = content_w / content_h;
+
+        if (r_c >= 1.0)
+          {
+            if ((alloc_w / r_c) > alloc_h)
+              {
+                box->x1 = 0.f;
+                box->x2 = alloc_w;
+
+                box->y1 = (alloc_h - (alloc_w / r_c)) / 2.0f;
+                box->y2 = box->y1 + (alloc_w / r_c);
+              }
+            else
+              {
+                box->y1 = 0.f;
+                box->y2 = alloc_h;
+
+                box->x1 = (alloc_w - (alloc_h * r_c)) / 2.0f;
+                box->x2 = box->x1 + (alloc_h * r_c);
+              }
+          }
+        else
+          {
+            if ((alloc_w / r_c) > alloc_h)
+              {
+                box->y1 = 0.f;
+                box->y2 = alloc_h;
+
+                box->x1 = (alloc_w - (alloc_h * r_c)) / 2.0f;
+                box->x2 = box->x1 + (alloc_h * r_c);
+              }
+            else
+              {
+                box->x1 = 0.f;
+                box->x2 = alloc_w;
+
+                box->y1 = (alloc_h - (alloc_w / r_c)) / 2.0f;
+                box->y2 = box->y1 + (alloc_w / r_c);
+              }
+          }
+
+        CLUTTER_NOTE (LAYOUT,
+                      "r_c: %.3f, r_a: %.3f\t"
+                      "a: [%.2fx%.2f], c: [%.2fx%.2f]\t"
+                      "b: [%.2f, %.2f, %.2f, %.2f]",
+                      r_c, alloc_w / alloc_h,
+                      alloc_w, alloc_h,
+                      content_w, content_h,
+                      box->x1, box->y1, box->x2, box->y2);
+      }
+      break;
+    }
+}
+
+/**
+ * clutter_actor_set_content_scaling_filters:
+ * @self: a #ClutterActor
+ * @min_filter: the minification filter for the content
+ * @mag_filter: the magnification filter for the content
+ *
+ * Sets the minification and magnification filter to be applied when
+ * scaling the #ClutterActor:content of a #ClutterActor.
+ *
+ * The #ClutterActor:minification-filter will be used when reducing
+ * the size of the content; the #ClutterActor:magnification-filter
+ * will be used when increasing the size of the content.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_set_content_scaling_filters (ClutterActor         *self,
+                                           ClutterScalingFilter  min_filter,
+                                           ClutterScalingFilter  mag_filter)
+{
+  ClutterActorPrivate *priv;
+  gboolean changed;
+  GObject *obj;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  priv = self->priv;
+  obj = G_OBJECT (self);
+
+  g_object_freeze_notify (obj);
+
+  changed = FALSE;
+
+  if (priv->min_filter != min_filter)
+    {
+      priv->min_filter = min_filter;
+      changed = TRUE;
+
+      g_object_notify_by_pspec (obj, obj_props[PROP_MINIFICATION_FILTER]);
+    }
+
+  if (priv->mag_filter != mag_filter)
+    {
+      priv->mag_filter = mag_filter;
+      changed = TRUE;
+
+      g_object_notify_by_pspec (obj, obj_props[PROP_MAGNIFICATION_FILTER]);
+    }
+
+  if (changed)
+    clutter_actor_queue_redraw (self);
+
+  g_object_thaw_notify (obj);
+}
+
+/**
+ * clutter_actor_get_content_scaling_filters:
+ * @self: a #ClutterActor
+ * @min_filter: (out) (allow-none): return location for the minification
+ *   filter, or %NULL
+ * @mag_filter: (out) (allow-none): return location for the magnification
+ *   filter, or %NULL
+ *
+ * Retrieves the values set using clutter_actor_set_content_scaling_filters().
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_get_content_scaling_filters (ClutterActor         *self,
+                                           ClutterScalingFilter *min_filter,
+                                           ClutterScalingFilter *mag_filter)
+{
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  if (min_filter != NULL)
+    *min_filter = self->priv->min_filter;
+
+  if (mag_filter != NULL)
+    *mag_filter = self->priv->mag_filter;
+}
+
+/*
+ * clutter_actor_queue_compute_expand:
+ * @self: a #ClutterActor
+ *
+ * Invalidates the needs_x_expand and needs_y_expand flags on @self
+ * and its parents up to the top-level actor.
+ *
+ * This function also queues a relayout if anything changed.
+ */
+static inline void
+clutter_actor_queue_compute_expand (ClutterActor *self)
+{
+  ClutterActor *parent;
+  gboolean changed;
+
+  if (self->priv->needs_compute_expand)
+    return;
+
+  changed = FALSE;
+  parent = self;
+  while (parent != NULL)
+    {
+      if (!parent->priv->needs_compute_expand)
+        {
+          parent->priv->needs_compute_expand = TRUE;
+          changed = TRUE;
+        }
+
+      parent = parent->priv->parent;
+    }
+
+  if (changed)
+    clutter_actor_queue_relayout (self);
+}
+
+/**
+ * clutter_actor_set_x_expand:
+ * @self: a #ClutterActor
+ * @expand: whether the actor should expand horizontally
+ *
+ * Sets whether a #ClutterActor should expand horizontally; this means
+ * that layout manager should allocate extra space for the actor, if
+ * possible.
+ *
+ * Setting an actor to expand will also make all its parent expand, so
+ * that it's possible to build an actor tree and only set this flag on
+ * its leaves and not on every single actor.
+ *
+ * Since: 1.12
+ */
+void
+clutter_actor_set_x_expand (ClutterActor *self,
+                            gboolean      expand)
+{
+  ClutterLayoutInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  expand = !!expand;
+
+  info = _clutter_actor_get_layout_info (self);
+  if (info->x_expand != expand)
+    {
+      info->x_expand = expand;
+
+      self->priv->x_expand_set = TRUE;
+
+      clutter_actor_queue_compute_expand (self);
+
+      g_object_notify_by_pspec (G_OBJECT (self),
+                                obj_props[PROP_X_EXPAND]);
+    }
+}
+
+/**
+ * clutter_actor_get_x_expand:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the value set with clutter_actor_set_x_expand().
+ *
+ * See also: clutter_actor_needs_expand()
+ *
+ * Return value: %TRUE if the actor has been set to expand
+ *
+ * Since: 1.12
+ */
+gboolean
+clutter_actor_get_x_expand (ClutterActor *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
+
+  return _clutter_actor_get_layout_info_or_defaults (self)->x_expand;
+}
+
+/**
+ * clutter_actor_set_y_expand:
+ * @self: a #ClutterActor
+ * @expand: whether the actor should expand vertically
+ *
+ * Sets whether a #ClutterActor should expand horizontally; this means
+ * that layout manager should allocate extra space for the actor, if
+ * possible.
+ *
+ * Setting an actor to expand will also make all its parent expand, so
+ * that it's possible to build an actor tree and only set this flag on
+ * its leaves and not on every single actor.
+ *
+ * Since: 1.12
+ */
+void
+clutter_actor_set_y_expand (ClutterActor *self,
+                            gboolean      expand)
+{
+  ClutterLayoutInfo *info;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  expand = !!expand;
+
+  info = _clutter_actor_get_layout_info (self);
+  if (info->y_expand != expand)
+    {
+      info->y_expand = expand;
+
+      self->priv->y_expand_set = TRUE;
+
+      clutter_actor_queue_compute_expand (self);
+
+      g_object_notify_by_pspec (G_OBJECT (self),
+                                obj_props[PROP_Y_EXPAND]);
+    }
+}
+
+/**
+ * clutter_actor_get_y_expand:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the value set with clutter_actor_set_y_expand().
+ *
+ * See also: clutter_actor_needs_expand()
+ *
+ * Return value: %TRUE if the actor has been set to expand
+ *
+ * Since: 1.12
+ */
+gboolean
+clutter_actor_get_y_expand (ClutterActor *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
+
+  return _clutter_actor_get_layout_info_or_defaults (self)->y_expand;
+}
+
+static void
+clutter_actor_compute_expand_recursive (ClutterActor *self,
+                                        gboolean     *x_expand_p,
+                                        gboolean     *y_expand_p)
+{
+  ClutterActorIter iter;
+  ClutterActor *child;
+  gboolean x_expand, y_expand;
+
+  x_expand = y_expand = FALSE;
+
+  /* note that we don't recurse into children if we're already set to expand;
+   * this avoids traversing the whole actor tree, even if it may lead to some
+   * child left with the needs_compute_expand flag set.
+   */
+  clutter_actor_iter_init (&iter, self);
+  while (clutter_actor_iter_next (&iter, &child))
+    {
+      x_expand = x_expand ||
+        clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_HORIZONTAL);
+
+      y_expand = y_expand ||
+        clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_VERTICAL);
+    }
+
+  *x_expand_p = x_expand;
+  *y_expand_p = y_expand;
+}
+
+static void
+clutter_actor_compute_expand (ClutterActor *self)
+{
+  if (self->priv->needs_compute_expand)
+    {
+      const ClutterLayoutInfo *info;
+      gboolean x_expand, y_expand;
+
+      info = _clutter_actor_get_layout_info_or_defaults (self);
+
+      if (self->priv->x_expand_set)
+        x_expand = info->x_expand;
+      else
+        x_expand = FALSE;
+
+      if (self->priv->y_expand_set)
+        y_expand = info->y_expand;
+      else
+        y_expand = FALSE;
+
+      /* we don't need to recurse down to the children if the
+       * actor has been forcibly set to expand
+       */
+      if (!(self->priv->x_expand_set && self->priv->y_expand_set))
+        {
+          if (self->priv->n_children != 0)
+            {
+              gboolean *x_expand_p, *y_expand_p;
+              gboolean ignored = FALSE;
+
+              x_expand_p = self->priv->x_expand_set ? &ignored : &x_expand;
+              y_expand_p = self->priv->y_expand_set ? &ignored : &y_expand;
+
+              clutter_actor_compute_expand_recursive (self,
+                                                      x_expand_p,
+                                                      y_expand_p);
+            }
+        }
+
+      self->priv->needs_compute_expand = FALSE;
+      self->priv->needs_x_expand = (x_expand != FALSE);
+      self->priv->needs_y_expand = (y_expand != FALSE);
+    }
+}
+
+/**
+ * clutter_actor_needs_expand:
+ * @self: a #ClutterActor
+ * @orientation: the direction of expansion
+ *
+ * Checks whether an actor, or any of its children, is set to expand
+ * horizontally or vertically.
+ *
+ * This function should only be called by layout managers that can
+ * assign extra space to their children.
+ *
+ * If you want to know whether the actor was explicitly set to expand,
+ * use clutter_actor_get_x_expand() or clutter_actor_get_y_expand().
+ *
+ * Return value: %TRUE if the actor should expand
+ *
+ * Since: 1.12
+ */
+gboolean
+clutter_actor_needs_expand (ClutterActor       *self,
+                            ClutterOrientation  orientation)
+{
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
+
+  if (!CLUTTER_ACTOR_IS_VISIBLE (self))
+    return FALSE;
+
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (self))
+    return FALSE;
+
+  clutter_actor_compute_expand (self);
+
+  switch (orientation)
+    {
+    case CLUTTER_ORIENTATION_HORIZONTAL:
+      return self->priv->needs_x_expand;
+
+    case CLUTTER_ORIENTATION_VERTICAL:
+      return self->priv->needs_y_expand;
+    }
+
+  return FALSE;
+}