layout: Add BoxLayout, a single line layout manager
authorEmmanuele Bassi <ebassi@linux.intel.com>
Tue, 13 Oct 2009 11:14:05 +0000 (12:14 +0100)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Wed, 14 Oct 2009 10:31:48 +0000 (11:31 +0100)
The BoxLayout layout manager implements a layout policy for arranging
children on a single line, either alongside the X axis or alongside the
Y axis.

.gitignore
clutter/Makefile.am
clutter/clutter-box-layout.c [new file with mode: 0644]
clutter/clutter-box-layout.h [new file with mode: 0644]
clutter/clutter.h
tests/interactive/Makefile.am
tests/interactive/test-box-layout.c [new file with mode: 0644]

index 2ebadd2..cdf6183 100644 (file)
@@ -135,6 +135,7 @@ TAGS
 /tests/interactive/test-clutter-cairo-flowers
 /tests/interactive/test-bin-layout
 /tests/interactive/test-flow-layout
+/tests/interactive/test-box-layout
 /tests/conform/stamp-test-conformance
 /tests/conform/test-anchors
 /tests/conform/test-conformance
index 61a7616..f4e6181 100644 (file)
@@ -67,6 +67,7 @@ source_h =                                    \
        $(srcdir)/clutter-binding-pool.h        \
        $(srcdir)/clutter-bin-layout.h          \
        $(srcdir)/clutter-box.h                 \
+       $(srcdir)/clutter-box-layout.h          \
        $(srcdir)/clutter-cairo-texture.h       \
        $(srcdir)/clutter-child-meta.h          \
        $(srcdir)/clutter-clone.h               \
@@ -137,6 +138,7 @@ source_c = \
        $(srcdir)/clutter-binding-pool.c        \
        $(srcdir)/clutter-bin-layout.c          \
        $(srcdir)/clutter-box.c                 \
+       $(srcdir)/clutter-box-layout.c          \
        $(srcdir)/clutter-cairo-texture.c       \
        $(srcdir)/clutter-child-meta.c          \
        $(srcdir)/clutter-clone.c               \
diff --git a/clutter/clutter-box-layout.c b/clutter/clutter-box-layout.c
new file mode 100644 (file)
index 0000000..c9371f9
--- /dev/null
@@ -0,0 +1,1496 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2009  Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ *
+ * Based on the NBTK NbtkBoxLayout actor by:
+ *   Thomas Wood <thomas.wood@intel.com>
+ */
+
+/**
+ * SECTION:clutter-box-layout
+ * @short_description: A layout manager arranging children on a single line
+ *
+ * The #ClutterBoxLayout is a #ClutterLayoutManager implementing the
+ * following layout policy:
+ * <itemizedlist>
+ *   <listitem><para>all children are arranged on a single
+ *   line;</para></listitem>
+ *   <listitem><para>the axis used is controlled by the
+ *   #ClutterBoxLayout:vertical boolean property;</para></listitem>
+ *   <listitem><para>the order of the packing is determined by the
+ *   #ClutterBoxLayout:pack-start boolean property;</para></listitem>
+ *   <listitem><para>each child will be allocated to its natural
+ *   size or, if set to expand, the available size;</para></listitem>
+ *   <listitem><para>if a child is set to fill on either (or both)
+ *   axis, its allocation will match all the available size; the
+ *   fill layout property only makes sense if the expand property is
+ *   also set;</para></listitem>
+ *   <listitem><para>if a child is set to expand but not to fill then
+ *   it is possible to control the alignment using the X and Y alignment
+ *   layout properties.</para></listitem>
+ * </itemizedlist>
+ *
+ * It is possible to control the spacing between children of a
+ * #ClutterBoxLayout by using clutter_box_layout_set_spacing().
+ *
+ * In order to set the layout properties when packing an actor inside a
+ * #ClutterBoxLayout you should use the clutter_box_layout_pack()
+ * function.
+ *
+ * #ClutterBoxLayout is available since Clutter 1.2
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include "clutter-box-layout.h"
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
+#include "clutter-layout-meta.h"
+#include "clutter-private.h"
+#include "clutter-types.h"
+
+#define CLUTTER_TYPE_BOX_CHILD          (clutter_box_child_get_type ())
+#define CLUTTER_BOX_CHILD(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BOX_CHILD, ClutterBoxChild))
+#define CLUTTER_IS_BOX_CHILD(obj)       (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BOX_CHILD))
+
+#define CLUTTER_BOX_LAYOUT_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutPrivate))
+
+typedef struct _ClutterBoxChild         ClutterBoxChild;
+typedef struct _ClutterLayoutMetaClass  ClutterBoxChildClass;
+
+struct _ClutterBoxLayoutPrivate
+{
+  ClutterContainer *container;
+
+  guint spacing;
+
+  guint is_vertical   : 1;
+  guint is_pack_start : 1;
+};
+
+struct _ClutterBoxChild
+{
+  ClutterLayoutMeta parent_instance;
+
+  ClutterBoxAlignment x_align;
+  ClutterBoxAlignment y_align;
+
+  guint x_fill : 1;
+  guint y_fill : 1;
+
+  guint expand : 1;
+};
+
+enum
+{
+  PROP_CHILD_0,
+
+  PROP_CHILD_X_ALIGN,
+  PROP_CHILD_Y_ALIGN,
+  PROP_CHILD_X_FILL,
+  PROP_CHILD_Y_FILL,
+  PROP_CHILD_EXPAND
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SPACING,
+  PROP_VERTICAL,
+  PROP_PACK_START
+};
+
+G_DEFINE_TYPE (ClutterBoxChild,
+               clutter_box_child,
+               CLUTTER_TYPE_LAYOUT_META);
+
+G_DEFINE_TYPE (ClutterBoxLayout,
+               clutter_box_layout,
+               CLUTTER_TYPE_LAYOUT_MANAGER);
+
+/*
+ * ClutterBoxChild
+ */
+
+static void
+box_child_set_align (ClutterBoxChild     *self,
+                     ClutterBoxAlignment  x_align,
+                     ClutterBoxAlignment  y_align)
+{
+  gboolean x_changed = FALSE, y_changed = FALSE;
+
+  if (self->x_align != x_align)
+    {
+      self->x_align = x_align;
+
+      x_changed = TRUE;
+    }
+
+  if (self->y_align != y_align)
+    {
+      self->y_align = y_align;
+
+      y_changed = TRUE;
+    }
+
+  if (x_changed || y_changed)
+    {
+      ClutterLayoutManager *layout;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      clutter_layout_manager_layout_changed (layout);
+
+      if (x_changed)
+        g_object_notify (G_OBJECT (self), "x-align");
+
+      if (y_changed)
+        g_object_notify (G_OBJECT (self), "y-align");
+    }
+}
+
+static void
+box_child_set_fill (ClutterBoxChild *self,
+                    gboolean         x_fill,
+                    gboolean         y_fill)
+{
+  gboolean x_changed = FALSE, y_changed = FALSE;
+
+  if (self->x_fill != x_fill)
+    {
+      self->x_fill = x_fill;
+
+      x_changed = TRUE;
+    }
+
+  if (self->y_fill != y_fill)
+    {
+      self->y_fill = y_fill;
+
+      y_changed = TRUE;
+    }
+
+  if (x_changed || y_changed)
+    {
+      ClutterLayoutManager *layout;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      clutter_layout_manager_layout_changed (layout);
+
+      if (x_changed)
+        g_object_notify (G_OBJECT (self), "x-fill");
+
+      if (y_changed)
+        g_object_notify (G_OBJECT (self), "y-fill");
+    }
+}
+
+static void
+box_child_set_expand (ClutterBoxChild *self,
+                      gboolean         expand)
+{
+  if (self->expand != expand)
+    {
+      ClutterLayoutManager *layout;
+
+      self->expand = expand;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      clutter_layout_manager_layout_changed (layout);
+
+      g_object_notify (G_OBJECT (self), "expand");
+    }
+}
+
+static void
+clutter_box_child_set_property (GObject      *gobject,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  ClutterBoxChild *self = CLUTTER_BOX_CHILD (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_X_ALIGN:
+      box_child_set_align (self,
+                           g_value_get_enum (value),
+                           self->y_align);
+      break;
+
+    case PROP_CHILD_Y_ALIGN:
+      box_child_set_align (self,
+                           self->x_align,
+                           g_value_get_enum (value));
+      break;
+
+    case PROP_CHILD_X_FILL:
+      box_child_set_fill (self,
+                          g_value_get_boolean (value),
+                          self->y_fill);
+      break;
+
+    case PROP_CHILD_Y_FILL:
+      box_child_set_fill (self,
+                          self->x_fill,
+                          g_value_get_boolean (value));
+      break;
+
+    case PROP_CHILD_EXPAND:
+      box_child_set_expand (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_box_child_get_property (GObject    *gobject,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  ClutterBoxChild *self = CLUTTER_BOX_CHILD (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_X_ALIGN:
+      g_value_set_enum (value, self->x_align);
+      break;
+
+    case PROP_CHILD_Y_ALIGN:
+      g_value_set_enum (value, self->y_align);
+      break;
+
+    case PROP_CHILD_X_FILL:
+      g_value_set_boolean (value, self->x_fill);
+      break;
+
+    case PROP_CHILD_Y_FILL:
+      g_value_set_boolean (value, self->y_fill);
+      break;
+
+    case PROP_CHILD_EXPAND:
+      g_value_set_boolean (value, self->expand);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_box_child_class_init (ClutterBoxChildClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  gobject_class->set_property = clutter_box_child_set_property;
+  gobject_class->get_property = clutter_box_child_get_property;
+
+  pspec = g_param_spec_boolean ("expand",
+                                "Expand",
+                                "Allocate extra space for the child",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_EXPAND, pspec);
+
+  pspec = g_param_spec_boolean ("x-fill",
+                                "Horizontal Fill",
+                                "Whether the child should receive priority "
+                                "when the container is allocating spare space "
+                                "on the horizontal axis",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_X_FILL, pspec);
+
+  pspec = g_param_spec_boolean ("y-fill",
+                                "Vertical Fill",
+                                "Whether the child should receive priority "
+                                "when the container is allocating spare space "
+                                "on the vertical axis",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_Y_FILL, pspec);
+
+  pspec = g_param_spec_enum ("x-align",
+                             "Horizontal Alignment",
+                             "Horizontal alignment of the actor within "
+                             "the cell",
+                             CLUTTER_TYPE_BOX_ALIGNMENT,
+                             CLUTTER_BOX_ALIGNMENT_CENTER,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_X_ALIGN, pspec);
+
+  pspec = g_param_spec_enum ("y-align",
+                             "Vertical Alignment",
+                             "Vertical alignment of the actor within "
+                             "the cell",
+                             CLUTTER_TYPE_BOX_ALIGNMENT,
+                             CLUTTER_BOX_ALIGNMENT_CENTER,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_Y_ALIGN, pspec);
+}
+
+static void
+clutter_box_child_init (ClutterBoxChild *self)
+{
+  self->x_align = CLUTTER_BOX_ALIGNMENT_CENTER;
+  self->y_align = CLUTTER_BOX_ALIGNMENT_CENTER;
+
+  self->x_fill = self->y_fill = FALSE;
+
+  self->expand = FALSE;
+}
+
+static inline void
+allocate_fill (ClutterActor    *child,
+               ClutterActorBox *childbox,
+               ClutterBoxChild *box_child)
+{
+  gfloat natural_width, natural_height;
+  gfloat min_width, min_height;
+  gfloat child_width, child_height;
+  gfloat available_width, available_height;
+  ClutterRequestMode request;
+  ClutterActorBox allocation = { 0, };
+  gdouble x_align, y_align;
+
+  if (box_child->x_align == CLUTTER_BOX_ALIGNMENT_START)
+    x_align = 0.0;
+  else if (box_child->x_align == CLUTTER_BOX_ALIGNMENT_CENTER)
+    x_align = 0.5;
+  else
+    x_align = 1.0;
+
+  if (box_child->y_align == CLUTTER_BOX_ALIGNMENT_START)
+    y_align = 0.0;
+  else if (box_child->y_align == CLUTTER_BOX_ALIGNMENT_CENTER)
+    y_align = 0.5;
+  else
+    y_align = 1.0;
+
+  available_width  = childbox->x2 - childbox->x1;
+  available_height = childbox->y2 - childbox->y1;
+
+  if (available_width < 0)
+    available_width = 0;
+
+  if (available_height < 0)
+    available_height = 0;
+
+  if (box_child->x_fill)
+    {
+      allocation.x1 = childbox->x1;
+      allocation.x2 = ceilf (allocation.x1 + available_width);
+    }
+
+  if (box_child->y_fill)
+    {
+      allocation.y1 = childbox->y1;
+      allocation.y2 = ceilf (allocation.y1 + available_height);
+    }
+
+  /* if we are filling horizontally and vertically then we're done */
+  if (box_child->x_fill && box_child->y_fill)
+    {
+      *childbox = allocation;
+      return;
+    }
+
+  request = clutter_actor_get_request_mode (child);
+  if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+    {
+      clutter_actor_get_preferred_width (child, available_height,
+                                         &min_width,
+                                         &natural_width);
+
+      child_width = CLAMP (natural_width, min_width, available_width);
+
+      clutter_actor_get_preferred_height (child, child_width,
+                                          &min_height,
+                                          &natural_height);
+
+      child_height = CLAMP (natural_height, min_height, available_height);
+    }
+  else
+    {
+      clutter_actor_get_preferred_height (child, available_width,
+                                          &min_height,
+                                          &natural_height);
+
+      child_height = CLAMP (natural_height, min_height, available_height);
+
+      clutter_actor_get_preferred_width (child, child_height,
+                                         &min_width,
+                                         &natural_width);
+
+      child_width = CLAMP (natural_width, min_width, available_width);
+    }
+
+  if (!box_child->x_fill)
+    {
+      allocation.x1 = ceilf (childbox->x1
+                    + ((available_width - child_width) * x_align));
+      allocation.x2 = ceilf (allocation.x1 + child_width);
+    }
+
+  if (!box_child->y_fill)
+    {
+      allocation.y1 = ceilf (childbox->y1
+                    + ((available_height - child_height) * y_align));
+      allocation.y2 = ceilf (allocation.y1 + child_height);
+    }
+
+  *childbox = allocation;
+}
+
+static ClutterLayoutMeta *
+clutter_box_layout_create_child_meta (ClutterLayoutManager *layout,
+                                      ClutterContainer     *container,
+                                      ClutterActor         *actor)
+{
+  return g_object_new (CLUTTER_TYPE_BOX_CHILD,
+                       "manager", layout,
+                       "container", container,
+                       "actor", actor,
+                       NULL);
+}
+
+static void
+clutter_box_layout_set_container (ClutterLayoutManager *layout,
+                                  ClutterContainer     *container)
+{
+  ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv;
+
+  priv->container = container;
+
+  if (priv->container != NULL)
+    {
+      ClutterRequestMode request_mode;
+
+      /* we need to change the :request-mode of the container
+       * to match the orientation
+       */
+      request_mode = (priv->is_vertical)
+                   ? CLUTTER_REQUEST_WIDTH_FOR_HEIGHT
+                   : CLUTTER_REQUEST_HEIGHT_FOR_WIDTH;
+      clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container),
+                                      request_mode);
+    }
+}
+
+static void
+get_preferred_width (ClutterBoxLayout *self,
+                     const GList      *children,
+                     gfloat            for_height,
+                     gfloat           *min_width_p,
+                     gfloat           *natural_width_p)
+{
+  ClutterBoxLayoutPrivate *priv = self->priv;
+  gint n_children = 0;
+  const GList *l;
+
+  if (min_width_p)
+    *min_width_p = 0;
+
+  if (natural_width_p)
+    *natural_width_p = 0;
+
+  for (l = children; l != NULL; l = l->next)
+    {
+      ClutterActor *child = l->data;
+      gfloat child_min = 0, child_nat = 0;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      n_children++;
+
+      clutter_actor_get_preferred_width (child,
+                                         (!priv->is_vertical)
+                                           ? for_height
+                                           : -1,
+                                         &child_min,
+                                         &child_nat);
+
+      if (priv->is_vertical)
+        {
+          if (min_width_p)
+            *min_width_p = MAX (child_min, *min_width_p);
+
+          if (natural_width_p)
+            *natural_width_p = MAX (child_nat, *natural_width_p);
+        }
+      else
+        {
+          if (min_width_p)
+            *min_width_p += child_min;
+
+          if (natural_width_p)
+            *natural_width_p += child_nat;
+        }
+    }
+
+
+  if (!priv->is_vertical && n_children > 1)
+    {
+      if (min_width_p)
+        *min_width_p += priv->spacing * (n_children - 1);
+
+      if (natural_width_p)
+        *natural_width_p += priv->spacing * (n_children - 1);
+    }
+}
+
+static void
+get_preferred_height (ClutterBoxLayout *self,
+                      const GList      *children,
+                      gfloat            for_height,
+                      gfloat           *min_height_p,
+                      gfloat           *natural_height_p)
+{
+  ClutterBoxLayoutPrivate *priv = self->priv;
+  gint n_children = 0;
+  const GList *l;
+
+  if (min_height_p)
+    *min_height_p = 0;
+
+  if (natural_height_p)
+    *natural_height_p = 0;
+
+  for (l = children; l != NULL; l = l->next)
+    {
+      ClutterActor *child = l->data;
+      gfloat child_min = 0, child_nat = 0;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      n_children++;
+
+      clutter_actor_get_preferred_height (child,
+                                          (priv->is_vertical)
+                                            ? for_height
+                                            : -1,
+                                          &child_min,
+                                          &child_nat);
+
+      if (!priv->is_vertical)
+        {
+          if (min_height_p)
+            *min_height_p = MAX (child_min, *min_height_p);
+
+          if (natural_height_p)
+            *natural_height_p = MAX (child_nat, *natural_height_p);
+        }
+      else
+        {
+          if (min_height_p)
+            *min_height_p += child_min;
+
+          if (natural_height_p)
+            *natural_height_p += child_nat;
+        }
+    }
+
+  if (priv->is_vertical && n_children > 1)
+    {
+      if (min_height_p)
+        *min_height_p += priv->spacing * (n_children - 1);
+
+      if (natural_height_p)
+        *natural_height_p += priv->spacing * (n_children - 1);
+    }
+}
+
+static void
+clutter_box_layout_get_preferred_width (ClutterLayoutManager *layout,
+                                        ClutterContainer     *container,
+                                        gfloat                for_height,
+                                        gfloat               *min_width_p,
+                                        gfloat               *natural_width_p)
+{
+  ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (layout);
+  GList *children;
+
+  children = clutter_container_get_children (container);
+
+  get_preferred_width (self, children, for_height,
+                       min_width_p,
+                       natural_width_p);
+
+  g_list_free (children);
+}
+
+static void
+clutter_box_layout_get_preferred_height (ClutterLayoutManager *layout,
+                                         ClutterContainer     *container,
+                                         gfloat                for_width,
+                                         gfloat               *min_height_p,
+                                         gfloat               *natural_height_p)
+{
+  ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (layout);
+  GList *children;
+
+  children = clutter_container_get_children (container);
+
+  get_preferred_height (self, children, for_width,
+                        min_height_p,
+                        natural_height_p);
+
+  g_list_free (children);
+}
+
+static void
+clutter_box_layout_allocate (ClutterLayoutManager   *layout,
+                             ClutterContainer       *container,
+                             const ClutterActorBox  *box,
+                             ClutterAllocationFlags  flags)
+{
+  ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv;
+  gfloat avail_width, avail_height, pref_width, pref_height;
+  GList *children, *l;
+  gint n_expand_children, extra_space;
+  gfloat position;
+
+  children = clutter_container_get_children (container);
+  if (children == NULL)
+    return;
+
+  clutter_actor_box_get_size (box, &avail_width, &avail_height);
+
+  if (priv->is_vertical)
+    {
+      get_preferred_height (CLUTTER_BOX_LAYOUT (layout),
+                            children, avail_width,
+                            NULL,
+                            &pref_height);
+
+      pref_width = avail_width;
+    }
+  else
+    {
+      get_preferred_width (CLUTTER_BOX_LAYOUT (layout),
+                           children, avail_height,
+                           NULL,
+                           &pref_width);
+
+      pref_height = avail_height;
+    }
+
+  /* count the number of children with expand set to TRUE */
+  n_expand_children = 0;
+  for (l = children; l; l = l->next)
+    {
+      ClutterLayoutMeta *meta;
+
+      meta = clutter_layout_manager_get_child_meta (layout,
+                                                    container,
+                                                    l->data);
+
+      if (CLUTTER_BOX_CHILD (meta)->expand)
+        n_expand_children++;
+    }
+
+  if (n_expand_children == 0)
+    {
+      extra_space = 0;
+      n_expand_children = 1;
+    }
+  else
+    {
+      if (priv->is_vertical)
+        extra_space = (avail_height - pref_height) / n_expand_children;
+      else
+        extra_space = (avail_width - pref_width) / n_expand_children;
+
+      /* don't shrink anything */
+      if (extra_space < 0)
+        extra_space = 0;
+    }
+
+  position = 0;
+
+  for (l = (priv->is_pack_start) ? g_list_last (children) : children;
+       l != NULL;
+       l = (priv->is_pack_start) ? l->prev : l->next)
+    {
+      ClutterActor *child = l->data;
+      ClutterActorBox child_box;
+      ClutterBoxChild *box_child;
+      ClutterLayoutMeta *meta;
+      gfloat child_nat;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = clutter_layout_manager_get_child_meta (layout,
+                                                    container,
+                                                    child);
+      box_child = CLUTTER_BOX_CHILD (meta);
+
+      if (priv->is_vertical)
+        {
+          clutter_actor_get_preferred_height (child, avail_width,
+                                              NULL, &child_nat);
+
+          child_box.y1 = ceilf (position);
+
+          if (box_child->expand)
+            child_box.y2 = ceilf (position + child_nat + extra_space);
+          else
+            child_box.y2 = ceilf (position + child_nat);
+
+          child_box.x1 = 0;
+          child_box.x2 = ceilf (avail_width);
+
+          allocate_fill (child, &child_box, box_child);
+          clutter_actor_allocate (child, &child_box, flags);
+
+          if (box_child->expand)
+            position += (child_nat + priv->spacing + extra_space);
+          else
+            position += (child_nat + priv->spacing);
+        }
+      else
+        {
+          clutter_actor_get_preferred_width (child, avail_height,
+                                             NULL, &child_nat);
+
+          child_box.x1 = ceilf (position);
+
+          if (box_child->expand)
+            child_box.x2 = ceilf (position + child_nat + extra_space);
+          else
+            child_box.x2 = ceilf (position + child_nat);
+
+          child_box.y1 = 0;
+          child_box.y2 = ceilf (avail_height);
+
+          allocate_fill (child, &child_box, box_child);
+          clutter_actor_allocate (child, &child_box, flags);
+
+          if (box_child->expand)
+            position += (child_nat + priv->spacing + extra_space);
+          else
+            position += (child_nat + priv->spacing);
+        }
+    }
+
+  g_list_free (children);
+}
+
+static void
+clutter_box_layout_set_property (GObject      *gobject,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_VERTICAL:
+      clutter_box_layout_set_vertical (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_SPACING:
+      clutter_box_layout_set_spacing (self, g_value_get_uint (value));
+      break;
+
+    case PROP_PACK_START:
+      clutter_box_layout_set_pack_start (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_box_layout_get_property (GObject    *gobject,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_VERTICAL:
+      g_value_set_boolean (value, priv->is_vertical);
+      break;
+
+    case PROP_SPACING:
+      g_value_set_uint (value, priv->spacing);
+      break;
+
+    case PROP_PACK_START:
+      g_value_set_boolean (value, priv->is_pack_start);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_box_layout_class_init (ClutterBoxLayoutClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterLayoutManagerClass *layout_class;
+  GParamSpec *pspec;
+
+  layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
+
+  gobject_class->set_property = clutter_box_layout_set_property;
+  gobject_class->get_property = clutter_box_layout_get_property;
+
+  layout_class->get_preferred_width =
+    clutter_box_layout_get_preferred_width;
+  layout_class->get_preferred_height =
+    clutter_box_layout_get_preferred_height;
+  layout_class->allocate = clutter_box_layout_allocate;
+  layout_class->set_container = clutter_box_layout_set_container;
+  layout_class->create_child_meta = clutter_box_layout_create_child_meta;
+
+  g_type_class_add_private (klass, sizeof (ClutterBoxLayoutPrivate));
+
+  /**
+   * ClutterBoxLayout:vertical:
+   *
+   * Whether the #ClutterBoxLayout should arrange its children
+   * alongside the Y axis, instead of alongside the X axis
+   *
+   * Since: 1.2
+   */
+  pspec = g_param_spec_boolean ("vertical",
+                                "Vertical",
+                                "Whether the layout should be vertical, rather"
+                                "than horizontal",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_VERTICAL, pspec);
+
+  /**
+   * ClutterBoxLayout:pack-start:
+   *
+   * Whether the #ClutterBoxLayout should pack items at the start
+   * or append them at the end
+   *
+   * Since: 1.2
+   */
+  pspec = g_param_spec_boolean ("pack-start",
+                                "Pack Start",
+                                "Whether to pack items at the start of the box",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_PACK_START, pspec);
+
+  /**
+   * ClutterBoxLayout:spacing:
+   *
+   * The spacing between children of the #ClutterBoxLayout, in pixels
+   *
+   * Since: 1.2
+   */
+  pspec = g_param_spec_uint ("spacing",
+                             "Spacing",
+                             "Spacing between children",
+                             0, G_MAXUINT, 0,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_SPACING, pspec);
+}
+
+static void
+clutter_box_layout_init (ClutterBoxLayout *layout)
+{
+  ClutterBoxLayoutPrivate *priv;
+
+  layout->priv = priv = CLUTTER_BOX_LAYOUT_GET_PRIVATE (layout);
+
+  priv->is_vertical = FALSE;
+  priv->is_pack_start = FALSE;
+  priv->spacing = 0;
+}
+
+/**
+ * clutter_box_layout_new:
+ *
+ * Creates a new #ClutterBoxLayout layout manager
+ *
+ * Return value: the newly created #ClutterBoxLayout
+ *
+ * Since: 1.2
+ */
+ClutterLayoutManager *
+clutter_box_layout_new (void)
+{
+  return g_object_new (CLUTTER_TYPE_BOX_LAYOUT, NULL);
+}
+
+/**
+ * clutter_box_layout_set_spacing:
+ * @layout: a #ClutterBoxLayout
+ * @spacing: the spacing between children of the layout, in pixels
+ *
+ * Sets the spacing between children of @layout
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_spacing (ClutterBoxLayout *layout,
+                                guint             spacing)
+{
+  ClutterBoxLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->spacing != spacing)
+    {
+      ClutterLayoutManager *manager;
+
+      priv->spacing = spacing;
+
+      manager = CLUTTER_LAYOUT_MANAGER (layout);
+      clutter_layout_manager_layout_changed (manager);
+
+      g_object_notify (G_OBJECT (layout), "spacing");
+    }
+}
+
+/**
+ * clutter_box_layout_get_spacing:
+ * @layout: a #ClutterBoxLayout
+ *
+ * Retrieves the spacing set using clutter_box_layout_set_spacing()
+ *
+ * Return value: the spacing between children of the #ClutterBoxLayout
+ *
+ * Since: 1.2
+ */
+guint
+clutter_box_layout_get_spacing (ClutterBoxLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), 0);
+
+  return layout->priv->spacing;
+}
+
+/**
+ * clutter_box_layout_set_vertical:
+ * @layout: a #ClutterBoxLayout
+ * @vertical: %TRUE if the layout should be vertical
+ *
+ * Sets whether @layout should arrange its children vertically alongside
+ * the Y axis, instead of horizontally alongside the X axis
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_vertical (ClutterBoxLayout *layout,
+                                 gboolean          vertical)
+{
+  ClutterBoxLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->is_vertical != vertical)
+    {
+      ClutterLayoutManager *manager;
+
+      priv->is_vertical = vertical ? TRUE : FALSE;
+
+      manager = CLUTTER_LAYOUT_MANAGER (layout);
+      clutter_layout_manager_layout_changed (manager);
+
+      g_object_notify (G_OBJECT (layout), "vertical");
+    }
+}
+
+/**
+ * clutter_box_layout_get_vertical:
+ * @layout: a #ClutterBoxLayout
+ *
+ * Retrieves the orientation of the @layout as set using the
+ * clutter_box_layout_set_vertical() function
+ *
+ * Return value: %TRUE if the #ClutterBoxLayout is arranging its children
+ *   vertically, and %FALSE otherwise
+ *
+ * Since: 1.2
+ */
+gboolean
+clutter_box_layout_get_vertical (ClutterBoxLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE);
+
+  return layout->priv->is_vertical;
+}
+
+/**
+ * clutter_box_layout_set_pack_start:
+ * @layout: a #ClutterBoxLayout
+ * @pack_start: %TRUE if the @layout should pack children at the
+ *   beginning of the layout
+ *
+ * Sets whether children of @layout should be layed out by appending
+ * them or by prepending them
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_pack_start (ClutterBoxLayout *layout,
+                                   gboolean          pack_start)
+{
+  ClutterBoxLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->is_pack_start != pack_start)
+    {
+      ClutterLayoutManager *manager;
+
+      priv->is_pack_start = pack_start ? TRUE : FALSE;
+
+      manager = CLUTTER_LAYOUT_MANAGER (layout);
+      clutter_layout_manager_layout_changed (manager);
+
+      g_object_notify (G_OBJECT (layout), "pack-start");
+    }
+}
+
+/**
+ * clutter_box_layout_get_pack_start:
+ * @layout: a #ClutterBoxLayout
+ *
+ * Retrieves the value set using clutter_box_layout_set_pack_start()
+ *
+ * Return value: %TRUE if the #ClutterBoxLayout should pack children
+ *  at the beginning of the layout, and %FALSE otherwise
+ *
+ * Since: 1.2
+ */
+gboolean
+clutter_box_layout_get_pack_start (ClutterBoxLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE);
+
+  return layout->priv->is_pack_start;
+}
+
+/**
+ * clutter_box_layout_pack:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor
+ * @expand: whether the @actor should expand
+ * @x_fill: whether the @actor should fill horizontally
+ * @y_fill: whether the @actor should fill vertically
+ * @x_align: the horizontal alignment policy for @actor
+ * @y_align: the vertical alignment policy for @actor
+ *
+ * Packs @actor inside the #ClutterContainer associated to @layout
+ * and sets the layout properties
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_pack (ClutterBoxLayout    *layout,
+                         ClutterActor        *actor,
+                         gboolean             expand,
+                         gboolean             x_fill,
+                         gboolean             y_fill,
+                         ClutterBoxAlignment  x_align,
+                         ClutterBoxAlignment  y_align)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before adding children",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  clutter_container_add_actor (priv->container, actor);
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  box_child_set_align (CLUTTER_BOX_CHILD (meta), x_align, y_align);
+  box_child_set_fill (CLUTTER_BOX_CHILD (meta), x_fill, y_fill);
+  box_child_set_expand (CLUTTER_BOX_CHILD (meta), expand);
+}
+
+/**
+ * clutter_box_layout_set_alignment:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_align: Horizontal alignment policy for @actor
+ * @y_align: Vertical alignment policy for @actor
+ *
+ * Sets the horizontal and vertical alignment policies for @actor
+ * inside @layout
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_alignment (ClutterBoxLayout    *layout,
+                                  ClutterActor        *actor,
+                                  ClutterBoxAlignment  x_align,
+                                  ClutterBoxAlignment  y_align)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  box_child_set_align (CLUTTER_BOX_CHILD (meta), x_align, y_align);
+}
+
+/**
+ * clutter_box_layout_get_alignment:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_align: (out): return location for the horizontal alignment policy
+ * @y_align: (out): return location for the vertical alignment policy
+ *
+ * Retrieves the horizontal and vertical alignment policies for @actor
+ * as set using clutter_box_layout_pack() or clutter_box_layout_set_alignment()
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_get_alignment (ClutterBoxLayout    *layout,
+                                  ClutterActor        *actor,
+                                  ClutterBoxAlignment *x_align,
+                                  ClutterBoxAlignment *y_align)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  if (x_align)
+    *x_align = CLUTTER_BOX_CHILD (meta)->x_align;
+
+  if (y_align)
+    *y_align = CLUTTER_BOX_CHILD (meta)->y_align;
+}
+
+/**
+ * clutter_box_layout_set_fill:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_fill: whether @actor should fill horizontally the allocated space
+ * @y_fill: whether @actor should fill vertically the allocated space
+ *
+ * Sets the horizontal and vertical fill policies for @actor
+ * inside @layout
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_fill (ClutterBoxLayout *layout,
+                             ClutterActor     *actor,
+                             gboolean          x_fill,
+                             gboolean          y_fill)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  box_child_set_fill (CLUTTER_BOX_CHILD (meta), x_fill, y_fill);
+}
+
+/**
+ * clutter_box_layout_get_fill:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_fill: (out): return location for the horizontal fill policy
+ * @y_fill: (out): return location for the vertical fill policy
+ *
+ * Retrieves the horizontal and vertical fill policies for @actor
+ * as set using clutter_box_layout_pack() or clutter_box_layout_set_fill()
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_get_fill (ClutterBoxLayout *layout,
+                             ClutterActor     *actor,
+                             gboolean         *x_fill,
+                             gboolean         *y_fill)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  if (x_fill)
+    *x_fill = CLUTTER_BOX_CHILD (meta)->x_fill;
+
+  if (y_fill)
+    *y_fill = CLUTTER_BOX_CHILD (meta)->y_fill;
+}
+
+/**
+ * clutter_box_layout_set_expand:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ * @expand: whether @actor should expand
+ *
+ * Sets whether @actor should expand inside @layout
+ *
+ * Since: 1.2
+ */
+void
+clutter_box_layout_set_expand (ClutterBoxLayout *layout,
+                               ClutterActor     *actor,
+                               gboolean          expand)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  box_child_set_expand (CLUTTER_BOX_CHILD (meta), expand);
+}
+
+/**
+ * clutter_box_layout_get_expand:
+ * @layout: a #ClutterBoxLayout
+ * @actor: a #ClutterActor child of @layout
+ *
+ * Retrieves whether @actor should expand inside @layout
+ *
+ * Return value: %TRUE if the #ClutterActor should expand, %FALSE otherwise
+ *
+ * Since: 1.2
+ */
+gboolean
+clutter_box_layout_get_expand (ClutterBoxLayout *layout,
+                               ClutterActor     *actor)
+{
+  ClutterBoxLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE);
+  g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE);
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return FALSE;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return FALSE;
+    }
+
+  g_assert (CLUTTER_IS_BOX_CHILD (meta));
+
+  return CLUTTER_BOX_CHILD (meta)->expand;
+}
diff --git a/clutter/clutter-box-layout.h b/clutter/clutter-box-layout.h
new file mode 100644 (file)
index 0000000..be2be91
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2009  Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ *
+ * Based on the NBTK NbtkBoxLayout actor by:
+ *   Thomas Wood <thomas.wood@intel.com>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_BOX_LAYOUT_H__
+#define __CLUTTER_BOX_LAYOUT_H__
+
+#include <clutter/clutter-layout-manager.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_BOX_LAYOUT                 (clutter_box_layout_get_type ())
+#define CLUTTER_BOX_LAYOUT(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayout))
+#define CLUTTER_IS_BOX_LAYOUT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BOX_LAYOUT))
+#define CLUTTER_BOX_LAYOUT_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutClass))
+#define CLUTTER_IS_BOX_LAYOUT_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BOX_LAYOUT))
+#define CLUTTER_BOX_LAYOUT_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutClass))
+
+typedef struct _ClutterBoxLayout                ClutterBoxLayout;
+typedef struct _ClutterBoxLayoutPrivate         ClutterBoxLayoutPrivate;
+typedef struct _ClutterBoxLayoutClass           ClutterBoxLayoutClass;
+
+/**
+ * ClutterBoxAlignment:
+ * @CLUTTER_BOX_ALIGNMENT_START: Align the child to the top or to
+ *   to the left, depending on the used axis
+ * @CLUTTER_BOX_ALIGNMENT_CENTER: Align the child to the center
+ * @CLUTTER_BOX_ALIGNMENT_END: Align the child to the bottom or to
+ *   the right, depending on the used axis
+ *
+ * The alignment policies available on each axis of the #ClutterBoxLayout
+ *
+ * Since: 1.2
+ */
+typedef enum {
+  CLUTTER_BOX_ALIGNMENT_START,
+  CLUTTER_BOX_ALIGNMENT_END,
+  CLUTTER_BOX_ALIGNMENT_CENTER
+} ClutterBoxAlignment;
+
+/**
+ * ClutterBoxLayout:
+ *
+ * The #ClutterBoxLayout structure contains only private data
+ * and should be accessed using the provided API
+ *
+ * Since: 1.2
+ */
+struct _ClutterBoxLayout
+{
+  /*< private >*/
+  ClutterLayoutManager parent_instance;
+
+  ClutterBoxLayoutPrivate *priv;
+};
+
+/**
+ * ClutterBoxLayoutClass:
+ *
+ * The #ClutterBoxLayoutClass structure contains only private
+ * data and should be accessed using the provided API
+ *
+ * Since: 1.2
+ */
+struct _ClutterBoxLayoutClass
+{
+  /*< private >*/
+  ClutterLayoutManagerClass parent_class;
+};
+
+GType clutter_box_layout_get_type (void) G_GNUC_CONST;
+
+ClutterLayoutManager *clutter_box_layout_new            (void);
+
+void                  clutter_box_layout_set_spacing    (ClutterBoxLayout    *layout,
+                                                         guint                spacing);
+guint                 clutter_box_layout_get_spacing    (ClutterBoxLayout    *layout);
+void                  clutter_box_layout_set_vertical   (ClutterBoxLayout    *layout,
+                                                         gboolean             vertical);
+gboolean              clutter_box_layout_get_vertical   (ClutterBoxLayout    *layout);
+void                  clutter_box_layout_set_pack_start (ClutterBoxLayout    *layout,
+                                                         gboolean             pack_start);
+gboolean              clutter_box_layout_get_pack_start (ClutterBoxLayout    *layout);
+
+void                  clutter_box_layout_pack           (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         gboolean             expand,
+                                                         gboolean             x_fill,
+                                                         gboolean             y_fill,
+                                                         ClutterBoxAlignment  x_align,
+                                                         ClutterBoxAlignment  y_align);
+void                  clutter_box_layout_set_alignment  (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         ClutterBoxAlignment  x_align,
+                                                         ClutterBoxAlignment  y_align);
+void                  clutter_box_layout_get_alignment  (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         ClutterBoxAlignment *x_align,
+                                                         ClutterBoxAlignment *y_align);
+void                  clutter_box_layout_set_fill       (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         gboolean             x_fill,
+                                                         gboolean             y_fill);
+void                  clutter_box_layout_get_fill       (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         gboolean            *x_fill,
+                                                         gboolean            *y_fill);
+void                  clutter_box_layout_set_expand     (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor,
+                                                         gboolean             expand);
+gboolean              clutter_box_layout_get_expand     (ClutterBoxLayout    *layout,
+                                                         ClutterActor        *actor);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_BOX_LAYOUT_H__ */
index 5c31317..087d93d 100644 (file)
@@ -45,6 +45,7 @@
 #include "clutter-binding-pool.h"
 #include "clutter-bin-layout.h"
 #include "clutter-box.h"
+#include "clutter-box-layout.h"
 #include "clutter-cairo-texture.h"
 #include "clutter-child-meta.h"
 #include "clutter-clone.h"
index a0af9d1..ec99254 100644 (file)
@@ -45,7 +45,8 @@ UNIT_TESTS = \
        test-clutter-cairo-flowers.c \
        test-cogl-vertex-buffer.c \
        test-bin-layout.c \
-       test-flow-layout.c
+       test-flow-layout.c \
+       test-box-layout.c
 
 if X11_TESTS
 UNIT_TESTS += test-pixmap.c
diff --git a/tests/interactive/test-box-layout.c b/tests/interactive/test-box-layout.c
new file mode 100644 (file)
index 0000000..9903fcf
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gmodule.h>
+
+#include <clutter/clutter.h>
+
+static ClutterActor *hover_actor = NULL;
+
+static void
+enter_event (ClutterActor *actor,
+             ClutterEvent *event,
+             gpointer data)
+{
+  ClutterColor color = { 0x00, 0x00, 0x00, 0xff };
+
+  clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (actor), 2);
+  clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (actor), &color);
+
+  hover_actor = actor;
+}
+
+static void
+leave_event (ClutterActor *actor,
+             ClutterEvent *event,
+             gpointer data)
+{
+  clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (actor), 0);
+
+  hover_actor = NULL;
+}
+
+static gboolean
+button_release_event (ClutterActor     *actor,
+                      ClutterEvent     *event,
+                      ClutterBoxLayout *box)
+{
+  gboolean xfill, yfill;
+  ClutterBoxAlignment xalign, yalign;
+  gint button;
+
+  button = clutter_event_get_button (event);
+
+  if (button == 1)
+    {
+      clutter_box_layout_get_fill (box, actor, &xfill, &yfill);
+      clutter_box_layout_set_fill (box, actor,
+                                   xfill ? FALSE : TRUE,
+                                   yfill ? FALSE : TRUE);
+    }
+  else
+    {
+      clutter_box_layout_get_alignment (box, actor, &xalign, &yalign);
+
+      if (xalign < 2)
+        xalign += 1;
+      else
+        xalign = 0;
+
+      if (yalign < 2)
+        yalign += 1;
+      else
+        yalign = 0;
+
+      clutter_box_layout_set_alignment (box, actor, xalign, yalign);
+    }
+
+  return TRUE;
+}
+
+static void
+add_actor (ClutterBoxLayout *box)
+{
+  ClutterActor *rect;
+  ClutterColor color = { 0xff, 0xff, 0xff, 255 };
+  static gboolean expand = TRUE;
+
+  clutter_color_from_hls (&color,
+                          g_random_double_range (0.0, 360.0),
+                          0.5,
+                          0.5);
+
+  rect = clutter_rectangle_new_with_color (&color);
+  clutter_actor_set_size (rect, 32, 64);
+  clutter_box_layout_pack (box, rect, expand,
+                           FALSE, /* x-fill */
+                           FALSE, /* y-fill */
+                           CLUTTER_BOX_ALIGNMENT_CENTER,
+                           CLUTTER_BOX_ALIGNMENT_CENTER);
+
+  clutter_actor_set_reactive (rect, TRUE);
+  g_signal_connect (rect, "enter-event", G_CALLBACK (enter_event), NULL);
+  g_signal_connect (rect, "leave-event", G_CALLBACK (leave_event), NULL);
+  g_signal_connect (rect, "button-release-event",
+                    G_CALLBACK (button_release_event),
+                    box);
+
+  expand = !expand;
+}
+
+static gboolean
+key_release_cb (ClutterActor     *actor,
+                ClutterEvent     *event,
+                ClutterBoxLayout *layout)
+{
+  gboolean toggle;
+  guint spacing;
+
+  switch (clutter_event_get_key_symbol (event))
+    {
+    case CLUTTER_v:
+      toggle = clutter_box_layout_get_vertical (layout);
+      clutter_box_layout_set_vertical (layout, !toggle);
+      break;
+
+    case CLUTTER_p:
+      toggle = clutter_box_layout_get_pack_start (layout);
+      clutter_box_layout_set_pack_start (layout, !toggle);
+      break;
+
+    case CLUTTER_s:
+      spacing = clutter_box_layout_get_spacing (layout);
+
+      if (spacing > 12)
+        spacing = 0;
+      else
+        spacing++;
+
+      clutter_box_layout_set_spacing (layout, spacing);
+      break;
+
+    case '+':
+      add_actor (layout);
+      break;
+
+    default:
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+stage_size_changed_cb (ClutterActor *stage,
+                       const ClutterActorBox *allocation,
+                       ClutterAllocationFlags flags,
+                       ClutterActor *box)
+{
+  gfloat width, height;
+
+  clutter_actor_box_get_size (allocation, &width, &height);
+  clutter_actor_set_size (box, width - 100, height - 100);
+}
+
+G_MODULE_EXPORT int
+test_box_layout_main (int argc, char *argv[])
+{
+  ClutterActor *stage, *box;
+  ClutterLayoutManager *layout;
+  gint i;
+
+  clutter_init (&argc, &argv);
+
+  stage = clutter_stage_get_default ();
+  clutter_stage_set_title (CLUTTER_STAGE (stage), "Box Layout");
+  clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
+  clutter_actor_set_size (stage, 640, 480);
+
+  layout = clutter_box_layout_new ();
+
+  box = clutter_box_new (layout);
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);
+
+  for (i = 0; i < 5; i++)
+    add_actor (CLUTTER_BOX_LAYOUT (layout));
+
+  g_signal_connect (stage, "key-release-event",
+                    G_CALLBACK (key_release_cb),
+                    layout);
+  g_signal_connect (stage, "allocation-changed",
+                    G_CALLBACK (stage_size_changed_cb),
+                    box);
+
+  clutter_actor_show (stage);
+
+  clutter_main ();
+
+  return EXIT_SUCCESS;
+}