Emmanuele Bassi
ebassi@openedhand.com
Implementing a new actor A few FIXMES: - more on composite/container actors, when/why to use... + implementaing a composite actor - set_parent() etc + implementing a container - interface etc - Painting + note on cogl_enable if painting texture or blended item (should at least call cogl_enable(0) - to reset state cache) + fine to use regular GL but then wont be portable + avoid further transforms ? In order to implement a new #ClutterActor subclass the usual machinery for subclassing a GObject should be used. After that, the ClutterActor::query_coords() and ClutterActor::request_coords() virtual functions should be overidden. Optionally, also the ClutterActor::paint() virtual functions should be overridden. The ClutterActor::query_coords() method of a #ClutterActor is invoked when clutter_actor_query_coords() is called on an instance of that actor class. It is used to return a #ClutterActorBox containing the coordinates of the bounding box for the actor (the coordinates of the top left corner and of the bottom right corner of the rectangle that fully contains the actor). Container actors, or composite actors with internal children, should call clutter_actor_query_coords() on each visible child. Remember: the returned coordinates must be relative to the parent actor. All the coordinates are expressed using #ClutterUnits, the internal high-precision unit type, which guarantee sub-pixel precision. #ClutterUnit has the same limitation that #ClutterFixed has, see the fixed point page. This example shows how an actor class should override the query_coords() virtual function of #ClutterActor. In this case, the returned bounding box is the sum of the bounding boxes of all the FooActor children. static void foo_actor_query_coords (ClutterActor *actor, ClutterActorBox *box) { FooActor *foo_actor = FOO_ACTOR (actor); GList *child; /* Clutter uses high-precision units which can be converted from * and into pixels, typographic points, percentages, etc. */ ClutterUnit width, height; /* initialize our size */ width = height = 0; for (l = foo_actor->children; l != NULL; l = l->next) { ClutterActor *child_actor = child->data; /* we consider only visible actors */ if (CLUTTER_ACTOR_IS_VISIBLE (child_actor)) { ClutterActorBox child_box = { 0, }; clutter_actor_query_coords (child_actor, &child_box); width += child_box.x2 - child_box.x2; height += child_box.y2 - child_box.y1; } } box->x2 = box->x1 + width box->y2 = box->y1 + height; } The ClutterActor::request_coords() method of a #ClutterActor is invoked when clutter_actor_request_coords() is called on an instance of that actor class. It is used to set the coordinates of the bounding box for the actor. Container actors, or composite actors with internal children, should override the request_coords() virtual function and call clutter_actor_request_coords() on each visible child. Every actor class overriding the request_coords() virtual function must chain up to the parent class request_coords() method. The ClutterActor::paint() method should be overridden if the actor needs to control its drawing process, by using the GL API directly. Actors performing transformations should push the GL matrix first and then pop the GL matrix before returning. Container actors or composite actors with internal children should do the same, and call clutter_actor_paint() on every visible child: When inside the ClutterActor::paint() method the actor is already positioned at the coordinates specified by its bounding box; all the paint operations should then take place from the (0, 0) coordinates. static void foo_actor_paint (ClutterActor *actor) { FooActor *foo_actor = FOO_ACTOR (actor); GList *child; /* by including <clutter/cogl.h> it's possible to use the internal * COGL abstraction API, which is also used by Clutter itself and avoids * changing the GL calls depending on the target platform (GL or GL/ES). */ cogl_push_matrix (); for (child = foo_actor->children; child != NULL; child = child->next) { ClutterActor *child_actor = child->data; if (CLUTTER_ACTOR_IS_MAPPED (child_actor)) clutter_actor_paint (child_actor); } cogl_pop_matrix (); } If the actor has a non-rectangular shape, or it has internal childrens that needs 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: static void foo_actor_pick (ClutterActor *actor, const ClutterColor *pick_color) { FooActor *foo_actor = FOO_ACTOR (actor); guint width, height; /* 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; /* by including <clutter/cogl.h> it's possible to use the internal * COGL abstraction API, which is also used by Clutter itself and avoids * changing the GL calls depending on the target platform (GL or GL/ES). */ cogl_color (pick_color); clutter_actor_get_size (actor, &width, &height); /* it is also possible to use raw GL calls, at the cost of losing * portability */ glEnable (GL_BLEND); /* draw a triangular shape */ glBegin (GL_POLYGON); glVertex2i (width / 2, 0 ); glVertex2i (width , height); glVertex2i (0 , height); glEnd (); cogl_pop_matrix (); }