mirror of
https://github.com/brl/mutter.git
synced 2024-12-29 06:12:14 +00:00
6680cebfe1
Add a reference to a GObject tutorial which is a good introduction to the various macros etc. and what they're for.
1145 lines
42 KiB
XML
1145 lines
42 KiB
XML
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
|
|
<chapter id="actors"
|
|
xmlns:xi="http://www.w3.org/2003/XInclude">
|
|
<title>Actors</title>
|
|
|
|
<epigraph>
|
|
<attribution>Edmon Gween, actor, on his deathbed</attribution>
|
|
<para>An actor's a guy who if you ain't talkin' about him, ain't
|
|
listening.</para>
|
|
</epigraph>
|
|
|
|
<section id="actors-introduction">
|
|
<title>Introduction</title>
|
|
|
|
<para>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.</para>
|
|
|
|
<para>Every node on the Clutter scene graph is an
|
|
<emphasis>actor</emphasis>. Every actor has a single relationship
|
|
with the others: it can be the parent of another actor, or a child of
|
|
another actor.</para>
|
|
|
|
<note><para>The stage is an actor that can have children but cannot have
|
|
any parent.</para></note>
|
|
|
|
<para>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.</para>
|
|
|
|
<para>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.</para>
|
|
|
|
</section>
|
|
|
|
<section id="actors-composite">
|
|
<title>Implementing a simple custom actor</title>
|
|
|
|
<section id="actors-composite-problem">
|
|
<title>Problem</title>
|
|
|
|
<para>You want to implement your own <type>ClutterActor</type>;
|
|
for example, a very simple button widget. But you want to base it
|
|
on existing Clutter primitives (rectangles, text) to minimise
|
|
the work required.</para>
|
|
</section>
|
|
|
|
<section id="actors-composite-solution">
|
|
<title>Solution</title>
|
|
|
|
<para>Implement a custom actor composed from a <type>ClutterBox</type>
|
|
packed with other <type>ClutterActors</type>. The custom actor
|
|
provides a facade over these internal actors, simplifying
|
|
access to their properties and behavior.</para>
|
|
|
|
<para>In this recipe, we subclass <type>ClutterActor</type> using this
|
|
approach to create a very simple button widget, <type>CbButton</type>.
|
|
It is not a complete button implementation: see
|
|
<ulink url="http://git.clutter-project.org/mx/tree/mx/mx-button.c">
|
|
<type>MxButton</type></ulink> for a more comprehensive example
|
|
(and the basis for this recipe). But this recipe does cover the most
|
|
important parts of a <type>ClutterActor</type> implementation,
|
|
as well some useful <type>GObject</type>-related code.</para>
|
|
|
|
<tip>
|
|
<para>As Clutter is a GObject-based library, it relies
|
|
heavily on GObject concepts and idioms. If you are unfamiliar with
|
|
GObject, please read
|
|
<ulink url="http://library.gnome.org/devel/gobject/unstable/">the GObject
|
|
Reference Manual</ulink> before proceeding. You might also find
|
|
<ulink url="http://diuf.unifr.ch/pai/wiki/doku.php/research:projects:gobject_tutorial">this
|
|
tutorial</ulink> a useful introduction.</para>
|
|
</tip>
|
|
|
|
<para>The code for this solution is structured like standard GObject
|
|
C library code:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>The header file <filename>cb-button.h</filename>
|
|
declares the class' public API (function prototypes, macros,
|
|
structs).</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The code file <filename>cb-button.c</filename>
|
|
contains the class implementation.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>One more example file, <filename>actors-composite-main.c</filename>,
|
|
shows how to use <type>CbButton</type> in an application.</para>
|
|
|
|
<para>Each of these files is described in more detail below.</para>
|
|
|
|
<note>
|
|
<para>In a more realistic context, <type>CbButton</type> 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.</para>
|
|
|
|
<para>If you <emphasis>are</emphasis> planning on building
|
|
your own widgets using Clutter as part of an application, or
|
|
to create your own library, the
|
|
<ulink url="http://git.clutter-project.org/mx/">Mx toolkit</ulink>
|
|
provides an excellent example of how to autotool your project.</para>
|
|
</note>
|
|
|
|
<example id="actors-composite-cb-button-h">
|
|
<title><filename>cb-button.h</filename>: header file</title>
|
|
|
|
<para>This defines the public API for the class, including
|
|
GObject type macros, class and object structures, and
|
|
function prototypes.</para>
|
|
|
|
<programlisting>
|
|
<xi:include href="examples/cb-button.h" parse="text">
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
</xi:include>
|
|
</programlisting>
|
|
</example>
|
|
|
|
<example id="actors-composite-cb-button-c">
|
|
<title><filename>cb-button.c</filename>: <type>ClutterActor</type>
|
|
and GObject implementation</title>
|
|
|
|
<para>This is the main C code file which implements both
|
|
the GObject and Clutter elements of <type>CbButton</type>.
|
|
The example below is liberally commented, and also gives some samples
|
|
of annotations to generate
|
|
<ulink url="http://www.gtk.org/gtk-doc/">gtk-docs</ulink> for the
|
|
widget. The
|
|
<link linkend="actors-composite-discussion-clutter-virtual-functions">discussion
|
|
section</link> comments more specifically about the Clutter-specific
|
|
parts of it.</para>
|
|
|
|
<programlisting>
|
|
<xi:include href="examples/cb-button.c" parse="text">
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
</xi:include>
|
|
</programlisting>
|
|
</example>
|
|
|
|
<example id="actors-composite-actors-composite-main-c">
|
|
<title><filename>actors-composite-main.c</filename>: trivial
|
|
application demonstrating usage of <type>CbButton</type></title>
|
|
|
|
<para>Note how any of the <type>ClutterActor</type>
|
|
functions (like <function>clutter_actor_set_size()</function>
|
|
and <function>clutter_actor_add_constraint()</function>) can
|
|
be applied to instances of our <type>ClutterActor</type>
|
|
implementation.</para>
|
|
|
|
<programlisting>
|
|
<xi:include href="examples/actors-composite-main.c" parse="text">
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
</xi:include>
|
|
</programlisting>
|
|
</example>
|
|
|
|
</section>
|
|
|
|
<section id="actors-composite-discussion">
|
|
<title>Discussion</title>
|
|
|
|
<para>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
|
|
<type>ClutterLayoutManager</type> to handle positioning of
|
|
the <type>ClutterText</type>; we change the background color of
|
|
the button by changing the color of the
|
|
<type>ClutterBox</type>; and we use a <type>ClutterClickAction</type>
|
|
to simplify implementation of a click signal.</para>
|
|
|
|
<para>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 <function>pick()</function> and <function>paint()</function>
|
|
implementations using Cogl (or similar): see
|
|
<link linkend="actors-non-rectangular">this recipe</link>
|
|
for more details.</para>
|
|
|
|
<para>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.</para>
|
|
|
|
<section id="actors-composite-discussion-clutter-virtual-functions">
|
|
<title>Implementing <type>ClutterActor</type> virtual functions</title>
|
|
|
|
<para>While most of the <type>CbButton</type> implementation
|
|
revolves around GObject, there are some elements of it
|
|
specific to Clutter. Due to the simplicity of
|
|
the <type>CbButton</type> actor, the implementation of
|
|
these functions is fairly trivial, as explained below:</para>
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<formalpara>
|
|
<title>Object destruction:
|
|
<function>cb_button_destroy()</function></title>
|
|
|
|
<para><type>ClutterActor</type> subclasses based
|
|
on composition should implement the <function>destroy()</function>
|
|
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
|
|
<emphasis>destroy</emphasis> signal which other code can
|
|
hook onto.</para>
|
|
</formalpara>
|
|
|
|
<para>In the case of <type>CbButton</type>, the
|
|
<function>destroy()</function> implementation calls
|
|
<function>clutter_actor_destroy()</function> on the child
|
|
<type>ClutterBox</type>, then sets that child to
|
|
<constant>NULL</constant>. Finally, it checks for a
|
|
<function>destroy()</function> implementation on the parent
|
|
class, then calls it if one exists.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<formalpara>
|
|
<title>Size requisition:
|
|
<function>cb_button_get_preferred_height()</function>
|
|
and <function>cb_button_get_preferred_width()</function></title>
|
|
|
|
<para>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 <function>get_preferred_height()</function>
|
|
and <function>get_preferred_width()</function> functions
|
|
on each actor in turn.</para>
|
|
</formalpara>
|
|
|
|
<para>If an actor will only ever be explictly sized
|
|
(via <function>clutter_actor_set_size()</function>,
|
|
<function>clutter_actor_set_height()</function> and/or
|
|
<function>clutter_actor_set_width()</function>),
|
|
there is no need to implement the <function>get_preferred_*()</function>
|
|
functions. (Some actors like <type>ClutterRectangle</type>
|
|
work this way and require explicit sizing.)</para>
|
|
|
|
<para>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
|
|
<type>CbButton</type>, a preferred height and width can be
|
|
computed; these are based on the height and width of
|
|
the child <type>ClutterBox</type>, plus 20 pixels on each
|
|
axis. Because the size of the box is itself dependent on
|
|
the size of the <type>ClutterText</type> inside it, the net
|
|
result is that the <type>CbButton</type> preferred size
|
|
is the size of the text actor inside it, plus 20 pixels on each
|
|
axis.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<formalpara>
|
|
<title>Allocation:
|
|
<function>cb_button_allocate()</function></title>
|
|
|
|
<para>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
|
|
<emphasis>box</emphasis>, representing the space available
|
|
to it on the stage.</para>
|
|
</formalpara>
|
|
|
|
<para>An actor implementation is responsible for distributing
|
|
space from its allocation box to its children as it sees
|
|
fit. In the case of <type>CbButton</type>, there is only a single
|
|
<type>ClutterBox</type> actor which needs allocation;
|
|
<function>cb_button_allocate()</function> therefore
|
|
allocates all of the button's space to its child
|
|
<type>ClutterBox</type>.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<formalpara>
|
|
<title>Painting and picking:
|
|
<function>cb_button_paint()</function></title>
|
|
|
|
<para>Clutter works its way through the actors on the
|
|
stage, following the actor hierarchy (top level
|
|
actors directly inside the stage first);
|
|
<function>clutter_actor_paint()</function>
|
|
is called on each actor. This, in turn, calls the actor's
|
|
<function>paint()</function> implementation. If the actor
|
|
is a container, it may iterate over its children,
|
|
calling <function>paint()</function> on each; the children
|
|
may call <function>paint()</function> on their children...;
|
|
and so on, until the leaves of the actor hierarchy are
|
|
reached.</para>
|
|
</formalpara>
|
|
|
|
<para>As our actor consists of a single <type>ClutterBox</type>
|
|
child, its <function>paint()</function> implementation simply
|
|
has to retrieve the reference to that <type>ClutterBox</type>
|
|
(from its private structure) and call
|
|
<function>clutter_actor_paint()</function>
|
|
on it. Painting of the <type>ClutterBox's</type> child
|
|
(the <type>ClutterText</type>) is handled by the
|
|
<type>ClutterBox</type>.</para>
|
|
|
|
<para>In cases where an actor is non-rectangular, you also
|
|
need to implement a <function>pick()</function> 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 <function>pick()</function>.</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="actors-allocation-notify">
|
|
<title>Knowing when an actor's position or size changes</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>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.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>You can use the <emphasis>notify</emphasis> signal,
|
|
detailed with the coordinate or the dimension you want
|
|
to know has changed:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
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);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>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
|
|
<emphasis>allocation-changed</emphasis> signal:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
g_signal_connect (actor, "allocation-changed",
|
|
G_CALLBACK (on_allocation_changed),
|
|
NULL);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>The signature for the handler of the "notify" signal is:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
void
|
|
on_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>While the signature for the handler of the "allocation-changed"
|
|
signal is:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
void
|
|
on_allocation_changed (ClutterActor *actor,
|
|
const ClutterActorBox *allocation,
|
|
ClutterAllocationFlags flags,
|
|
gpointer user_data);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>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 <property>x</property>, <property>y</property>,
|
|
<property>width</property> and <property>height</property>
|
|
properties as well.</para>
|
|
|
|
<para>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:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
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);
|
|
}
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>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:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
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));
|
|
}
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>All actors will update these properties when their size
|
|
or position change.</para>
|
|
|
|
<para>Note that the stage, on the other hand, will not notify on
|
|
position changes, so it is not possible to use the
|
|
<property>x</property> and <property>y</property>
|
|
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.</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="actors-paint-wrappers">
|
|
<title>Overriding the paint sequence</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want to override the way an actor paints itself
|
|
without creating a subclass.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>You can use the <emphasis>paint</emphasis> signal to
|
|
invoke a callback that will be executed before the actor's
|
|
paint implementation:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
g_signal_connect (actor, "paint", G_CALLBACK (on_paint), NULL);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>You can paint something after the actor's paint implementation
|
|
by using the <function>g_signal_connect_after()</function> function
|
|
instead of <function>g_signal_connect()</function>:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
g_signal_connect_after (actor, "paint", G_CALLBACK (on_paint_after), NULL);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>The signature for the handler of the "paint" signal is:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
void on_paint (ClutterActor *actor, gpointer user_data);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>The paint cycle in Clutter works its way recursively from the
|
|
stage through every child.</para>
|
|
|
|
<para>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.</para>
|
|
|
|
<para>The "paint" signal is defined as <emphasis>run-last</emphasis>,
|
|
that is the signal handlers connected to it using
|
|
<function>g_signal_connetc()</function> 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
|
|
<function>g_signal_connect_after()</function> will be called.</para>
|
|
|
|
<para>This allows pre- and post-default paint handlers, and it also
|
|
allows completely overriding the way an Actor draws itself by default;
|
|
for instance:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
void
|
|
on_paint (ClutterActor *actor)
|
|
{
|
|
do_my_paint (actor);
|
|
|
|
g_signal_stop_emission_by_name (actor, "paint");
|
|
}
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>The code above will prevent the default paint implementation of
|
|
the actor from running.</para>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="actors-opacity">
|
|
<title>Making an actor transparent by changing its opacity</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want an actor to be transparent so that other
|
|
actors are visible through it.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>Change the actor's <emphasis>opacity</emphasis> so that
|
|
it is partially (or even fully) transparent:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
/* 25% transparency */
|
|
clutter_actor_set_opacity (actor, 191);
|
|
|
|
/* 50% transparency */
|
|
clutter_actor_set_opacity (actor, 122);
|
|
|
|
/* completely transparent */
|
|
clutter_actor_set_opacity (actor, 0);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>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.</para>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>Opacity is a property of every <type>ClutterActor</type>.
|
|
It is a float on a scale from 0 (invisible) to 255 (completely
|
|
opaque). Actors with <code>0 < opacity < 255</code> will
|
|
have a varying amount of solidity on the stage, so other actors
|
|
may be visible through them.</para>
|
|
|
|
<para>For example, below are 4 yellow rectangles overlapping
|
|
a white rectangle on a blue stage:</para>
|
|
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata format="PNG"
|
|
fileref="images/actors-opacity.png" />
|
|
</imageobject>
|
|
<alt>
|
|
<para>The effect of different opacities levels on
|
|
an actor's appearance</para>
|
|
</alt>
|
|
</mediaobject>
|
|
</screenshot>
|
|
|
|
<para>The rectangles have the following opacities:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>top-left: <code>255</code> (0% transparency)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>top-right: <code>191</code> (25% transparency)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>bottom-right: <code>122</code> (50% transparency)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>bottom-left: <code>61</code> (75% transparency)</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>Notice how both the stage and the white rectangle are
|
|
visible through the yellow rectangles.</para>
|
|
|
|
<para>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.</para>
|
|
|
|
<para>The following sections cover some other considerations
|
|
when working with actor opacity.</para>
|
|
|
|
<section>
|
|
<title>Container and color opacity</title>
|
|
|
|
<para>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 <code>122</code>
|
|
(50% transparent) and the child also has an opacity of
|
|
<code>122</code>, the child's <emphasis>effective</emphasis>
|
|
opacity is 25% (<code>opacity = 61</code>, and it is
|
|
75% transparent).</para>
|
|
|
|
<para>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:</para>
|
|
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata format="PNG"
|
|
fileref="images/actors-opacity-container-affects-opacity.png" />
|
|
</imageobject>
|
|
<alt>
|
|
<para>How a container's opacity affects the opacity of
|
|
its children</para>
|
|
</alt>
|
|
</mediaobject>
|
|
</screenshot>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>The left-hand rectangle has <code>opacity = 255</code>
|
|
and is in a <type>ClutterGroup</type> with
|
|
<code>opacity = 255</code>. This means it is fully opaque.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The middle rectangle has <code>opacity = 255</code>
|
|
and is in a <type>ClutterGroup</type> with
|
|
<code>opacity = 122</code>. Notice that the parent opacity
|
|
makes the rectangle appear darker, as the stage colour is showing
|
|
through from behind.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The right-hand rectangle has <code>opacity = 122</code>
|
|
and is in a <type>ClutterGroup</type> with
|
|
<code>opacity = 122</code>. Notice that the rectangle appears
|
|
to be even darker, as the stage colour is showing
|
|
through both the rectangle and its parent.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>Similarly, <type>ClutterColor</type> also contains an
|
|
<varname>alpha</varname> property which governs the transparency
|
|
of the color. Where an actor can have a color set (e.g.
|
|
<type>ClutterRectangle</type>) the alpha value of the color also
|
|
affects the transparency of the actor, for example:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* color with 50% transparency */
|
|
ClutterColor half_transparent_color = { 255, 0, 0, 122 };
|
|
|
|
ClutterRectangle *actor = clutter_rectangle_new ();
|
|
|
|
/* set actor's transparency to 50% */
|
|
clutter_actor_set_opacity (actor, 122);
|
|
|
|
/* rectangle will be 25% opaque/75% transparent */
|
|
clutter_rectangle_set_color (CLUTTER_RECTANGLE (actor),
|
|
&half_transparent_color);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Depth and depth order</title>
|
|
|
|
<para>Each actor has two more aspects which affect its
|
|
apparent opacity:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>An actor's <emphasis>depth</emphasis> 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 <type>ClutterStage</type> documentation for
|
|
more details about fog.</para>
|
|
|
|
<para>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.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The <emphasis>depth order</emphasis> governs how
|
|
actors within a <type>ClutterContainer</type> implementation
|
|
are placed with respect to each other.</para>
|
|
|
|
<note>
|
|
<para>Depth ordering is not the same thing as depth: depth
|
|
ordering records relationships between actors at the same
|
|
depth.</para>
|
|
</note>
|
|
|
|
<para>If you have two overlapping actors <code>actorA</code> and
|
|
<code>actorB</code> in a container, and you want <code>actorA</code>
|
|
(opaque) to be visible through <code>actorB</code> (transparent),
|
|
you should ensure that <code>actorB</code> is "above" <code>actorA</code>
|
|
in the depth ordering. You could do this as follows:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
/*
|
|
* 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);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para><function>clutter_actor_raise()</function>,
|
|
<function>clutter_actor_lower()</function> and related
|
|
<type>ClutterActor</type> functions set
|
|
depth ordering on actors; see also <type>ClutterContainer</type>'s
|
|
<function>clutter_container_raise_child()</function> and
|
|
<function>clutter_container_lower_child()</function>
|
|
functions.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="actors-non-rectangular">
|
|
<title>Creating an actor with a non-rectangular shape</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want to create a <type>ClutterActor</type> subclass,
|
|
but don't want it to be rectangular; for example, you want a
|
|
star-shaped actor.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>Use Cogl primitives to draw the actor.</para>
|
|
|
|
<para>Below is an example of the pick and paint implementations for a
|
|
star-shaped <type>StarActor</type> class (an extension of
|
|
<type>ClutterActor</type>).</para>
|
|
|
|
<para>Like <type>ClutterRectangle</type>, it has a private
|
|
struct internally, which contains a <type>ClutterColor</type>
|
|
denoting the color it should be painted. This is used to set the Cogl
|
|
source color.</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
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 ();
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>If you need more information about how to implement your own
|
|
<type>ClutterActor</type>, see the Clutter reference
|
|
manual.</para>
|
|
|
|
<para>Note that the code in these two functions is virtually identical:
|
|
the Discussion section suggests how to remove this redundancy.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>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 <type>ClutterRectangle</type>)
|
|
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).</para>
|
|
|
|
<para>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).</para>
|
|
|
|
<para>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.</para>
|
|
|
|
<section>
|
|
<title>Cogl path coordinates</title>
|
|
|
|
<para>In the example shown, <function>cogl_path_move_to()</function>
|
|
and <function>cogl_path_line_to()</function> are used. These
|
|
take absolute <code>x</code> and <code>y</code> coordinates as
|
|
arguments, relative to the GL 'modelview' transform matrix; in
|
|
the case of an actor's <function>paint</function> implementation,
|
|
relative to the bounding box for the actor. So if an actor has
|
|
width and height of 50 pixels, and you used
|
|
<function>cogl_move_to (25, 25)</function> in its
|
|
<function>paint</function> implementation, the "pen"
|
|
moves to the centre of the actor, regardless of where the actor
|
|
is positioned on the stage. Similarly, using
|
|
<function>cogl_path_line_to()</function> creates a line segment
|
|
from the current pen position to the absolute coordinates
|
|
(<code>x</code>, <code>y</code>) specified.</para>
|
|
|
|
<para>The Cogl API also provides various "rel" variants of the path
|
|
functions (e.g. <function>cogl_path_rel_line_to()</function>), which
|
|
create path segments relative to the current pen position (i.e.
|
|
<code>pen_x + x</code>, <code>pen_y + y</code>).</para>
|
|
|
|
<para>It's important to note that the path isn't drawn until you
|
|
call <function>cogl_path_stroke()</function> (to draw the path segments)
|
|
or <function>cogl_path_fill()</function> (to fill the area enclosed by
|
|
the path). The path is cleared once it's been drawn.
|
|
Using the <function>*_preserve</function> variants of these functions draws
|
|
the path and retains it (so it could be drawn again).</para>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Other Cogl primitives</title>
|
|
|
|
<para>Note that the Cogl primitives API provides other types of path
|
|
segment beyond straight lines that we didn't use here, including:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>Bezier curves (<function>cogl_path_curve_to()</function>)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Arcs (<function>cogl_path_arc()</function>)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Polygons (<function>cogl_path_polygon()</function>)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Rectangles (<function>cogl_path_rectangle()</function>)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Rectangles with rounded corners
|
|
(<function>cogl_path_round_rectangle()</function>)</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Ellipses (<function>cogl_path_ellipse()</function>)</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>If you need more flexibility than is available in the Cogl path
|
|
API, you can make direct use of the <type>CoglVertexBuffer</type>
|
|
API instead. This is a lower-level API, but could potentially
|
|
be used to draw more complex shapes.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Using <type>ClutterPath</type> to store the path</title>
|
|
|
|
<para>The disadvantage of the code above is that the paths are stored in two
|
|
places: once for <function>pick</function>, and once for
|
|
<function>paint</function>. It would make sense to store the
|
|
path in one place and reference it from both of these functions to
|
|
prevent duplication.</para>
|
|
|
|
<para>Clutter provides a <type>ClutterPath</type> 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.</para>
|
|
|
|
<para>We can use a <type>ClutterPath</type> instance stored
|
|
inside the actor to define the path for <function>pick</function> and
|
|
<function>paint</function>; then, inside those functions, we
|
|
translate the <type>ClutterPath</type> into Cogl path function calls
|
|
(NB <type>ClutterPath</type> is effectively a declarative method
|
|
for defining a path, while the Cogl path API is imperative).</para>
|
|
|
|
<para>First we add a <varname>path</varname> member to the private
|
|
struct for the <type>StarActor</type> class (using standard
|
|
GObject mechanisms). The <function>init</function> implementation for
|
|
<type>StarActor</type> creates an empty path:</para>
|
|
|
|
<informalexample>
|
|
<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);
|
|
}
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>One consideration is that the path coordinates need to
|
|
fit inside the actor's bounding box. So as the actor's allocation
|
|
changes, <varname>path</varname> also needs to change. We can do this
|
|
by implementing <function>allocate</function> for the
|
|
<type>StarActor</type> class:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
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);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>This clears then adds segments to the
|
|
<type>ClutterPath</type> stored with the
|
|
<type>StarActor</type> instance. The positioning and
|
|
lengths of the segments are relative to the size of the actor when
|
|
its allocation changes.</para>
|
|
|
|
<para>The <function>pick</function> and <function>paint</function>
|
|
functions now reference the <type>ClutterPath</type> (only the
|
|
<function>pick</function> is shown below); and
|
|
to turn the path into drawing operations, we implement a
|
|
<function>star_actor_convert_clutter_path_node()</function> function
|
|
which takes a <type>ClutterPathNode</type> and converts it
|
|
into its Cogl equivalent:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
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 ();
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<note>
|
|
<para>The conversion function only covers
|
|
<type>ClutterPathNode</type> types encountered in this
|
|
actor.</para>
|
|
</note>
|
|
|
|
<para>Instead of converting to Cogl path operations, another alternative
|
|
would be to use the <function>clutter_path_to_cairo_path()</function>
|
|
function to write directly from the <type>ClutterPath</type>
|
|
onto a Cairo context.</para>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</chapter>
|