Emmanuele Bassi
ebassi@openedhand.com
Implementing a new actor In order to implement a new #ClutterActor subclass the usual machinery for subclassing a #GObject should be used: #define FOO_TYPE_ACTOR (foo_actor_get_type ()) #define FOO_ACTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOO_TYPE_ACTOR, FooActor)) #define FOO_IS_ACTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOO_TYPE_ACTOR)) #define FOO_ACTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FOO_TYPE_ACTOR, FooActorClass)) #define FOO_IS_ACTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FOO_TYPE_ACTOR)) #define FOO_ACTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FOO_TYPE_ACTOR, FooActorClass)) typedef struct _FooActor { ClutterActor parent_instance; } FooActor; typedef struct _FooActorClass { ClutterActorClass parent_class; } FooActorClass; G_DEFINE_TYPE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR); static void foo_actor_class_init (FooActorClass *klass) { } static void foo_actor_init (FooActor *actor) { } The implementation of an actor roughly depends on what kind of actor the class should display. The implementation process can be broken down into sections: size requisition used by containers to know how much space an actor requires for itself and its eventual children. size allocation used by containers to define how much space an actor should have for itself and its eventual children. painting and picking the actual actor painting and the "picking" done to determine the actors that received events Container actors should also implement the #ClutterContainer interface to provide a consistent API for adding, removing and iterating over their children. Size requisition Actors should usually implement the size requisition virtual functions unless they depend on explicit sizing by the developer, using the clutter_actor_set_width() and clutter_actor_set_height() functions and their wrappers. For instance, an actor that depends on the explicit sizing is the #ClutterRectangle actor. The size requisition is split into two different phases: width requisition and height requisition. The ClutterActor::get_preferred_width() and ClutterActor::get_preferred_height() methods of a #ClutterActor are invoked when clutter_actor_get_preferred_width() and clutter_actor_get_preferred_height() are respectively called on an instance of that actor class. They are used to return the preferred size of the actor. Container actors, or composite actors with internal children, should call clutter_actor_get_preferred_width() and clutter_actor_get_preferred_height() on each visible child inside their implementation of the get_preferred_width() and get_preferred_height() virtual functions. The get_preferred_width() and get_preferred_height() virtual functions return both the minimum size of the actor and its natural size. The minimum size is defined as the amount of space an actor must occupy to be useful; the natural size is defined as the amount of space an actor would occupy if nothing would constrain it. The natural size must always be greater than, or equal to the minimum size. #ClutterActor will warn in case this assumption is not respected by an implementation. The height request may be computed for a specific width, which is passed to the implementation, thus allowing height-for-width geometry management. Similarly, the width request may be computed for a specific height, allowing width-for-height geometry management. By default, every #ClutterActor uses the height-for-width geometry management, but the setting can be changed by using the #ClutterActor:request-mode property. The clutter_actor_get_preferred_size() function will automatically check the geometry management preferred by the actor and return its preferred size depending on the value of the request-mode property and on the natural size of the actor. The size requisition starts from the #ClutterStage and it is performed on every child of the stage following down the hierarchy of the scene graph. The size requisition should not take into account the actor's scale, rotation or anchor point unless an actor is performing layout management depending on those properties. All the sizes are expressed using #ClutterUnits, the internal high-precision unit type, which guarantees sub-pixel precision. #ClutterUnit currently has the same limitations that #ClutterFixed has, see the fixed point page. Width requisition implementation of a container This example shows how an actor class should override the get_preferred_width() virtual function of #ClutterActor. In this case, the returned widths are the union of the extents of all the FooActor children. The get_preferred_height() implementation would be similar to the get_preferred_width() implementation, so it is omitted. static void foo_actor_get_preferred_width (ClutterActor *actor, ClutterUnit for_height, ClutterUnit *min_width_p, ClutterUnit *natural_width_p) { GList *l; ClutterUnit min_left, min_right; ClutterUnit natural_left, natural_right; min_left = 0; min_right = 0; natural_left = 0; natural_right = 0; for (l = children; l != NULL; l = l->next) { ClutterActor *child = l->data; ClutterUnit child_x, child_min, child_natural; child_x = clutter_actor_get_xu (child); clutter_actor_get_preferred_width (child, for_height, &child_min, &child_natural); if (l == children) { /* First child */ min_left = child_x; natural_left = child_x; min_right = min_left + child_min; natural_right = natural_left + child_natural; } else { if (child_x < min_left) min_left = child_x; if (child_x < natural_left) natural_left = child_x; if (child_x + child_min > min_right) min_right = child_x + child_min; if (child_x + child_natural > natural_right) natural_right = child_x + child_natural; } } /* The request is defined as the width and height we want starting from * our origin, since our allocation will set the origin; so we now need * to remove any part of the request that is to the left of the origin. */ if (min_left < 0) min_left = 0; if (natural_left < 0) natural_left = 0; if (min_right < 0) min_right = 0; if (natural_right < 0) natural_right = 0; g_assert (min_right >= min_left); g_assert (natural_right >= natural_left); if (min_width_p) *min_width_p = min_right - min_left; if (natural_width_p) *natural_width_p = natural_right - min_left; } Size allocation The ClutterActor::allocate() method of a #ClutterActor is invoked when clutter_actor_allocate() is called on an instance of that actor class. It is used by a parent actor to set the coordinates of the bounding box for its children actors. Hence, container actors, or composite actors with internal children, should override the allocate() virtual function and call clutter_actor_allocate() on each visible child. Each actor can know from their allocation box whether they have been moved with respect to their parent actor. Each child will also be able to tell whether their parent has been moved with respect to the stage. The allocate() virtual function implementation will be notified whether the actor has been moved, while clutter_actor_allocate() will usually be invoked with a boolean flag meaning that the parent has been moved. Allocation of a container In this example, FooActor acts like a horizontal box with overflowing, like a toolbar which will display more children as it expands. The children falling outside of the allocated area will fade out; the children falling inside the same area will fade in. static void foo_actor_allocate (ClutterActor *actor, const ClutterActorBox *box, gboolean absolute_origin_changed) { FooActor *foo_actor = FOO_ACTOR (actor); ClutterUnit current_width; GList *l; /* chain up to set the allocation of the actor */ CLUTTER_ACTOR_CLASS (foo_actor_parent_class)->allocate (actor, box, absolute_origin_changed); current_width = foo_actor->padding; for (l = foo_actor->children; l != NULL; l = l->next) { FooActorChild *child = l->data; ClutterUnit natural_width, natural_height; ClutterActorBox child_box = { 0, }; /* each child will get as much space as they require */ clutter_actor_get_preferred_size (CLUTTER_ACTOR (child), NULL, NULL, &natural_width, &natural_height); /* if the child is overflowing, we just fade it out */ if (current_width + natual_width > box->x2 - box->x1) foo_actor_fade_child (foo_actor, child, 0); else { current_width += natural_width + priv->padding; child_box.x1 = current_width; child_box.y1 = 0; child_box.x2 = child_box.x1 + natural_width; child_box.y2 = child_box.y1 + natural_height; /* update the allocation */ clutter_actor_allocate (CLUTTER_ACTOR (child), &child_box, absolute_origin_changed); /* fade the child in if it wasn't visible */ foo_actor_fade_child (foo_actor, child, 255); } } } An actor should always paint inside its allocation. It is, however, possible to paint outside the negotiated size by overriding the ClutterActor::get_paint_area() virtual function and setting the passed #ClutterActorBox with their untransformed paint area. This allows writing actors that can effectively paint on an area different in size and position from the allocation area. The ClutterActor::get_paint_area() method of a #ClutterActor is internally invoked when clutter_actor_get_paint_area() is called on an instance of that actor class. The get_paint_area() virtual function must return the untransformed area used by the actor to paint itself; clutter_actor_get_paint_area() will then proceed to transform the area coordinates into the frame of reference of the actor's parent (taking into account anchor point, scaling and rotation). The default paint area is the allocation area of the actor. Implementation of get_paint_area() In this example, FooActor implements the get_paint_area() virtual function to return an area equivalent to those of its children plus a border which is not taken into account by the size negotiation process. static void foo_actor_get_paint_area (ClutterActor *actor, ClutterActorBox *box) { FooActor *foo_actor = FOO_ACTOR (actor); if (!foo_actor->children) { /* if we don't have any children we return the * allocation given to us */ clutter_actor_get_allocation_box (actor, box); } else { ClutterActorBox all_box = { 0, }; GList *l; /* our paint area is the union of the children * paint areas, plus a border */ for (l = foo_actor->children; l != NULL; l = l>next) { ClutterActor *child = l->data; ClutterActorBox child_box = { 0, }; clutter_actor_get_paint_area (child, &child_box); if (l == foo_actor->children) all_box = child_box; else { if (child_box.x1 < all_box.x1) all_box.x1 = child_box.x1; if (child_box.y1 < all_box.y1) all_box.y1 = child_box.y1; if (child_box.x2 < all_box.x2) all_box.x2 = child_box.x2; if (child_box.y2 < all_box.y2) all_box.y2 = child_box.y2; } } /* apply the border width around the box */ all_box.x1 -= (foo_actor->border_width / 2); all_box.y1 -= (foo_actor->border_width / 2); all_box.x2 += (foo_actor->border_width / 2); all_box.y2 += (foo_actor->border_width / 2); *box = all_box; } } Painting and picking The ClutterActor::paint() method should be overridden if the actor needs to control its drawing process, either by using the Clutter GL and GLES abstraction library (COGL) or by directly using the GL or GLES API. Actors performing transformations should push the GL matrix first and then pop the GL matrix before returning. Paint implementation of a simple actor In this example, the FooActor implementation of the paint() virtual function is drawing a rectangle with rounded corners with a custom color. The COGL API is used, to allow portability between GL and GLES platforms. static void foo_actor_paint (ClutterActor *actor) { FooActor *foo_actor = FOO_ACTOR (actor); ClutterColor color = { 0, }; ClutterUnit w, h, r; cogl_push_matrix (); /* FooActor has a specific background color */ color.red = foo_actor->bg_color.red; color.green = foo_actor->bg_color.green; color.blue = foo_actor->bg_color.blue; /* the alpha component must take into account the absolute * opacity of the actor on the screen at this point in the * scenegraph; this value is obtained by calling * clutter_actor_get_paint_opacity(). */ color.alpha = clutter_actor_get_paint_opacity (actor); /* set the color of the pen */ cogl_color (&color); /* get the size of the actor with sub-pixel precision */ w = CLUTTER_UNITS_TO_FIXED (clutter_actor_get_widthu (actor)); h = CLUTTER_UNITS_TO_FIXED (clutter_actor_get_heightu (actor)); /* this is the arc radius for the rounded rectangle corners */ r = CLUTTER_UNITS_TO_FIXED (foo_actor->radius); /* paint a rounded rectangle using GL primitives; the area of * paint is (0, 0) - (width, height), which means the whole * allocation or, if the actor has a fixed size, the size that * has been set. */ cogl_round_rectangle (0, 0, w, h, r, 5); /* and fill it with the current color */ cogl_fill (); cogl_pop_matrix (); } When inside the ClutterActor::paint() method the actor is already positioned at the coordinates specified by its parent; all the paint operations should take place from the (0, 0) coordinates. Container actors or composite actors with internal children should also override the paint method, and call clutter_actor_paint() on every visible child: Paint implementation of a container In this example, FooActor is a simple container invoking clutter_actor_paint() on every visible child. To allow transformations on itself to affect the children, the GL modelview matrix is pushed at the beginning of the paint sequence, and the popped at the end. static void foo_actor_paint (ClutterActor *actor) { FooActor *foo_actor = FOO_ACTOR (actor); GList *child; cogl_push_matrix (); for (child = foo_actor->children; child != NULL; child = child->next) { ClutterActor *child_actor = child->data; /* paint only visible children */ if (CLUTTER_ACTOR_IS_VISIBLE (child_actor)) clutter_actor_paint (child_actor); } cogl_pop_matrix (); } A container imposing a layout on its children may opt to use the paint area as returned by clutter_actor_get_paint_area() instead of the allocation. If the actor has a non-rectangular shape, or it has internal children that need to be distinguished by the events delivery mechanism, the ClutterActor::pick() method should also be overridden. The pick() method works exactly like the paint() method, but the actor should paint just its shape with the passed colour: Pick implementation of a simple actor In this example, FooActor overrides the pick() virtual function default implementation to paint itself with a shaped silhouette, to allow events only on the actual shape of the actor instead of the paint area. static void foo_actor_pick (ClutterActor *actor, const ClutterColor *pick_color) { FooActor *foo_actor = FOO_ACTOR (actor); ClutterUnit w, h, r; /* it is possible to avoid a costly paint by checking whether the * actor should really be painted in pick mode */ if (!clutter_actor_should_pick_paint (actor)) return; w = CLUTTER_UNITS_TO_FIXED (clutter_actor_get_widthu (actor)); h = CLUTTER_UNITS_TO_FIXED (clutter_actor_get_heightu (actor)); /* this is the arc radius for the rounded rectangle corners */ r = CLUTTER_UNITS_TO_FIXED (foo_actor->radius); /* use the passed color to paint ourselves */ cogl_color (pick_color); /* paint a round rectangle */ cogl_round_rectangle (0, 0, w, h, r, 5); /* and fill it with the current color */ cogl_fill (); cogl_pop_matrix (); } Containers should simply chain up to the parent class' pick() implementation to get their silhouette painted and then paint their children: Pick implementation of a container In this example, FooActor allows the picking of each child it contains, as well as itself. static void foo_actor_pick (ClutterActor *actor, const ClutterColor *pick_color) { FooActor *foo_actor = FOO_ACTOR (actor); /* this will paint a silhouette corresponding to the paint box */ CLUTTER_ACTOR_CLASS (foo_actor_parent_class)->pick (actor, pick_color); /* clutter_actor_paint() is context-sensitive, and will perform * a pick paint if the scene graph is in pick mode */ if (CLUTTER_ACTOR_IS_VISIBLE (foo_actor->child)) clutter_actor_paint (foo_actor->child); } Implementing Containers The #ClutterContainer interface should be implemented by subclasses of #ClutterActor who wants to provide a general API for adding child actors. If the #ClutterActor subclass only handles internal children, or it's not suitable for having generic actors added to it, it should not implement the #ClutterContainer interface, but simply use clutter_actor_set_parent(): Parenting an actor In this example, FooActor has an internal child of type BazActor which is assigned using a specific function called foo_actor_add_baz(). The FooActor instance takes ownership of the BazActor instance and sets the parent-child relationship using clutter_actor_set_parent(). void foo_actor_add_baz (FooActor *foo_actor, BazActor *baz_actor) { g_return_if_fail (FOO_IS_ACTOR (foo_actor)); g_return_if_fail (BAZ_IS_ACTOR (baz_actor)); /* unparent the previous BazActor; this will automatically call * g_object_unref() on the actor */ if (foo_actor->baz) clutter_actor_unparent (foo_actor->baz); foo_actor->baz = baz_actor; /* this will cause the initial floating reference of ClutterActor to * disappear, and add a new reference on baz_actor. foo_actor has now * taken ownership of baz_actor, so that: * * foo_actor_add_baz (foo_actor, baz_actor_new ()); * * is a safe statement (no reference is leaked). */ clutter_actor_set_parent (CLUTTER_ACTOR (baz_actor), CLUTTER_ACTOR (foo_actor)); /* a container should queue a change in the layout */ clutter_actor_queue_relayout (CLUTTER_ACTOR (foo_actor)); /* emit a signal and notification */ g_signal_emit (foo_actor, foo_actor_signals[BAZ_CHANGED], 0, baz_actor); g_object_notify (G_OBJECT (foo_actor), "baz"); } In order to implement the #ClutterContainer interface, these virtual functions must be defined: ClutterContainer::add The container actor should hold a pointer to the passed #ClutterActor, call clutter_actor_set_parent() on it and then emit the #ClutterContainer::actor-added signal to notify handlers of the newly added actor. ClutterContainer::remove The container actor should increase the reference count of the passed #ClutterActor, remove the pointer held on the child and call clutter_actor_unparent() on it; then, emit the #ClutterContainer::actor-removed signal and decrease the reference count. ClutterContainer::foreach The container should invoke the callback on every child it is holding. ClutterContainer::raise The container should move the passed child on top of the given sibling, or on top of the paint stack in case the sibling is NULL. ClutterContainer::lower The container should move the passed child below the given sibling, or on the bottom of the paint stack in case the sibling is NULL. ClutterContainer::sort_depth_order The container should sort the paint stack depending on the relative depths of each child.