Example

The following example demonstrates the implementation of a box container that lays its child actors out horizontally. A real container should probably allow optional padding around the container and spacing between the child actors. You might also want to allow some child actors to expand to fill the available space, or align differently inside the container.

Figure A.2. Behaviour

Behaviour

Source code

File: examplebox.h

#ifndef CLUTTER_TUTORIAL_EXAMPLEBOX_H
#define CLUTTER_TUTORIAL_EXAMPLEBOX_H

#include <cluttermm.h>
#include <list>

namespace Tutorial
{

class Box : public Clutter::Actor, public Clutter::Container
{
public:
  virtual ~Box();
  static Glib::RefPtr<Box> create();

  void remove_all();

protected:
  Box();

  // Clutter::Container interface:
  virtual void add_vfunc(const Glib::RefPtr<Actor>& actor);
  virtual void remove_vfunc(const Glib::RefPtr<Actor>& actor);
  virtual void raise_vfunc(const Glib::RefPtr<Actor>& actor, const Glib::RefPtr<Actor>& sibling);
  virtual void lower_vfunc(const Glib::RefPtr<Actor>& actor, const Glib::RefPtr<Actor>& sibling);
  virtual void sort_depth_order_vfunc();
  virtual void foreach_vfunc(ClutterCallback callback, gpointer user_data);

  // Clutter::Actor interface:
  virtual void on_paint();
  virtual void show_all_vfunc();
  virtual void hide_all_vfunc();
  virtual void pick_vfunc(const Clutter::Color& color);
  virtual void get_preferred_width_vfunc(Clutter::Unit for_height,
    Clutter::Unit& min_width_p, Clutter::Unit& natural_width_p);
  virtual void get_preferred_height_vfunc(Clutter::Unit for_width,
    Clutter::Unit& min_height_p, Clutter::Unit& natural_height_p);
  virtual void allocate_vfunc(const Clutter::ActorBox& box, bool absolute_origin_changed);

private:
  typedef std::list< Glib::RefPtr<Clutter::Actor> > ChildrenList;
  ChildrenList children_;
};

} // namespace Tutorial

#endif /* !CLUTTER_TUTORIAL_EXAMPLEBOX_H */

File: main.cc

#include "examplebox.h"
#include <cluttermm.h>

int main(int argc, char** argv)
{
  Clutter::init(&argc, &argv);

  // Get the stage and set its size and color:
  const Glib::RefPtr<Clutter::Stage> stage = Clutter::Stage::get_default();
  stage->set_size(200, 200);
  stage->set_color(Clutter::Color(0x00, 0x00, 0x00, 0xFF)); // black

  // Add our custom container to the stage:
  const Glib::RefPtr<Tutorial::Box> box = Tutorial::Box::create();

  // Set the size to the preferred size of the container:
  box->set_size(-1, -1);
  box->set_position(20, 20);

  // Add some actors to our container:
  const Glib::RefPtr<Clutter::Rectangle>
    rect1 = Clutter::Rectangle::create(Clutter::Color(0xFF, 0xFF, 0xFF, 0x99));
  rect1->set_size(75, 75);
  box->add_actor(rect1);

  const Glib::RefPtr<Clutter::Rectangle>
    rect2 = Clutter::Rectangle::create(Clutter::Color(0x10, 0x40, 0x90, 0xFF));
  rect2->set_size(75, 75);
  box->add_actor(rect2);

  stage->add_actor(box);
  box->show_all();
  stage->show();

  // Start the main loop, so we can respond to events:
  Clutter::main();

  return 0;
}

File: examplebox.cc

#include "examplebox.h"
#include <cogl/cogl.h>
#include <algorithm>

namespace Tutorial
{

/*
 * Simple example of a container actor.
 *
 * Tutorial::Box imposes a specific layout on its children, unlike
 * Clutter::Group which is a free-form container.
 *
 * Specifically, Tutorial::Box lays out its children along an imaginary
 * horizontal line.
 */

Box::Box()
:
  // Create a named GObject type for the custom container class:
  Glib::ObjectBase(typeid(Box))
{
  property_request_mode() = Clutter::REQUEST_WIDTH_FOR_HEIGHT;
}

Box::~Box()
{
  remove_all();
}

Glib::RefPtr<Box> Box::create()
{
  return Glib::RefPtr<Box>(new Box());
}

void Box::remove_all()
{
  while (!children_.empty())
    remove_actor(children_.front());
}

void Box::add_vfunc(const Glib::RefPtr<Clutter::Actor>& actor)
{
  children_.push_front(actor);

  // Ugly but necessary:  Explicitely acquire an additional reference
  // because Glib::RefPtr assumes ownership.
  actor->set_parent(Glib::RefPtr<Clutter::Actor>((reference(), this)));
  actor_added(actor);
  queue_relayout();
}

void Box::remove_vfunc(const Glib::RefPtr<Clutter::Actor>& actor)
{
  const Glib::RefPtr<Clutter::Actor> element = actor;
  const ChildrenList::iterator p = std::find(children_.begin(), children_.end(), element);

  if(p != children_.end())
  {
    element->unparent();
    children_.erase(p);
    actor_removed(element);
    queue_relayout();
  }
}

void Box::raise_vfunc(const Glib::RefPtr<Clutter::Actor>&, const Glib::RefPtr<Clutter::Actor>&)
{
  g_assert_not_reached();
}

void Box::lower_vfunc(const Glib::RefPtr<Clutter::Actor>&, const Glib::RefPtr<Clutter::Actor>&)
{
  g_assert_not_reached();
}

void Box::sort_depth_order_vfunc()
{
  g_assert_not_reached();
}

void Box::foreach_vfunc(ClutterCallback callback, gpointer user_data)
{
  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
    callback((*p)->gobj(), user_data);
}

void Box::on_paint()
{
  cogl_push_matrix();

  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
  {
    if((*p)->is_mapped())
      (*p)->paint();
  }

  cogl_pop_matrix();
}

void Box::show_all_vfunc()
{
  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
    (*p)->show();

  show();
}

void Box::hide_all_vfunc()
{
  hide();

  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
    (*p)->hide();
}

void Box::pick_vfunc(const Clutter::Color& color)
{
  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
  {
    if((*p)->is_mapped())
      (*p)->pick(color);
  }
}

/*
 * For this container, the preferred width is the sum of the widths
 * of the children. The preferred width depends on the height provided
 * by for_height.
 */
void Box::get_preferred_width_vfunc(Clutter::Unit  for_height,
                                    Clutter::Unit& min_width_p,
                                    Clutter::Unit& natural_width_p)
{
  Clutter::Unit min_width     = 0;
  Clutter::Unit natural_width = 0;

  // Calculate the preferred width for this container, 
  // based on the preferred width requested by the children.
  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
    if((*p)->is_visible())
    {
      Clutter::Unit child_min_width     = 0;
      Clutter::Unit child_natural_width = 0;

      (*p)->get_preferred_width(for_height, child_min_width, child_natural_width);

      min_width     += child_min_width;
      natural_width += child_natural_width;
    }

  min_width_p     = min_width;
  natural_width_p = natural_width;
}

/*
 * For this container, the preferred height is the maximum height
 * of the children. The preferred height is independent of the given width.
 */
void Box::get_preferred_height_vfunc(Clutter::Unit  for_width,
                                     Clutter::Unit& min_height_p,
                                     Clutter::Unit& natural_height_p)
{
  Clutter::Unit min_height     = 0;
  Clutter::Unit natural_height = 0;

  // Calculate the preferred height for this container,
  // based on the preferred height requested by the children.
  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
    if((*p)->is_visible())
    {
      Clutter::Unit child_min_height     = 0;
      Clutter::Unit child_natural_height = 0;

      (*p)->get_preferred_height(-1, child_min_height, child_natural_height);

      min_height     = std::max(min_height,     child_min_height);
      natural_height = std::max(natural_height, child_natural_height);
    }

  min_height_p     = min_height;
  natural_height_p = natural_height;
}

void Box::allocate_vfunc(const Clutter::ActorBox& box, bool absolute_origin_changed)
{
  Clutter::Unit child_x = 0;

  for(ChildrenList::iterator p = children_.begin(); p != children_.end(); ++p)
  {
    Clutter::Unit min_width    = 0;
    Clutter::Unit min_height   = 0;
    Clutter::Unit child_width  = 0;
    Clutter::Unit child_height = 0;

    (*p)->get_preferred_size(min_width, min_height, child_width, child_height);

    // Calculate the position and size that the child may actually have.
    // Position the child just after the previous child, horizontally.
    const Clutter::ActorBox child_box (child_x, 0, child_x + child_width, child_height);
    child_x += child_width;

    // Tell the child what position and size it may actually have:
    (*p)->allocate(child_box, absolute_origin_changed);
  }

  Clutter::Actor::allocate_vfunc(box, absolute_origin_changed);
}

} // namespace Tutorial