2. Implementing a simple custom actor

2.1. Problem

You want to implement your own ClutterActor; for example, a very simple button widget. But you want to base it on existing Clutter primitives (rectangles, text) to minimise the work required.

2.2. Solution

Implement a custom actor composed from a ClutterBox packed with other ClutterActors. The custom actor provides a facade over these internal actors, simplifying access to their properties and behavior.

In this recipe, we subclass ClutterActor using this approach to create a very simple button widget, CbButton. It is not a complete button implementation: see MxButton for a more comprehensive example (and the basis for this recipe). But this recipe does cover the most important parts of a ClutterActor implementation, as well some useful GObject-related code.

Tip

As Clutter is a GObject-based library, it relies heavily on GObject concepts and idioms. If you are unfamiliar with GObject, please read the GObject Reference Manual before proceeding.

The code for this solution is structured like standard GObject C library code:

  • The header file cb-button.h declares the class' public API (function prototypes, macros, structs).

  • The code file cb-button.c contains the class implementation.

One more example file, actors-composite-main.c, shows how to use CbButton in an application.

Each of these files is described in more detail below.

Note

In a more realistic context, CbButton would have some build infrastructure (for example, autotooling) so it could be compiled, installed, and reused in a variety of applications. However, for the purposes of cookbook examples, these issues are ignored here.

If you are planning on building your own widgets using Clutter as part of an application, or to create your own library, the Mx toolkit provides an excellent example of how to autotool your project.

Example 2.1. cb-button.h: header file

This defines the public API for the class, including GObject type macros, class and object structures, and function prototypes.

/* inclusion guard */
#ifndef __CB_BUTTON_H__
#define __CB_BUTTON_H__

/* include any dependencies */
#include <clutter/clutter.h>

/* GObject implementation */

/* declare this function signature to remove compilation errors with -Wall;
 * the cb_button_get_type() function is actually added via the
 * G_DEFINE_TYPE macro in the .c file
 */
GType cb_button_get_type (void);

/* GObject type macros */
/* returns the class type identifier (GType) for CbButton */
#define CB_TYPE_BUTTON            (cb_button_get_type ())

/* cast obj to a CbButton object structure*/
#define CB_BUTTON(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CB_TYPE_BUTTON, CbButton))

/* check whether obj is a CbButton */
#define CB_IS_BUTTON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CB_TYPE_BUTTON))

/* cast klass to CbButtonClass class structure */
#define CB_BUTTON_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CB_TYPE_BUTTON, CbButtonClass))

/* check whether klass is a member of the CbButtonClass */
#define CB_IS_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CB_TYPE_BUTTON))

/* get the CbButtonClass structure for a CbButton obj */
#define CB_BUTTON_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CB_TYPE_BUTTON, CbButtonClass))

/*
 * Private instance fields; see
 * http://www.gotw.ca/gotw/024.htm for the rationale
 */
typedef struct _CbButtonPrivate CbButtonPrivate;
typedef struct _CbButton        CbButton;
typedef struct _CbButtonClass   CbButtonClass;

/* object structure */
struct _CbButton
{
  /*<private>*/
  ClutterActor parent_instance;

  /* structure containing private members */
  /*<private>*/
  CbButtonPrivate *priv;
};

/* class structure */
struct _CbButtonClass
{
  /* signals */
  void (* clicked)(CbButton *button);

  /*<private>*/
  ClutterActorClass parent_class;
};

/* public API */

/* constructor - note this returns a ClutterActor instance */
ClutterActor *cb_button_new (void);

/* getter */
const gchar *cb_button_get_text (CbButton *self);

/* setters - these are wrappers round functions
 * which change properties of the internal actors
 */
void cb_button_set_text (CbButton    *self,
                         const gchar *text);

void cb_button_set_background_color (CbButton           *self,
                                     const ClutterColor *color);

void cb_button_set_text_color (CbButton           *self,
                               const ClutterColor *color);

#endif /* __CB_BUTTON_H__ */


Example 2.2. cb-button.c: ClutterActor and GObject implementation

This is the main C code file which implements both the GObject and Clutter elements of CbButton. The example below is liberally commented, and also gives some samples of annotations to generate gtk-docs for the widget. The discussion section comments more specifically about the Clutter-specific parts of it.

#include "cb-button.h"

/**
 * SECTION:cb-button
 * @short_description: Button widget
 *
 * A button widget with support for a text label and background color.
 */

/* convenience macro for GType implementations; see:
 * http://library.gnome.org/devel/gobject/2.27/gobject-Type-Information.html#G-DEFINE-TYPE:CAPS
 */
G_DEFINE_TYPE (CbButton, cb_button, CLUTTER_TYPE_ACTOR);

/* macro for accessing the object's private structure */
#define CB_BUTTON_GET_PRIVATE(obj) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CB_TYPE_BUTTON, CbButtonPrivate))

/* private structure - should only be accessed through the public API;
 * this is used to store member variables whose properties
 * need to be accessible from the implementation; for example, if we
 * intend to create wrapper functions which modify properties on the
 * actors composing an object, we should keep a reference to the actors
 * here
 *
 * this is also the place where other state variables go:
 * for example, you might record the current state of the button
 * (toggled on or off) or a background image
 */
struct _CbButtonPrivate
{
  ClutterActor  *child;
  ClutterActor  *label;
  ClutterAction *click_action;
  gchar         *text;
};

/* enumerates property identifiers for this class;
 * note that property identifiers should be non-zero integers,
 * so we add an unused PROP_0 to occupy the 0 position in the enum
 */
enum {
  PROP_0,
  PROP_TEXT
};

/* enumerates signal identifiers for this class;
 * LAST_SIGNAL is not used as a signal identifier, but is instead
 * used to delineate the size of the cache array for signals (see below)
 */
enum {
  CLICKED,
  LAST_SIGNAL
};

/* cache array for signals */
static guint cb_button_signals[LAST_SIGNAL] = { 0, };

/* from http://mail.gnome.org/archives/gtk-devel-list/2004-July/msg00158.html:
 *
 * "The finalize method finishes releasing the remaining
 * resources just before the object itself will be freed from memory, and
 * therefore it will only be called once. The two step process helps break
 * cyclic references. Both dispose and finalize must chain up to their
 * parent objects by calling their parent's respective methods *after* they
 * have disposed or finalized their own members."
 */
static void
cb_button_finalize (GObject *gobject)
{
  CbButtonPrivate *priv = CB_BUTTON (gobject)->priv;

  g_free (priv->text);

  /* call the parent class' finalize() method */
  G_OBJECT_CLASS (cb_button_parent_class)->finalize (gobject);
}

/* enables objects to be uniformly treated as GObjects;
 * also exposes properties so they become scriptable, e.g.
 * through ClutterScript
 */
static void
cb_button_set_property (GObject      *gobject,
                        guint         prop_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  CbButton *button = CB_BUTTON (gobject);

  switch (prop_id)
    {
    case PROP_TEXT:
      cb_button_set_text (button, g_value_get_string (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

/* enables objects to be uniformly treated as GObjects */
static void
cb_button_get_property (GObject    *gobject,
                        guint       prop_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  CbButtonPrivate *priv = CB_BUTTON (gobject)->priv;

  switch (prop_id)
    {
    case PROP_TEXT:
      g_value_set_string (value, priv->text);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

/* ClutterActor implementation
 *
 * we only implement destroy(), get_preferred_height(), get_preferred_width(),
 * allocate(), and paint(), as this is the minimum we can get away with
 */

/* composite actors should implement destroy(), and inside their
 * implementation destroy any actors they are composed from;
 * in this case, we just destroy the child ClutterBox
 */
static void
cb_button_destroy (ClutterActor *self)
{
  CbButtonPrivate *priv = CB_BUTTON (self)->priv;

  /* we just destroy the child, and let the child
   * deal with destroying _its_ children; note that we have a guard
   * here in case the child has already been destroyed
   */
  if (priv->child)
    {
      clutter_actor_destroy (priv->child);
      priv->child = NULL;
    }

  /* chain up to destroy() on the parent ClutterActorClass;
   * note that we check the parent class has a destroy() implementation
   * before calling it
   */
  if (CLUTTER_ACTOR_CLASS (cb_button_parent_class)->destroy)
    CLUTTER_ACTOR_CLASS (cb_button_parent_class)->destroy (self);
}

/* get_preferred_height and get_preferred_width defer to the
 * internal ClutterBox, adding 20px padding on each axis;
 * min_*_p is the minimum height or width the actor should occupy
 * to be useful; natural_*_p is the height or width the actor
 * would occupy if not constrained
 *
 * note that if we required explicit sizing for CbButtons
 * (i.e. a developer must set their height and width),
 * we wouldn't need to implement these functions
 */
static void
cb_button_get_preferred_height (ClutterActor *self,
                                gfloat for_width,
                                gfloat *min_height_p,
                                gfloat *natural_height_p)
{
  CbButtonPrivate *priv = CB_BUTTON (self)->priv;

  clutter_actor_get_preferred_height (priv->child,
                                      for_width,
                                      min_height_p,
                                      natural_height_p);

  *min_height_p += 20.0;
  *natural_height_p += 20.0;
}

static void
cb_button_get_preferred_width (ClutterActor *self,
                               gfloat for_height,
                               gfloat *min_width_p,
                               gfloat *natural_width_p)
{
  CbButtonPrivate *priv = CB_BUTTON (self)->priv;

  clutter_actor_get_preferred_width (priv->child,
                                     for_height,
                                     min_width_p,
                                     natural_width_p);

  *min_width_p += 20.0;
  *natural_width_p += 20.0;
}

/* use the actor's allocation for the ClutterBox */
static void
cb_button_allocate (ClutterActor          *actor,
                    const ClutterActorBox *box,
                    ClutterAllocationFlags flags)
{
  CbButtonPrivate *priv = CB_BUTTON (actor)->priv;
  ClutterActorBox child_box = { 0, };

  /* set the allocation for the whole button */
  CLUTTER_ACTOR_CLASS (cb_button_parent_class)->allocate (actor, box, flags);

  /* make the child (the ClutterBox) fill the parent;
   * note that this allocation box is relative to the
   * coordinates of the whole button actor, so we can't just
   * use the box passed into this function; instead, it
   * is adjusted to span the whole of the actor, from its
   * top-left corner (0,0) to its bottom-right corner
   * (width,height)
   */
  child_box.x1 = 0.0;
  child_box.y1 = 0.0;
  child_box.x2 = clutter_actor_box_get_width (box);
  child_box.y2 = clutter_actor_box_get_height (box);

  clutter_actor_allocate (priv->child, &child_box, flags);
}

/* paint function implementation: just calls paint() on the ClutterBox */
static void
cb_button_paint (ClutterActor *actor)
{
  CbButtonPrivate *priv = CB_BUTTON (actor)->priv;

  clutter_actor_paint (priv->child);
}

/* proxy ClickAction signals so they become signals from the actor */
static void
cb_button_clicked (ClutterClickAction *action,
                   ClutterActor       *actor,
                   gpointer            user_data)
{
  /* emit signal via the cache array */
  g_signal_emit (actor, cb_button_signals[CLICKED], 0);
}

/* GObject class and instance initialization functions; note that
 * these have been placed after the Clutter implementation, as
 * they refer to the static function implementations above
 */

/* class init: attach functions to superclasses, define properties
 * and signals
 */
static void
cb_button_class_init (CbButtonClass *klass)
{
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamSpec *pspec;

  gobject_class->finalize = cb_button_finalize;
  gobject_class->set_property = cb_button_set_property;
  gobject_class->get_property = cb_button_get_property;

  actor_class->destroy = cb_button_destroy;
  actor_class->get_preferred_height = cb_button_get_preferred_height;
  actor_class->get_preferred_width = cb_button_get_preferred_width;
  actor_class->allocate = cb_button_allocate;
  actor_class->paint = cb_button_paint;

  g_type_class_add_private (klass, sizeof (CbButtonPrivate));

  /**
   * CbButton:text:
   *
   * The text shown on the #CbButton
   */
  pspec = g_param_spec_string ("text",
                               "Text",
                               "Text of the button",
                               NULL,
                               G_PARAM_READWRITE);
  g_object_class_install_property (gobject_class, PROP_TEXT, pspec);

  /**
   * CbButton::clicked:
   * @button: the #CbButton that emitted the signal
   *
   * The ::clicked signal is emitted when the internal #ClutterClickAction
   * associated with a #CbButton emits its own ::clicked signal
   */
  cb_button_signals[CLICKED] =
    g_signal_new ("clicked",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (CbButtonClass, clicked),
                  NULL,
                  NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);
}

/* object init: create a private structure and pack
 * composed ClutterActors into it
 */
static void
cb_button_init (CbButton *self)
{
  CbButtonPrivate *priv;
  ClutterLayoutManager *layout;

  priv = self->priv = CB_BUTTON_GET_PRIVATE (self);

  clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);

  /* the only child of this actor is a ClutterBox with a
   * ClutterBinLayout: painting and allocation of the actor basically
   * involves painting and allocating this child box
   */
  layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
                                   CLUTTER_BIN_ALIGNMENT_CENTER);

  priv->child = clutter_actor_new ();
  clutter_actor_set_layout_manager (priv->child, layout);

  /* set the parent of the ClutterBox to this instance */
  clutter_actor_add_child (CLUTTER_ACTOR (self), priv->child);

  /* add text label to the button; see the ClutterText API docs
   * for more information about available properties
   */
  priv->label = g_object_new (CLUTTER_TYPE_TEXT,
                              "line-alignment", PANGO_ALIGN_CENTER,
                              "ellipsize", PANGO_ELLIPSIZE_END,
                              NULL);

  clutter_actor_add_child (priv->child, priv->label);

  /* add a ClutterClickAction on this actor, so we can proxy its
   * "clicked" signal into a signal from this actor
   */
  priv->click_action = clutter_click_action_new ();
  clutter_actor_add_action (CLUTTER_ACTOR (self), priv->click_action);

  g_signal_connect (priv->click_action,
                    "clicked",
                    G_CALLBACK (cb_button_clicked),
                    NULL);
}

/* public API */
/* examples of public API functions which wrap functions
 * on internal actors
 */

/**
 * cb_button_set_text:
 * @self: a #CbButton
 * @text: the text to display on the button
 *
 * Set the text on the button
 */
void
cb_button_set_text (CbButton    *self,
                    const gchar *text)
{
  CbButtonPrivate *priv;

  /* public API should check its arguments;
   * see also g_return_val_if_fail for functions which
   * return a value
   */
  g_return_if_fail (CB_IS_BUTTON (self));

  priv = self->priv;

  g_free (priv->text);

  if (text)
    priv->text = g_strdup (text);
  else
    priv->text = g_strdup ("");

  /* call a function on the ClutterText inside the layout */
  clutter_text_set_text (CLUTTER_TEXT (priv->label), priv->text);
}

/**
 * cb_button_set_background_color:
 * @self: a #CbButton
 * @color: the #ClutterColor to use for the button's background
 *
 * Set the color of the button's background
 */
void
cb_button_set_background_color (CbButton           *self,
                                const ClutterColor *color)
{
  g_return_if_fail (CB_IS_BUTTON (self));

  clutter_actor_set_background_color (self->priv->child, color);
}

/**
 * cb_button_set_text_color:
 * @self: a #CbButton
 * @color: the #ClutterColor to use as the color for the button text
 *
 * Set the color of the text on the button
 */
void
cb_button_set_text_color (CbButton           *self,
                          const ClutterColor *color)
{
  g_return_if_fail (CB_IS_BUTTON (self));

  clutter_text_set_color (CLUTTER_TEXT (self->priv->label), color);
}

/**
 * cb_button_get_text:
 * @self: a #CbButton
 *
 * Get the text displayed on the button
 *
 * Returns: the button's text. This must not be freed by the application.
 */
const gchar *
cb_button_get_text (CbButton *self)
{
  g_return_val_if_fail (CB_IS_BUTTON (self), NULL);

  return self->priv->text;
}

/**
 * cb_button_new:
 *
 * Creates a new #CbButton instance
 *
 * Returns: a new #CbButton
 */
ClutterActor *
cb_button_new (void)
{
  return g_object_new (CB_TYPE_BUTTON, NULL);
}


Example 2.3. actors-composite-main.c: trivial application demonstrating usage of CbButton

Note how any of the ClutterActor functions (like clutter_actor_set_size() and clutter_actor_add_constraint()) can be applied to instances of our ClutterActor implementation.

#include <stdlib.h>
#include "cb-button.h"

/* colors */
static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor white_color = { 0xff, 0xff, 0xff, 0xff };
static const ClutterColor yellow_color = { 0x88, 0x88, 0x00, 0xff };

/* click handler */
static void
clicked (CbButton *button,
         gpointer  data)
{
  const gchar *current_text;

  g_debug ("Clicked");

  current_text = cb_button_get_text (button);

  if (g_strcmp0 (current_text, "hello") == 0)
    cb_button_set_text (button, "world");
  else
    cb_button_set_text (button, "hello");
}

int
main (int   argc,
      char *argv[])
{
  ClutterActor *stage;
  ClutterActor *button;
  ClutterConstraint *align_x_constraint;
  ClutterConstraint *align_y_constraint;

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

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, 400, 400);
  clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  button = cb_button_new ();
  cb_button_set_text (CB_BUTTON (button), "hello");

  /* the following is equivalent to the two lines above:
   *
   *  button = g_object_new (CB_TYPE_BUTTON,
   *                         "text", "winkle",
   *                         NULL);
   *
   * because we defined a set_property function, which can accept
   * a PROP_TEXT parameter, GObject can create a button and set one
   * or more properties with a single call to g_object_new()
   */

  /* note that the size of the button is left to Clutter's size requisition */
  cb_button_set_text_color (CB_BUTTON (button), &white_color);
  cb_button_set_background_color (CB_BUTTON (button), &yellow_color);
  g_signal_connect (button, "clicked", G_CALLBACK (clicked), NULL);

  align_x_constraint = clutter_align_constraint_new (stage,
                                                     CLUTTER_ALIGN_X_AXIS,
                                                     0.5);

  align_y_constraint = clutter_align_constraint_new (stage,
                                                     CLUTTER_ALIGN_Y_AXIS,
                                                     0.5);

  clutter_actor_add_constraint (button, align_x_constraint);
  clutter_actor_add_constraint (button, align_y_constraint);

  clutter_actor_add_child (stage, button);

  clutter_actor_show (stage);

  clutter_main ();

  return EXIT_SUCCESS;
}


2.3. Discussion

The actor implemented here is based on simple composition: bundling several actors together and wrapping their behavior and properties. In the example here, we make use of a ClutterLayoutManager to handle positioning of the ClutterText; we change the background color of the button by changing the color of the ClutterBox; and we use a ClutterClickAction to simplify implementation of a click signal.

You may find that this approach is appropriate if you need to implement a simple rectangular actor. However, it puts some constraints on the outline of the actor, making it harder to use a custom outline: for example, a rectangle with rounded corners or a shape which can't be approximated by a rectangle. Such cases require both pick() and paint() implementations using Cogl (or similar): see this recipe for more details.

The composition approach may also be inappropriate where you need to do a lot of custom animation and drawing; and it is likely to be inappropriate for implementing a container actor. See the notes on implementing a new actor in the Clutter reference manual for more details of what may be required in these cases.

2.3.1. Implementing ClutterActor virtual functions

While most of the CbButton implementation revolves around GObject, there are some elements of it specific to Clutter. Due to the simplicity of the CbButton actor, the implementation of these functions is fairly trivial, as explained below:

  • Object destruction: cb_button_destroy()ClutterActor subclasses based on composition should implement the destroy() virtual function. This is called on an actor when its container is destroyed to clean up the resources allocated to the actor; it also emits a destroy signal which other code can hook onto.

    In the case of CbButton, the destroy() implementation calls clutter_actor_destroy() on the child ClutterBox, then sets that child to NULL. Finally, it checks for a destroy() implementation on the parent class, then calls it if one exists.

  • Size requisition: cb_button_get_preferred_height() and cb_button_get_preferred_width()During the size requisition phase, Clutter asks each actor the minimum size it should be to remain useful, and the maximum size it would be if unconstrained. This is done by calling the get_preferred_height() and get_preferred_width() functions on each actor in turn.

    If an actor will only ever be explictly sized (via clutter_actor_set_size(), clutter_actor_set_height() and/or clutter_actor_set_width()), there is no need to implement the get_preferred_*() functions. (Some actors like ClutterRectangle work this way and require explicit sizing.)

    However, if an actor's size should be negotiated during the size requisition phase, you can implement these functions, using the size of the child actors as a basis for the preferred height and width. In the case of CbButton, a preferred height and width can be computed; these are based on the height and width of the child ClutterBox, plus 20 pixels on each axis. Because the size of the box is itself dependent on the size of the ClutterText inside it, the net result is that the CbButton preferred size is the size of the text actor inside it, plus 20 pixels on each axis.

  • Allocation: cb_button_allocate()The requests gathered during size requisition are then negotiated by Clutter, each actor receiving some allocation of the available space. At the end of this process, each actor is allocated a box, representing the space available to it on the stage.

    An actor implementation is responsible for distributing space from its allocation box to its children as it sees fit. In the case of CbButton, there is only a single ClutterBox actor which needs allocation; cb_button_allocate() therefore allocates all of the button's space to its child ClutterBox.

  • Painting and picking: cb_button_paint()Clutter works its way through the actors on the stage, following the actor hierarchy (top level actors directly inside the stage first); clutter_actor_paint() is called on each actor. This, in turn, calls the actor's paint() implementation. If the actor is a container, it may iterate over its children, calling paint() on each; the children may call paint() on their children...; and so on, until the leaves of the actor hierarchy are reached.

    As our actor consists of a single ClutterBox child, its paint() implementation simply has to retrieve the reference to that ClutterBox (from its private structure) and call clutter_actor_paint() on it. Painting of the ClutterBox's child (the ClutterText) is handled by the ClutterBox.

    In cases where an actor is non-rectangular, you also need to implement a pick() function. (This is used to determine which actor was the recipient of an event occurring within the stage.) However, because the actor in this recipe is a simple rectangle, there is no need to implement pick().