Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-actor.c
index 5ac4814..5865d57 100644 (file)
  *   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>
  * 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_interval (transition, clutter_interval_new (G_TYPE_UINT, 255, 0));
+ * 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>
  * transition = clutter_property_transition_new ("opacity");
  *
  * /&ast; we want to animate the opacity between 0 and 255 &ast;/
- * internal = clutter_interval_new (G_TYPE_UINT, 0, 255);
- * clutter_transition_set_interval (transition, interval);
+ * 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);
  *   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
@@ -770,6 +786,11 @@ struct _ClutterActorPrivate
   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
@@ -788,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
    */
@@ -860,6 +884,8 @@ enum
 
   PROP_LAYOUT_MANAGER,
 
+  PROP_X_EXPAND,
+  PROP_Y_EXPAND,
   PROP_X_ALIGN,
   PROP_Y_ALIGN,
   PROP_MARGIN_TOP,
@@ -916,6 +942,14 @@ enum
 
 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);
@@ -978,6 +1012,8 @@ static inline void clutter_actor_set_background_color_internal (ClutterActor *se
 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 { \
@@ -1602,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]);
 
@@ -1697,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]);
 
@@ -2165,13 +2223,16 @@ 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
     {
@@ -2184,16 +2245,28 @@ clutter_actor_notify_if_geometry_changed (ClutterActor          *self,
       height = priv->allocation.y2 - priv->allocation.y1;
 
       if (x != old->x1)
-        g_object_notify_by_pspec (obj, obj_props[PROP_X]);
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_X]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_POSITION]);
+        }
 
       if (y != old->y1)
-        g_object_notify_by_pspec (obj, obj_props[PROP_Y]);
+        {
+          g_object_notify_by_pspec (obj, obj_props[PROP_Y]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_POSITION]);
+        }
 
       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_WIDTH]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
+        }
 
       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_HEIGHT]);
+          g_object_notify_by_pspec (obj, obj_props[PROP_SIZE]);
+        }
     }
 
   g_object_thaw_notify (obj);
@@ -2223,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, };
 
@@ -2238,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;
 
@@ -2248,9 +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));
@@ -3739,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,
@@ -3788,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 |
@@ -3798,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
@@ -3822,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;
@@ -3829,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);
 
@@ -3882,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);
@@ -4334,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;
@@ -4342,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;
@@ -4540,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;
@@ -4613,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;
@@ -4621,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;
 
@@ -4635,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;
 
@@ -4648,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;
 
@@ -4657,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;
 
@@ -4666,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;
 
@@ -4675,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;
 
@@ -4916,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;
@@ -5153,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,
@@ -5253,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;
 }
 
 /**
@@ -5434,6 +5618,28 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         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:
    *
    * Width of the actor (in pixels). If written, forces the minimum and
@@ -5472,6 +5678,27 @@ clutter_actor_class_init (ClutterActorClass *klass)
                         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:
    *
    * The fixed X position of the actor in pixels.
@@ -5674,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:
@@ -6258,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
    */
@@ -6652,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()
@@ -7109,6 +7370,15 @@ clutter_actor_init (ClutterActor *self)
   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;
 }
 
 /**
@@ -7732,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,
@@ -7946,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;
     }
@@ -8022,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;
@@ -8079,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;
     }
@@ -8154,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;
@@ -8327,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
@@ -8395,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
@@ -8418,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))
@@ -8500,18 +8813,20 @@ clutter_actor_allocate (ClutterActor           *self,
   if (child_moved)
     flags |= CLUTTER_ABSOLUTE_ORIGIN_CHANGED;
 
-  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, &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);
 }
 
 /**
@@ -8713,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);
 }
 
 /**
@@ -8793,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);
 }
@@ -8818,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);
 
@@ -8856,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);
 
@@ -8893,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);
 
@@ -8930,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);
 
@@ -9136,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
@@ -9158,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 (self, width);
-  clutter_actor_set_height (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);
 }
 
 /**
@@ -9575,12 +9954,12 @@ clutter_actor_set_x_internal (ClutterActor *self,
 
   linfo = _clutter_actor_get_layout_info (self);
 
-  if (priv->position_set && linfo->fixed_x == x)
+  if (priv->position_set && linfo->fixed_pos.x == x)
     return;
 
   clutter_actor_store_old_geometry (self, &old);
 
-  linfo->fixed_x = x;
+  linfo->fixed_pos.x = x;
   clutter_actor_set_fixed_position_set (self, TRUE);
 
   clutter_actor_notify_if_geometry_changed (self, &old);
@@ -9598,12 +9977,12 @@ clutter_actor_set_y_internal (ClutterActor *self,
 
   linfo = _clutter_actor_get_layout_info (self);
 
-  if (priv->position_set && linfo->fixed_y == y)
+  if (priv->position_set && linfo->fixed_pos.y == y)
     return;
 
   clutter_actor_store_old_geometry (self, &old);
 
-  linfo->fixed_y = y;
+  linfo->fixed_pos.y = y;
   clutter_actor_set_fixed_position_set (self, TRUE);
 
   clutter_actor_notify_if_geometry_changed (self, &old);
@@ -9611,6 +9990,35 @@ clutter_actor_set_y_internal (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
@@ -9714,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;
@@ -9762,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;
@@ -10896,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 >
@@ -10943,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)
@@ -11019,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;
@@ -11048,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);
@@ -11064,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))
@@ -12637,22 +13067,79 @@ parse_behaviours (ClutterScript *script,
   return g_slist_reverse (retval);
 }
 
-static gboolean
-clutter_actor_parse_custom_node (ClutterScriptable *scriptable,
-                                 ClutterScript     *script,
-                                 GValue            *value,
-                                 const gchar       *name,
-                                 JsonNode          *node)
+static ClutterMargin *
+parse_margin (ClutterActor *self,
+              JsonNode     *node)
 {
-  ClutterActor *actor = CLUTTER_ACTOR (scriptable);
-  gboolean retval = FALSE;
+  ClutterMargin *margin;
+  JsonArray *array;
 
-  if ((name[0] == 'x' && name[1] == '\0') ||
-      (name[0] == 'y' && name[1] == '\0') ||
-      (strcmp (name, "width") == 0) ||
-      (strcmp (name, "height") == 0) ||
-      (strcmp (name, "anchor_x") == 0) ||
-      (strcmp (name, "anchor_y") == 0))
+  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,
+                                 GValue            *value,
+                                 const gchar       *name,
+                                 JsonNode          *node)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (scriptable);
+  gboolean retval = FALSE;
+
+  if ((name[0] == 'x' && name[1] == '\0') ||
+      (name[0] == 'y' && name[1] == '\0') ||
+      (strcmp (name, "width") == 0) ||
+      (strcmp (name, "height") == 0) ||
+      (strcmp (name, "anchor_x") == 0) ||
+      (strcmp (name, "anchor_y") == 0))
     {
       ParseDimension dimension;
       gfloat units;
@@ -12726,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;
 }
@@ -12818,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);
 }
@@ -12977,6 +13480,10 @@ clutter_actor_set_animatable_property (ClutterActor *actor,
       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;
@@ -12985,6 +13492,16 @@ clutter_actor_set_animatable_property (ClutterActor *actor,
       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;
@@ -13264,247 +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);
-  clutter_vertex_init (vertex, x, y, z);
-
-  return vertex;
-}
-
-/**
- * clutter_vertex_init:
- * @vertex: a #ClutterVertex
- * @x: X coordinate
- * @y: Y coordinate
- * @z: Z coordinate
- *
- * Initializes @vertex with the given coordinates.
- *
- * Since: 1.10
- */
-void
-clutter_vertex_init (ClutterVertex *vertex,
-                     gfloat         x,
-                     gfloat         y,
-                     gfloat         z)
-{
-  g_return_if_fail (vertex != NULL);
-
-  vertex->x = x;
-  vertex->y = y;
-  vertex->z = z;
-}
-
-/**
- * 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
@@ -15693,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;
@@ -15715,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;
 }
@@ -15734,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;
 }
@@ -16050,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
@@ -16229,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
@@ -17031,14 +17249,6 @@ _clutter_actor_get_transition (ClutterActor *actor,
   return g_hash_table_lookup (info->transitions, pspec->name);
 }
 
-typedef struct _TransitionClosure
-{
-  ClutterActor *actor;
-  ClutterTransition *transition;
-  gchar *name;
-  gulong completed_id;
-} TransitionClosure;
-
 static void
 transition_closure_free (gpointer data)
 {
@@ -17062,39 +17272,29 @@ transition_closure_free (gpointer data)
 }
 
 static void
-on_transition_completed (ClutterTransition *transition,
-                         TransitionClosure *clos)
+on_transition_stopped (ClutterTransition *transition,
+                       gboolean           is_finished,
+                       TransitionClosure *clos)
 {
-  ClutterTimeline *timeline = CLUTTER_TIMELINE (transition);
   ClutterActor *actor = clos->actor;
   ClutterAnimationInfo *info;
-  gint n_repeats, cur_repeat;
-
-  info = _clutter_actor_get_animation_info (actor);
 
   /* reset the caches used by animations */
   clutter_actor_store_content_box (actor, NULL);
 
-  /* ensure that we remove the transition only at the end
-   * of its run; we emit ::completed for every repeat
-   */
-  n_repeats = clutter_timeline_get_repeat_count (timeline);
-  cur_repeat = clutter_timeline_get_current_repeat (timeline);
+  if (!is_finished)
+    return;
 
-  if (cur_repeat == n_repeats)
-    {
-      if (clutter_transition_get_remove_on_complete (transition))
-        {
-          /* 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);
-        }
-    }
+  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)
@@ -17236,6 +17436,7 @@ _clutter_actor_create_transition (ClutterActor *actor,
   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;
@@ -17294,6 +17495,11 @@ _clutter_actor_create_transition (ClutterActor *actor,
       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);
 
@@ -17323,10 +17529,6 @@ out:
  * 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 given the easing duration, mode, and delay
- * associated to the actor's current easing state; it is possible to modify
- * these values after calling clutter_actor_add_transition().
- *
  * The @transition will be started once added.
  *
  * This function will take a reference on the @transition.
@@ -17351,15 +17553,6 @@ clutter_actor_add_transition (ClutterActor      *self,
 
   info = _clutter_actor_get_animation_info (self);
 
-  if (info->cur_state == NULL)
-    {
-      g_warning ("No easing state is defined for the actor '%s'; you "
-                 "must call clutter_actor_save_easing_state() before "
-                 "calling clutter_actor_add_transition().",
-                 _clutter_actor_get_debug_name (self));
-      return;
-    }
-
   if (info->transitions == NULL)
     info->transitions = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                NULL,
@@ -17378,16 +17571,12 @@ clutter_actor_add_transition (ClutterActor      *self,
 
   timeline = CLUTTER_TIMELINE (transition);
 
-  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);
-
   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, "completed",
-                                         G_CALLBACK (on_transition_completed),
+  clos->completed_id = g_signal_connect (timeline, "stopped",
+                                         G_CALLBACK (on_transition_stopped),
                                          clos);
 
   CLUTTER_NOTE (ANIMATION,
@@ -17643,13 +17832,13 @@ clutter_actor_get_easing_delay (ClutterActor *self)
  *   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, "completed",
- *                     G_CALLBACK (on_transition_complete),
+ *   g_signal_connect (transition, "stopped",
+ *                     G_CALLBACK (on_transition_stopped),
  *                     actor);
  * ]|
  *
- * will call the <function>on_transition_complete</function> callback when
- * the transition is complete.
+ * 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
@@ -18063,17 +18252,16 @@ clutter_actor_get_content_box (ClutterActor    *self,
     case CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT:
       {
         double r_c = content_w / content_h;
-        double r_a = alloc_w / alloc_h;
 
         if (r_c >= 1.0)
           {
-            if (r_a >= 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);
+                box->y1 = (alloc_h - (alloc_w / r_c)) / 2.0f;
+                box->y2 = box->y1 + (alloc_w / r_c);
               }
             else
               {
@@ -18086,7 +18274,7 @@ clutter_actor_get_content_box (ClutterActor    *self,
           }
         else
           {
-            if (r_a >= 1.0)
+            if ((alloc_w / r_c) > alloc_h)
               {
                 box->y1 = 0.f;
                 box->y2 = alloc_h;
@@ -18099,10 +18287,19 @@ clutter_actor_get_content_box (ClutterActor    *self,
                 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);
+                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;
     }
@@ -18188,3 +18385,274 @@ clutter_actor_get_content_scaling_filters (ClutterActor         *self,
   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;
+}