Actors Edmon Gween, actor, on his deathbed An actor's a guy who if you ain't talkin' about him, ain't listening.
Introduction When building a User Interface with Clutter, the visible part of the UI — that is, what is displayed on the screen — is commonly referred to as "the scene graph". Like every graph, a scene graph is composed by nodes. Every node on the Clutter scene graph is an actor. Every actor has a single relationship with the others: it can be the parent of another actor, or a child of another actor. The stage is an actor that can have children but cannot have any parent. Actors have different attributes: a position, a size, a scale factor, a rotation angle on each axis (relative to a specific center on the normal plane for that axis), an opacity factor. The scene graph is not fixed: it can be changed, not only by adding or removing actors, but also by changing the parent-child relationship: it is possible, for instance, to move an entire section of the scene graph from one parent actor to another.
Implementing a simple custom actor
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.
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. 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. You might also find this tutorial a useful introduction. 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. 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. <filename>cb-button.h</filename>: header file This defines the public API for the class, including GObject type macros, class and object structures, and function prototypes. a code sample should be here... but isn't <filename>cb-button.c</filename>: <type>ClutterActor</type> 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. a code sample should be here... but isn't <filename>actors-composite-main.c</filename>: trivial application demonstrating usage of <type>CbButton</type> 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. a code sample should be here... but isn't
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.
Implementing <type>ClutterActor</type> 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: <function>cb_button_destroy()</function> 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: <function>cb_button_get_preferred_height()</function> and <function>cb_button_get_preferred_width()</function> 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: <function>cb_button_allocate()</function> 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: <function>cb_button_paint()</function> 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().
Knowing when an actor's position or size changes
Problem You want to know when the position or the size, or both, of an actor change, for instance to update an unrelated actor or some internal state.
Solution You can use the notify signal, detailed with the coordinate or the dimension you want to know has changed: g_signal_connect (actor, "notify::x", G_CALLBACK (on_x_changed), NULL); g_signal_connect (actor, "notify::height", G_CALLBACK (on_height_changed), NULL); g_signal_connect (actor, "notify::depth", G_CALLBACK (on_depth_changed), NULL); If you want to know if any of the coordinates or dimensions of an actor have been changed, except for depth, you can use the allocation-changed signal: g_signal_connect (actor, "allocation-changed", G_CALLBACK (on_allocation_changed), NULL); The signature for the handler of the "notify" signal is: void on_notify (GObject *gobject, GParamSpec *pspec, gpointer user_data); While the signature for the handler of the "allocation-changed" signal is: void on_allocation_changed (ClutterActor *actor, const ClutterActorBox *allocation, ClutterAllocationFlags flags, gpointer user_data);
Discussion Any change the position and size of an actor will cause a change in the allocation of the actor itself. This will update the values of the x, y, width and height properties as well. The first technique allows a greater deal of granularity, allowing you to know what exactly changed. Inside the callback for the signal you can query the value of the property: void on_x_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data) { gint x_value = 0; /* Round the X coordinate to the nearest pixel */ x_value = floorf (clutter_actor_get_x (CLUTTER_ACTOR (gobject))) + 0.5; g_print ("The new X coordinate is '%d' pixels\n", x_value); } The second technique is more indicated if you want to get notification that any of the positional or dimensional attributes changed, except for the depth: void on_allocation_changed (ClutterActor *actor, const ClutterActorBox *allocation, ClutterAllocationFlags flags, gpointer user_data) { g_print ("The bounding box is now: (%.2f, %.2f) (%.2f x %.2f)\n", clutter_actor_box_get_x (allocation), clutter_actor_box_get_y (allocation), clutter_actor_box_get_width (allocation), clutter_actor_box_get_height (allocation)); } All actors will update these properties when their size or position change. Note that the stage, on the other hand, will not notify on position changes, so it is not possible to use the x and y properties to know that the platform-specific window embedding the stage has been moved — if the platform supports a windowing system. In order to achieve that you will have to use backend-specific API to extract the surface used by the stage and then platform-specific API to retrieve its coordinates.
Overriding the paint sequence
Problem You want to override the way an actor paints itself without creating a subclass.
Solution You can use the paint signal to invoke a callback that will be executed before the actor's paint implementation: g_signal_connect (actor, "paint", G_CALLBACK (on_paint), NULL); You can paint something after the actor's paint implementation by using the g_signal_connect_after() function instead of g_signal_connect(): g_signal_connect_after (actor, "paint", G_CALLBACK (on_paint_after), NULL); The signature for the handler of the "paint" signal is: void on_paint (ClutterActor *actor, gpointer user_data);
Discussion The paint cycle in Clutter works its way recursively from the stage through every child. Whenever an Actor is going to be painted it will be positioned in a new frame of reference according to the list of transformations (scaling, rotation and additional translations). After that, the "paint" signal will be emitted. The "paint" signal is defined as run-last, that is the signal handlers connected to it using g_signal_connetc() will be called first; then the default handler defined by the Actor's sub-class will be called; finally, all the signal handlers connected to the signal using g_signal_connect_after() will be called. This allows pre- and post-default paint handlers, and it also allows completely overriding the way an Actor draws itself by default; for instance: void on_paint (ClutterActor *actor) { do_my_paint (actor); g_signal_stop_emission_by_name (actor, "paint"); } The code above will prevent the default paint implementation of the actor from running.
Making an actor transparent by changing its opacity
Problem You want an actor to be transparent so that other actors are visible through it.
Solution Change the actor's opacity so that it is partially (or even fully) transparent: /* 25% transparency */ clutter_actor_set_opacity (actor, 191); /* 50% transparency */ clutter_actor_set_opacity (actor, 122); /* completely transparent */ clutter_actor_set_opacity (actor, 0); Any actor covered or overlapped by the transparent actor should be visible through it; the Discussion section gives some examples of how visible you can expect the covered or overlapped actor to be.
Discussion Opacity is a property of every ClutterActor. It is a float on a scale from 0 (invisible) to 255 (completely opaque). Actors with 0 < opacity < 255 will have a varying amount of solidity on the stage, so other actors may be visible through them. For example, below are 4 yellow rectangles overlapping a white rectangle on a blue stage: The effect of different opacities levels on an actor's appearance The rectangles have the following opacities: top-left: 255 (0% transparency) top-right: 191 (25% transparency) bottom-right: 122 (50% transparency) bottom-left: 61 (75% transparency) Notice how both the stage and the white rectangle are visible through the yellow rectangles. As opacity is a property of every actor, it can be animated like any other GObject property, using any of the approaches in the animation API. The following sections cover some other considerations when working with actor opacity.
Container and color opacity If a container has its opacity set, any children of the container have their opacity combined with their parent's opacity. For example, if a parent has an opacity of 122 (50% transparent) and the child also has an opacity of 122, the child's effective opacity is 25% (opacity = 61, and it is 75% transparent). To demonstrate the visual effect of this, here are three rectangles with the same color but different opacity settings, inside parents which also have different opacity settings: How a container's opacity affects the opacity of its children The left-hand rectangle has opacity = 255 and is in a ClutterGroup with opacity = 255. This means it is fully opaque. The middle rectangle has opacity = 255 and is in a ClutterGroup with opacity = 122. Notice that the parent opacity makes the rectangle appear darker, as the stage colour is showing through from behind. The right-hand rectangle has opacity = 122 and is in a ClutterGroup with opacity = 122. Notice that the rectangle appears to be even darker, as the stage colour is showing through both the rectangle and its parent. Similarly, ClutterColor also contains an alpha property which governs the transparency of the color. Where an actor can have a color set (e.g. ClutterRectangle) the alpha value of the color also affects the transparency of the actor, for example:
Depth and depth order Each actor has two more aspects which affect its apparent opacity: An actor's depth can have an effect if the stage has fog (a depth cueing effect) turned on. As an actor's depth increases, the actor apparently "recedes" from view and gradually blends into the colour of the stage. This produces an effect similar to making the actor transparent. See the ClutterStage documentation for more details about fog. Depth also needs to be considered if you want one actor to be visible through another: the actor you want to see through a transparent actor must be "deeper" than (or at the same depth as) the transparent actor. The depth order governs how actors within a ClutterContainer implementation are placed with respect to each other. Depth ordering is not the same thing as depth: depth ordering records relationships between actors at the same depth. If you have two overlapping actors actorA and actorB in a container, and you want actorA (opaque) to be visible through actorB (transparent), you should ensure that actorB is "above" actorA in the depth ordering. You could do this as follows: /* * raise actorB so it is above actorA in the depth order; * NB actorA and actorB both need to be in the same container * for this to work */ clutter_actor_raise (actorB, actorA); clutter_actor_set_child_above_sibling(), clutter_actor_set_child_below_sibling() and related ClutterActor functions set depth ordering on actors.
Creating an actor with a non-rectangular shape
Problem You want to create a ClutterActor subclass, but don't want it to be rectangular; for example, you want a star-shaped actor.
Solution Use Cogl primitives to draw the actor. Below is an example of the pick and paint implementations for a star-shaped StarActor class (an extension of ClutterActor). Like ClutterRectangle, it has a private struct internally, which contains a ClutterColor denoting the color it should be painted. This is used to set the Cogl source color. priv->color; clutter_actor_get_allocation_box (actor, &allocation); clutter_actor_box_get_size (&allocation, &width, &height); tmp_alpha = clutter_actor_get_paint_opacity (actor) * color.alpha / 255; cogl_path_new (); cogl_set_source_color4ub (color.red, color.green, color.blue, tmp_alpha); /* create and store a path describing a star */ cogl_path_move_to (width * 0.5, 0); cogl_path_line_to (width, height * 0.75); cogl_path_line_to (0, height * 0.75); cogl_path_move_to (width * 0.5, height); cogl_path_line_to (0, height * 0.25); cogl_path_line_to (width, height * 0.25); cogl_path_line_to (width * 0.5, height); cogl_path_fill (); } static void star_actor_pick (ClutterActor *actor, const ClutterColor *pick_color) { if (!clutter_actor_should_pick_paint (actor)) return; ClutterActorBox allocation = { 0, }; gfloat width, height; clutter_actor_get_allocation_box (actor, &allocation); clutter_actor_box_get_size (&allocation, &width, &height); cogl_path_new (); cogl_set_source_color4ub (pick_color->red, pick_color->green, pick_color->blue, pick_color->alpha); /* create and store a path describing a star */ cogl_path_move_to (width * 0.5, 0); cogl_path_line_to (width, height * 0.75); cogl_path_line_to (0, height * 0.75); cogl_path_move_to (width * 0.5, height); cogl_path_line_to (0, height * 0.25); cogl_path_line_to (width, height * 0.25); cogl_path_line_to (width * 0.5, height); cogl_path_fill (); } ]]> If you need more information about how to implement your own ClutterActor, see the Clutter reference manual. Note that the code in these two functions is virtually identical: the Discussion section suggests how to remove this redundancy.
Discussion The above is one approach to creating a non-rectangular actor. But it's also possible to get a similar effect by subclassing an existing actor (like ClutterRectangle) and giving it a non-rectangular appearance. You could do this by making the underlying rectangle transparent and then drawing on top of it (e.g. using Cairo or Cogl). However, if you then made such an actor reactive, events like mouse button presses would be triggered from anywhere on the underlying rectangle. This is true even if the visible part of the actor only partially fills the rectangle (underneath, it's still a rectangle). The advantage of using Cogl paths is that the reactive area of the actor is defined by the Cogl path. So if you have a star-shaped actor, only clicks (or other events) directly on the star will have any effect on it.
Cogl path coordinates In the example shown, cogl_path_move_to() and cogl_path_line_to() are used. These take absolute x and y coordinates as arguments, relative to the GL 'modelview' transform matrix; in the case of an actor's paint implementation, relative to the bounding box for the actor. So if an actor has width and height of 50 pixels, and you used cogl_move_to (25, 25) in its paint implementation, the "pen" moves to the centre of the actor, regardless of where the actor is positioned on the stage. Similarly, using cogl_path_line_to() creates a line segment from the current pen position to the absolute coordinates (x, y) specified. The Cogl API also provides various "rel" variants of the path functions (e.g. cogl_path_rel_line_to()), which create path segments relative to the current pen position (i.e. pen_x + x, pen_y + y). It's important to note that the path isn't drawn until you call cogl_path_stroke() (to draw the path segments) or cogl_path_fill() (to fill the area enclosed by the path). The path is cleared once it's been drawn. Using the *_preserve variants of these functions draws the path and retains it (so it could be drawn again).
Other Cogl primitives Note that the Cogl primitives API provides other types of path segment beyond straight lines that we didn't use here, including: Bezier curves (cogl_path_curve_to()) Arcs (cogl_path_arc()) Polygons (cogl_path_polygon()) Rectangles (cogl_path_rectangle()) Rectangles with rounded corners (cogl_path_round_rectangle()) Ellipses (cogl_path_ellipse()) One important drawback of using Cogl path primitives is that they will not produce high quality results; more specifically, Cogl does not draw anti-aliased primitives. It is recommended to use the Cairo API to draw during the paint sequence, and the Cogl API to draw during the pick sequence. If you need more flexibility than is available in the Cogl path API, you can make direct use of the CoglVertexBuffer API instead. This is a lower-level API, but could potentially be used to draw more complex shapes.
Using <type>ClutterPath</type> to store the path The disadvantage of the code above is that the paths are stored in two places: once for pick, and once for paint. It would make sense to store the path in one place and reference it from both of these functions to prevent duplication. Clutter provides a ClutterPath API for storing generic path descriptions. It can be used to describe paths which translate to Cogl or Cairo paths, and can also be used to describe animation paths. We can use a ClutterPath instance stored inside the actor to define the path for pick and paint; then, inside those functions, we translate the ClutterPath into Cogl path function calls (NB ClutterPath is effectively a declarative method for defining a path, while the Cogl path API is imperative). First we add a path member to the private struct for the StarActor class (using standard GObject mechanisms). The init implementation for StarActor creates an empty path: static void star_actor_init (StarActor *self) { self->priv = STAR_ACTOR_GET_PRIVATE (self); self->priv->path = clutter_path_new (); clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE); } One consideration is that the path coordinates need to fit inside the actor's bounding box. So as the actor's allocation changes, path also needs to change. We can do this by implementing allocate for the StarActor class: priv->path; gfloat width, height; clutter_actor_box_get_size (box, &width, &height); /* create and store a path describing a star */ clutter_path_clear (path); clutter_path_add_move_to (path, width * 0.5, 0); clutter_path_add_line_to (path, width, height * 0.75); clutter_path_add_line_to (path, 0, height * 0.75); clutter_path_add_move_to (path, width * 0.5, height); clutter_path_add_line_to (path, 0, height * 0.25); clutter_path_add_line_to (path, width, height * 0.25); clutter_path_add_line_to (path, width * 0.5, height); CLUTTER_ACTOR_CLASS (star_actor_parent_class)->allocate (actor, box, flags); } ]]> This clears then adds segments to the ClutterPath stored with the StarActor instance. The positioning and lengths of the segments are relative to the size of the actor when its allocation changes. The pick and paint functions now reference the ClutterPath (only the pick is shown below); and to turn the path into drawing operations, we implement a star_actor_convert_clutter_path_node() function which takes a ClutterPathNode and converts it into its Cogl equivalent: type) { case CLUTTER_PATH_MOVE_TO: knot = node->points[0]; cogl_path_move_to (knot.x, knot.y); break; case CLUTTER_PATH_LINE_TO: knot = node->points[0]; cogl_path_line_to (knot.x, knot.y); break; default: break; } } static void star_actor_pick (ClutterActor *actor, const ClutterColor *pick_color) { if (!clutter_actor_should_pick_paint (actor)) return; ClutterActorBox allocation = { 0, }; gfloat width, height; ClutterPath *path = STAR_ACTOR (actor)->priv->path; clutter_actor_get_allocation_box (actor, &allocation); clutter_actor_box_get_size (&allocation, &width, &height); cogl_path_new (); cogl_set_source_color4ub (pick_color->red, pick_color->green, pick_color->blue, pick_color->alpha); clutter_path_foreach (path, star_actor_convert_clutter_path_node, NULL); cogl_path_fill (); } ]]> The conversion function only covers ClutterPathNode types encountered in this actor. Instead of converting to Cogl path operations, another alternative would be to use the clutter_path_to_cairo_path() function to write directly from the ClutterPath onto a Cairo context.