<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>6. Creating an actor with a non-rectangular shape</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot"><link rel="home" href="index.html" title="The Clutter Cookbook"><link rel="up" href="actors.html" title="Chapter 2. Actors"><link rel="prev" href="actors-opacity.html" title="5. Making an actor transparent by changing its opacity"><link rel="next" href="events.html" title="Chapter 3. Events"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">6. Creating an actor with a non-rectangular shape</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="actors-opacity.html">Prev</a> </td><th width="60%" align="center">Chapter 2. Actors</th><td width="20%" align="right"> <a accesskey="n" href="events.html">Next</a></td></tr></table><hr></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="actors-non-rectangular"></a>6. Creating an actor with a non-rectangular shape</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200513185968"></a>6.1. Problem</h3></div></div></div><p>You want to create a <span class="type">ClutterActor</span> subclass,
but don't want it to be rectangular; for example, you want a
star-shaped actor.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200513184096"></a>6.2. Solution</h3></div></div></div><p>Use Cogl primitives to draw the actor.</p><p>Below is an example of the pick and paint implementations for a
star-shaped <span class="type">StarActor</span> class (an extension of
<span class="type">ClutterActor</span>).</p><p>Like <span class="type">ClutterRectangle</span>, it has a private
struct internally, which contains a <span class="type">ClutterColor</span>
denoting the color it should be painted. This is used to set the Cogl
source color.</p><div class="informalexample"><pre class="programlisting">static void
star_actor_paint (ClutterActor *actor)
{
ClutterActorBox allocation = { 0, };
gfloat width, height;
guint tmp_alpha;
/* priv is a private internal struct */
ClutterColor color = STAR_ACTOR (actor)->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 ();
}</pre></div><p>If you need more information about how to implement your own
<span class="type">ClutterActor</span>, see the Clutter reference
manual.</p><p>Note that the code in these two functions is virtually identical:
the Discussion section suggests how to remove this redundancy.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200513175280"></a>6.3. Discussion</h3></div></div></div><p>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 <span class="type">ClutterRectangle</span>)
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).</p><p>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).</p><p>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.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="idm140200513171936"></a>6.3.1. Cogl path coordinates</h4></div></div></div><p>In the example shown, <code class="function">cogl_path_move_to()</code>
and <code class="function">cogl_path_line_to()</code> are used. These
take absolute <code class="code">x</code> and <code class="code">y</code> coordinates as
arguments, relative to the GL 'modelview' transform matrix; in
the case of an actor's <code class="function">paint</code> implementation,
relative to the bounding box for the actor. So if an actor has
width and height of 50 pixels, and you used
<code class="function">cogl_move_to (25, 25)</code> in its
<code class="function">paint</code> implementation, the "pen"
moves to the centre of the actor, regardless of where the actor
is positioned on the stage. Similarly, using
<code class="function">cogl_path_line_to()</code> creates a line segment
from the current pen position to the absolute coordinates
(<code class="code">x</code>, <code class="code">y</code>) specified.</p><p>The Cogl API also provides various "rel" variants of the path
functions (e.g. <code class="function">cogl_path_rel_line_to()</code>), which
create path segments relative to the current pen position (i.e.
<code class="code">pen_x + x</code>, <code class="code">pen_y + y</code>).</p><p>It's important to note that the path isn't drawn until you
call <code class="function">cogl_path_stroke()</code> (to draw the path segments)
or <code class="function">cogl_path_fill()</code> (to fill the area enclosed by
the path). The path is cleared once it's been drawn.
Using the <code class="function">*_preserve</code> variants of these functions draws
the path and retains it (so it could be drawn again).</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="idm140200513158992"></a>6.3.2. Other Cogl primitives</h4></div></div></div><p>Note that the Cogl primitives API provides other types of path
segment beyond straight lines that we didn't use here, including:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>Bezier curves (<code class="function">cogl_path_curve_to()</code>)</p></li><li class="listitem"><p>Arcs (<code class="function">cogl_path_arc()</code>)</p></li><li class="listitem"><p>Polygons (<code class="function">cogl_path_polygon()</code>)</p></li><li class="listitem"><p>Rectangles (<code class="function">cogl_path_rectangle()</code>)</p></li><li class="listitem"><p>Rectangles with rounded corners
(<code class="function">cogl_path_round_rectangle()</code>)</p></li><li class="listitem"><p>Ellipses (<code class="function">cogl_path_ellipse()</code>)</p></li></ul></div><p>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.</p><p>If you need more flexibility than is available in the Cogl path
API, you can make direct use of the <span class="type">CoglVertexBuffer</span>
API instead. This is a lower-level API, but could potentially
be used to draw more complex shapes.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="idm140200513145552"></a>6.3.3. Using <span class="type">ClutterPath</span> to store the path</h4></div></div></div><p>The disadvantage of the code above is that the paths are stored in two
places: once for <code class="function">pick</code>, and once for
<code class="function">paint</code>. It would make sense to store the
path in one place and reference it from both of these functions to
prevent duplication.</p><p>Clutter provides a <span class="type">ClutterPath</span> 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.</p><p>We can use a <span class="type">ClutterPath</span> instance stored
inside the actor to define the path for <code class="function">pick</code> and
<code class="function">paint</code>; then, inside those functions, we
translate the <span class="type">ClutterPath</span> into Cogl path function calls
(NB <span class="type">ClutterPath</span> is effectively a declarative method
for defining a path, while the Cogl path API is imperative).</p><p>First we add a <code class="varname">path</code> member to the private
struct for the <span class="type">StarActor</span> class (using standard
GObject mechanisms). The <code class="function">init</code> implementation for
<span class="type">StarActor</span> creates an empty path:</p><div class="informalexample"><pre class="programlisting">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);
}</pre></div><p>One consideration is that the path coordinates need to
fit inside the actor's bounding box. So as the actor's allocation
changes, <code class="varname">path</code> also needs to change. We can do this
by implementing <code class="function">allocate</code> for the
<span class="type">StarActor</span> class:</p><div class="informalexample"><pre class="programlisting">static void
star_actor_allocate (ClutterActor *actor,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
ClutterPath *path = STAR_ACTOR (actor)->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);
}</pre></div><p>This clears then adds segments to the
<span class="type">ClutterPath</span> stored with the
<span class="type">StarActor</span> instance. The positioning and
lengths of the segments are relative to the size of the actor when
its allocation changes.</p><p>The <code class="function">pick</code> and <code class="function">paint</code>
functions now reference the <span class="type">ClutterPath</span> (only the
<code class="function">pick</code> is shown below); and
to turn the path into drawing operations, we implement a
<code class="function">star_actor_convert_clutter_path_node()</code> function
which takes a <span class="type">ClutterPathNode</span> and converts it
into its Cogl equivalent:</p><div class="informalexample"><pre class="programlisting">static void
star_actor_convert_clutter_path_node (const ClutterPathNode *node,
gpointer data)
{
g_return_if_fail (node != NULL);
ClutterKnot knot;
switch (node->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 ();
}</pre></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The conversion function only covers
<span class="type">ClutterPathNode</span> types encountered in this
actor.</p></div><p>Instead of converting to Cogl path operations, another alternative
would be to use the <code class="function">clutter_path_to_cairo_path()</code>
function to write directly from the <span class="type">ClutterPath</span>
onto a Cairo context.</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="actors-opacity.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="actors.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="events.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">5. Making an actor transparent by changing its opacity </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 3. Events</td></tr></table></div></body></html>