2. Stacking actors on top of each other

2.1. Problem

You want to lay out several actors so that they are in layers on top of each other (e.g. to create a button widget composed from a rectangle with text on top of it).

2.2. Solution

The most flexible approach is to use a ClutterBinLayout associated with a ClutterActor:

/* define some colors */
const ClutterColor background_color = { 0xaa, 0x99, 0x00, 0xff };
const ClutterColor text_color = { 0xff, 0xff, 0xff, 0xff };

ClutterLayoutManager *layout;
ClutterActor *box;
ClutterActor *background;
ClutterActor *text;

/*
 * create a layout, setting the default x and y alignment;
 * actors fill the whole allocation of the layout's container
 * by default
 */
layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
                                 CLUTTER_BIN_ALIGNMENT_FILL);

/* create the box whose children the layout will manage */
box = clutter_box_new (layout);

/*
 * fill doesn't have much effect here
 * unless the container has height and/or width
 */
clutter_actor_set_size (box, 100, 30);

/*
 * background for the button; could equally be a texture
 * with an image loaded into it or any other ClutterActor
 */
background = clutter_rectangle_new_with_color (&background_color);

/*
 * add the background to the container;
 * as it should use the default alignment, it can be added
 * direct to the container, rather than via the layout
 */
clutter_actor_add_child (box, background);

/* text for the button */
text = clutter_text_new_full ("Sans 15px", "Click me", &text_color);

/*
 * the text requires a different alignment from the background
 * (centered on the box)
 * so we add it via the layout so the default
 * alignment can be overridden
 */
clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout),
                        text,
                        CLUTTER_BIN_ALIGNMENT_CENTER,
                        CLUTTER_BIN_ALIGNMENT_CENTER);

/*
 * ensure the actors are arranged in the correct depth order;
 * in this case, the text is on top
 * (NB this is not strictly necesary here as text is added after
 * background)
 */
clutter_actor_raise_top (text);

2.3. Discussion

This section covers some other aspects of using a ClutterBinLayout.

2.3.1. Setting and changing alignment

Alignment is the only layout property available for ClutterBinLayout. Each actor can have a different setting for its alignment in one or both of the x or y axes. However, as shown in the solution above, alignment can also be used to expand an actor to fill the container (CLUTTER_BIN_ALIGNMENT_FILL) in one or both axes.

Setting alignment does not have any effect if the container is the same size as all of the actors inside it: in this case, every alignment produces the same layout. But if the container associated with the layout is larger than the actor being aligned, alignment will have an effect; see this section for more details.

Changing an actor's alignment after it has been added to a ClutterBinLayout may make the actor "jump" (without animation) to a new position and/or change its size. The exception is changing from some other alignment to CLUTTER_BIN_ALIGNMENT_FIXED: in this case, the actor will retain the position and size it had before its alignment was fixed.

2.3.2. Size requisitioning

A container with a ClutterBinLayout will by default request the width of the widest actor in it, and the height of the tallest. If you add actors smaller than those dimensions, they will be aligned inside the container according to the layout's policies. Here's an example where a ClutterBinLayout requests a size to encompass the tallest (light grey rectangle) and widest (dark grey rectangle) actors inside it, with other actors aligned within those bounds:

Size requisition in a ClutterBinLayout

Note

The screenshot also shows the 9 possible combinations of start, center and end alignments on the x and y axes. See the sample code for more details.

The white space is the stage visible behind the ClutterActor holding the coloured rectangles. Notice that the layout is the width of the widest actor within it and the height of the tallest.

You can also manually set a size on the container associated with a layout to override the automatically-computed size requisition.

2.3.3. Depth ordering

Another important consideration is the depth ordering of actors inside a ClutterBinLayout. By default, the depth ordering mirrors the order in which actors are added to the layout: the earlier an actor is added, the lower down in the depth order it is. If this isn't what you want, you can fix the depth ordering using clutter_actor_set_child_above_sibling(), clutter_actor_set_child_below_sibling() and their relatives.

2.3.4. Other ways to stack actors

ClutterBinLayout makes it simple to lay out large numbers of actors in a stack and align them to the container; see the example below which shows layering of many actors on top of each other.

However, if you have a small number of actors and you need some simple alignment, an alternative is to use manual positioning inside a ClutterFixedLayout, possibly combined with ClutterConstraints to align actors with each other and bind their widths and heights together. See this section for more details.

Note

By default, ClutterActor uses a ClutterFixedLayout as its layout manager.

2.4. Full examples

Example 7.1. ClutterBinLayout, with actors in 9 combinations of start, center and end alignment combinations

#include <clutter/clutter.h>

static const ClutterColor dark_grey = { 0x66, 0x66, 0x66, 0xff };
static const ClutterColor light_grey = { 0xcc, 0xcc, 0xcc, 0xff };

int
main (int argc, char *argv[])
{
  ClutterActor *stage;
  ClutterLayoutManager *layout;
  ClutterActor *box;
  ClutterActor *rect1, *rect2;
  guint align_x, align_y, diff_x, diff_y;
  ClutterColor *color;
  ClutterActor *rect;

  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, 400, 400);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_START,
                                   CLUTTER_BIN_ALIGNMENT_START);

  box = clutter_box_new (layout);

  rect1 = clutter_rectangle_new_with_color (&dark_grey);
  clutter_actor_set_size (rect1, 400, 200);

  rect2 = clutter_rectangle_new_with_color (&light_grey);
  clutter_actor_set_size (rect2, 200, 400);

  clutter_container_add (CLUTTER_CONTAINER (box),
                         rect1,
                         rect2,
                         NULL);

  /*
   * 2 = CLUTTER_BIN_ALIGNMENT_START
   * 3 = CLUTTER_BIN_ALIGNMENT_END
   * 4 = CLUTTER_BIN_ALIGNMENT_CENTER
   */
  for (align_x = 2; align_x < 5; align_x++)
    {
      for (align_y = 2; align_y < 5; align_y++)
        {
          diff_x = align_x - 1;
          if (align_x == 3)
            diff_x = 3;
          else if (align_x == 4)
            diff_x = 2;

          diff_y = align_y - 1;
          if (align_y == 3)
            diff_y = 3;
          else if (align_y == 4)
            diff_y = 2;

          color = clutter_color_new (255 - diff_x * 50,
                                                   100 + diff_y * 50,
                                                   0,
                                                   255);
          rect = clutter_rectangle_new_with_color (color);
          clutter_actor_set_size (rect, 100, 100);
          clutter_bin_layout_set_alignment (CLUTTER_BIN_LAYOUT (layout),
                                            rect,
                                            align_x,
                                            align_y);
          clutter_container_add_actor (CLUTTER_CONTAINER (box), rect);
        }
    }

  clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);

  clutter_actor_show (stage);

  clutter_main ();

  return 0;
}


Example 7.2. Layering multiple textures on top of each other inside a ClutterBinLayout

/*
 * Display multiple rotated copies of an image on top of each other
 *
 * Invoke with the path to a file to load a custom image
 */
#include <clutter/clutter.h>

#define STAGE_SIDE 512

static const ClutterColor box_color = { 0x33, 0x33, 0x55, 0xff };

int
main (int argc, char *argv[])
{
  ClutterLayoutManager *layout;
  ClutterActor *box;
  ClutterActor *stage;
  ClutterActor *texture;
  CoglHandle *cogl_texture;
  GError *error = NULL;
  gfloat width;

  const gchar *filename = "redhand.png";

  if (argc > 1)
    filename = argv[1];

  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, STAGE_SIDE, STAGE_SIDE);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
                                   CLUTTER_BIN_ALIGNMENT_CENTER);

  box = clutter_actor_new ();
  clutter_actor_set_layout_manager (box, layout);
  clutter_actor_set_background_color (box, &box_color);

  texture = clutter_texture_new_from_file (filename, &error);

  if (error != NULL)
    g_error ("Error loading file %s; message was:\n%s",
             filename,
             error->message);

  /*
   * get a reference to the underlying Cogl texture
   * for copying onto each Clutter texture placed into the layout
   */
  cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (texture));

  /*
   * add gradually turning and shrinking textures,
   * smallest one last; each actor ends up on top
   * of the one added just before it
   */
  for (width = STAGE_SIDE * 0.75; width >= STAGE_SIDE * 0.0625; width -= STAGE_SIDE * 0.0625)
    {
      ClutterActor *texture_copy = clutter_texture_new ();
      clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (texture_copy),
                                        cogl_texture);
      clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture_copy),
                                             TRUE);
      clutter_actor_set_z_rotation_from_gravity (texture_copy,
                                                 (gfloat)(width * 0.5) - (STAGE_SIDE * 0.03125),
                                                 CLUTTER_GRAVITY_CENTER);
      clutter_actor_set_width (texture_copy, width);
      clutter_actor_add_child (box, texture_copy);
    }

  clutter_actor_add_constraint (box, clutter_align_constraint_new (stage, CLUTTER_ALIGN_BOTH, 0.5));
  clutter_actor_add_child (stage, box);

  clutter_actor_show (stage);

  clutter_main ();

  return 0;
}