mutter/doc/cookbook/clutter-cookbook.xml.in

1285 lines
43 KiB
XML
Raw Normal View History

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
<!ENTITY appurl "http://www.clutter-project.org">
<!ENTITY docurl "http://docs.clutter-project.org/docs/clutter-cookbook">
<!ENTITY author_mail "ebassi@linux.intel.com">
<!ENTITY apiversion "@CLUTTER_API_VERSION@">
]>
<book lang="en">
2009-09-02 12:13:44 +00:00
<bookinfo> <!-- {{{ -->
<author>
<firstname>Emmanuele</firstname>
<surname>Bassi</surname>
<address><email>&author_mail;</email></address>
</author>
<copyright>
<year>2009, 2010</year>
<holder>Intel Corporation</holder>
</copyright>
<legalnotice>
<para>This document is distributed under the terms of the GNU General
Public License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version. A copy
of this license can be found in the file COPYING included with the
source code of this program.</para>
</legalnotice>
<title>The Clutter Cookbook</title>
<releaseinfo>for Clutter &apiversion;</releaseinfo>
</bookinfo> <!-- }}} -->
<chapter id="introduction"> <!-- {{{ -->
<title>Preface</title>
<epigraph>
<attribution>The Perl Cookbook</attribution>
<para>Let me show you that easy way, so others may easily follow.</para>
</epigraph>
<para>There is a wonderful simile in the preface of the <emphasis>Perl
Cookbook</emphasis>: approaching a programming problem is oftentimes
similar to balancing Columbus's egg. The initial difficulties of dealing
with, and more importantly solving, problems in the computer programming
field sometimes can only be overcome if somebody shows you how to use a
new tool. This is true for programming languages but also for programming
libraries.</para>
<para>This book has been written to try and give you a reference on
how to solve common issues that you might have to face when using
the Clutter toolkit.</para>
<para>This book is not meant to be a replacement for the API reference,
even though there will be descriptions of how Clutter works and how
its API looks like. We will require knowledge of the Clutter API, but
we will also point out where to find more information on the API that
examples have used.</para>
<para>Indeed, this book should be used as a companion to the API reference,
expanding the examples and showing how to achieve a specific result.</para>
<para>This is not a book for learning Clutter. This is also not a book
for learning C, or GObject or even GUI development.</para>
<para>Above all, this is a book for learning <emphasis>more</emphasis>
about Clutter, and about how to use it in the most efficient and easiest
way. It is meant to help you move past the basic usage of Clutter.</para>
<para>This book is divided into chapters. Each chapter is dedicated to
a specific class, like ClutterTexture, or a specific area, like animations.
Each chapter starts with a short introduction, followed by different
<emphasis>recipes</emphasis>. Each recipe starts with a problem, or a short
statement describing what we want to achieve; a solution, containing the
source code; and a discussion section, where the code is explained, where
alternative approaches might be useful, caveats and references to the
Clutter API for furher studying.</para>
<para>This book, in the cookbook spirit, can be accessed mostly at
random.</para>
<section>
<title>About Clutter</title>
<para>Clutter is an free and open source software library for creating
portable, dynamic, compelling and fast graphical user interfaces.</para>
<para>Clutter uses OpenGL (and, optionally, OpenGL ES on mobile and
embedded platforms) for rendering the user interface elements, but
at the same time it exposes an application program interface that hides
the underlying complexity of the OpenGL state machine from the
developer.</para>
<para>The program interface of Clutter is intended to be easy to use,
efficient, flexible and as self-documenting as possible.</para>
</section>
<section>
<title>About this document</title>
<para>This document is available in various formats like HTML, and
PDF.</para>
<para>The latest version is always available at
<ulink url="&docurl;">&docurl;</ulink>.</para>
</section>
<section>
<title>Acknowledgments</title>
<para>This book has been written taking the inspiration from the Perl
Cookbook, authored by Tom Christiansen and Nathan Torkington.</para>
<para>Various recipes throughout the book have been written by Elliot
Smith.</para>
</section>
<section>
<title>Where to get Clutter</title>
<para>You can obtain Clutter from <ulink url="&appurl;">&appurl;</ulink>.</para>
<para>Clutter is also available on all major GNU/Linux distributions,
in various package formats.</para>
<para>On OSX, Clutter is available with both Fink and MacPorts.</para>
<para>Binaries for Microsoft Windows are also available.</para>
</section>
</chapter> <!-- introduction }}} -->
<chapter id="actors"> <!-- actors {{{ -->
<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 &mdash; that is, what is displayed on the screen &mdash; 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-allocation-notify"> <!-- {{{ -->
<title>Knowing when an actor position or size change</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 :x, :y, :width and :height 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)
{
ClutterActor *actor = CLUTTER_ACTOR (gobject);
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 :x and :y
properties to know that the platform-specific window embedding the
stage has been moved &mdash; 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> <!-- recipe 1 }}} -->
<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 traslations). 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> <!-- }}} -->
</chapter> <!-- actors }}} -->
<chapter id="events">
<title>Events</title>
<epigraph>
<attribution>The Tenth Doctor (David Tennant)</attribution>
<para>Crossing into established events is strictly forbidden. Except for
cheap tricks.</para>
</epigraph>
<section id="events-introduction">
<title>Introduction</title>
<para>Once you have set up a scene on the stage, in order to respond
to user interaction you will have to handle events coming from the
underlying platform.</para>
<para>Events are relayed to actors by Clutter in form of
<emphasis>signals</emphasis>; signals are a facility provided by the
GObject framework to call functions depending on a unique name. A signal
can be thought as a message that an object instance broadcasts to various
listener functions.</para>
<para>There are various events that Clutter will handle: mostly, they
deal with input devices, like a mouse pointer or a keyboard; but they can
also come from the windowing system, like the
<emphasis>delete-event</emphasis> signal that is emitted when the user
closes the window of the stage.</para>
<para>Each event has a particular <emphasis>source</emphasis>, that is
the actor that received the event. The event handling sequence is divided
in two phases:</para>
<orderedlist>
<listitem><para>the <emphasis>capture</emphasis> phase, which consists
in an emission of the <emphasis>captured-event</emphasis> signal
starting from the stage to, following the parent-child relationship,
the source of the event;</para></listitem>
<listitem><para>the <emphasis>bubble</emphasis> phase, which consists
in an emission of the <emphasis>event</emphasis> signal starting from
the the source of the event to, following the parent-child
relationship, the stage.</para></listitem>
</orderedlist>
<para>At any point during the event emission sequence a handler of either
the captured-event or the event signals can stop it, by returning a value
of TRUE, which means that the event has been handled. If an event hasn't
been handled, FALSE should be returned instead.</para>
</section>
<section id="events-handling-key-events">
<title>Handling key events</title>
<section>
<title>Problem</title>
<para>You want to respond to key presses on an actor.</para>
</section>
<section>
<title>Solutions</title>
<para>There are two possible solutions:</para>
<orderedlist>
<listitem>
<para><emphasis>Solution 1:</emphasis> Connect a callback to the
actor; inside the callback, manually analyse which key and
modifier(s) were pressed and react accordingly.</para>
</listitem>
<listitem>
<para><emphasis>Solution 2:</emphasis> Use an actor's
ClutterBindingPool to declaratively assign actions to specific
key and modifier combinations.</para>
</listitem>
</orderedlist>
<para>Each solution is covered below.</para>
<section>
<title>Solution 1</title>
<para>Connect the key-press-event signal for an actor to a callback;
then examine the event in the callback to determine which key and
modifiers were pressed.</para>
<para>First, connect an actor's key-press-event signal
to a callback:</para>
<informalexample>
<programlisting>
g_signal_connect (actor,
"key-press-event",
G_CALLBACK (_key_press_cb),
NULL);
</programlisting>
</informalexample>
<para>Then, in the callback, check which key was pressed and which
modifiers were down at the same time. For example, this callback
checks for a press on the up arrow key and whether
the Shift and/or Control key were down:</para>
<informalexample>
<programlisting>
static void
_key_press_cb (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
guint keyval = clutter_event_get_key_symbol (event);
if (CLUTTER_Up == keyval)
{
ClutterModifierType modifiers = clutter_event_get_state (event);
switch (modifiers)
{
case CLUTTER_SHIFT_MASK:
g_debug ("Up and shift pressed");
break;
case CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK:
g_debug ("Up and shift and control pressed");
break;
default:
g_debug ("Up pressed");
break;
}
}
}
</programlisting>
</informalexample>
<para>Note that Clutter provides a range of key value definitions
(like CLUTTER_Up, used above). These are generated from the list in the
<ulink url="http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h">X.Org source code</ulink>
(replace "XK" with "CLUTTER" in the definitions there to get the
CLUTTER equivalents; alternatively, look at the clutter-keysyms.h
header file for the list).</para>
<para>CLUTTER_SHIFT_MASK, CLUTTER_CONTROL_MASK and other modifiers are
defined in the ClutterModifierType enum.</para>
</section>
<section>
<title>Solution 2</title>
<para>Assign actions to an actor's ClutterBindingPool.
A binding pool stores mappings from a key press (either a single key
or a key plus modifiers) to actions; an action is simply a callback
function with a specific signature.</para>
<para>While this approach is trickier to implement, it is more
flexible and removes the drudgery of writing branching code to
handle different key presses. See the Discussion section for
more details.</para>
<para>To use this approach with an actor which will receive key press
events, first get that actor's binding pool. In the example below,
we're using the binding pool for the default ClutterStage:</para>
<informalexample>
<programlisting>
ClutterBindingPool *binding_pool;
binding_pool = clutter_binding_pool_get_for_class (CLUTTER_STAGE_GET_CLASS (stage));
</programlisting>
</informalexample>
<para>Next, install actions into the binding pool. For example, to
install an action bound to the up arrow key, which calls the _move_up
function when that key is pressed, you would do:</para>
<informalexample>
<programlisting>
clutter_binding_pool_install_action (binding_pool,
"move-up", /* identifier */
CLUTTER_Up, /* up arrow pressed */
0, /* no modifiers pressed */
G_CALLBACK (_move_up),
NULL, /* no user data passed */
NULL);
</programlisting>
</informalexample>
<para>Another example, binding up + Shift + Control to an action which
calls _move_up_shift_control when activated:</para>
<informalexample>
<programlisting>
clutter_binding_pool_install_action (binding_pool,
"move-up-shift-control",
CLUTTER_Up,
CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK,
G_CALLBACK (_move_up_shift_control),
NULL,
NULL);
</programlisting>
</informalexample>
<para>The function called when an action is activated looks like this
(for _move_up):</para>
<informalexample>
<programlisting>
static void
_move_up (GObject *instance,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
gpointer user_data)
{
g_debug ("Up pressed");
}
</programlisting>
</informalexample>
<para>Then bind the key-press-event for the actor (in our case,
the stage) to a callback:</para>
<informalexample>
<programlisting>
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (_key_press_cb),
NULL);
</programlisting>
</informalexample>
<para>Finally, inside the callback, pass control to the actor's
binding pool rather than dissecting the key press event
yourself:</para>
<informalexample>
<programlisting>
static gboolean
_key_press_cb (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
ClutterBindingPool *pool;
pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
return clutter_binding_pool_activate (pool,
clutter_event_get_key_symbol (event),
clutter_event_get_state (event),
G_OBJECT (actor));
}
</programlisting>
</informalexample>
<para>Now, when a key plus modifiers that has been bound to an action
is pressed on the actor, the appropriate action is activated.</para>
</section>
</section>
<section>
<title>Discussion</title>
<section>
<title>Pros and cons of Solution 1 and Solution 2</title>
<para>Solution 1 is the simplest (in terms of the amount of code you
have to write for simple cases), but could quickly turn into a mess if
you need many conditions or want to capture many key combinations.
Also, if multiple actors need to respond to key press events, you'll
need similar event dissection code in each callback.</para>
<para>Solution 2 is more complicated to implement, but scales better
if you have many different key combinations on multiple actors.
The binding pool protects you from the minutiae of detecting which
keys were pressed, leaving you to concentrate on the
triggered actions instead. This could simplify your control
logic.</para>
<para>In addition, Solution 2 lets you write a single callback to
handle all key press events for all actors. This callback could then
use clutter_binding_pool_find (as in the example code) to determine
which binding pool to activate (depending on which actor received the
key press event).</para>
<para>Finally, a binding pool allows you to block and unblock actions.
This means you can make the response to a key press event conditional
on application state. For example, let's say you wanted the up arrow
key to move an actor, but only when the actor is at the bottom
of the stage. To implement this, you could disable the up arrow key
action in the binding pool initially; then, once the actor reaches the
bottom of the stage, enable the up arrow key action again. While this
is possible with Solution 1, you would have to implement more of the
state management code yourself.</para>
</section>
<section>
<title>Other useful things to know about key press events</title>
<itemizedlist>
<listitem>
<para>A ClutterKeyEvent contains only a <emphasis>single</emphasis>
key value, plus possibly one or more modifier keys (like Shift,
Control, Alt etc.). There are no functions in the
Clutter API which return events for tracking near-simultaneous
presses on multiple keys.</para>
</listitem>
<listitem>
<para>By default, the stage receives all key events.
To make another actor receive key events, use
clutter_stage_set_key_focus:</para>
<informalexample>
<programlisting>
/*
* stage is a ClutterStage instance;
* actor is the ClutterActor instance which should receive key events
*/
clutter_stage_set_key_focus (stage, actor);
</programlisting>
</informalexample>
</listitem>
</itemizedlist>
</section>
</section>
</section>
</chapter> <!-- }}} events -->
<chapter id="textures"> <!-- textures {{{ -->
<title>Textures</title>
<epigraph>
<attribution>the author of the epigraph</attribution>
<para>a short epigraph</para>
</epigraph>
<section id="textures-introduction">
<title>Introduction</title>
<para>introduction</para>
</section>
<section id="textures-drawing-with-cairo">
<title>Drawing 2D graphics onto a texture</title>
<section>
<title>Problem</title>
<para>You want to draw 2D graphics inside a Clutter application.</para>
</section>
<section>
<title>Solution</title>
<para>Create a ClutterCairoTexture, then draw onto the Cairo context
it wraps using the Cairo API:</para>
<informalexample>
<programlisting>
ClutterActor *texture;
cairo_t *cr;
guint width, height;
width = 800;
height = 600;
texture = clutter_cairo_texture_new (width, height);
cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (texture));
/*
* write onto the Cairo context cr using the Cairo API;
* see <ulink url="http://cairographics.org/manual/">the Cairo API reference</ulink> for details
*/
cairo_move_to (cr, 0, 0);
cairo_line_to (cr, 800, 600);
cairo_stroke (cr);
/* does the actual drawing onto the texture */
cairo_destroy (cr);
</programlisting>
</informalexample>
<para>Here's a <ulink url="http://cairographics.org/tutorial/">useful
Cairo tutorial</ulink> if you want to learn more about the Cairo API
itself.</para>
</section>
<section>
<title>Discussion</title>
<para>A ClutterCairoTexture is a standard ClutterActor, so it can be
added to ClutterContainers (e.g. a ClutterStage or ClutterGroup),
animated, resized etc. in the usual ways.</para>
<para>Other useful operations:</para>
<itemizedlist>
<listitem>
<para><emphasis>To draw on part of the texture:</emphasis>
use <function>clutter_cairo_texture_create_region</function> to
retrieve a Cairo context for the region you want to draw on.</para>
</listitem>
<listitem>
<para><emphasis>To clear existing content from a texture:</emphasis>
use <function>clutter_cairo_texture_clear</function>.</para>
<para>You may need to do this as the texture reuses the same
Cairo context each time you call
<function>clutter_cairo_texture_create</function> or
<function>clutter_cairo_texture_create_region</function>.</para>
</listitem>
<listitem>
<para><emphasis>To resize the Cairo context wrapped
by a texture</emphasis>, use
<function>clutter_cairo_texture_set_surface_size</function>.</para>
</listitem>
</itemizedlist>
<section>
<title>Drawing pages from a PDF onto a ClutterCairoContext</title>
<para>Other libraries may provide an API for writing onto a
Cairo context; you can make use of these APIs on the exposed
Cairo context of a ClutterCairoTexture. For example, you
can use the poppler-glib API to display pages
from a PopplerDocument inside a Clutter application:</para>
<informalexample>
<programlisting>
<![CDATA[
#include <poppler/glib/poppler.h>
/* snipped setup code (as above) */
/*
* cast to CLUTTER_CAIRO_TEXTURE, as the functions
* used below require that type
*/
ClutterCairoTexture *cc_texture = CLUTTER_CAIRO_TEXTURE (texture);
clutter_cairo_texture_clear (cc_texture);
gchar *file_uri = "file:///path/to/file.pdf";
guint page_num = 0;
double page_width, page_height;
PopplerDocument *doc;
PopplerPage *page;
GError *error = NULL;
doc = poppler_document_new_from_file (file_uri, NULL, &error);
page = poppler_document_get_page (doc, page_num);
poppler_page_get_size (page, &page_width, &page_height);
cr = clutter_cairo_texture_create (cc_texture);
/* render the page to the context */
poppler_page_render (page, cr);
cairo_destroy (cr);
]]>
</programlisting>
</informalexample>
<para>Note that if the page is larger than the Cairo context,
some of it might not be visible. Similarly, if the ClutterCairoTexture
is larger than the stage, some of that might not be visible. So you
may need to do some work to make the ClutterCairoTexture fit
inside the stage properly (e.g. resize the stage), and/or some work
to make the PDF page sit inside the Cairo context (e.g. scale the PDF
page or put it inside a scrollable actor).</para>
</section>
</section>
</section>
<section id="textures-aspect-ratio">
<title>Maintaining the aspect ratio when loading an
image into a texture</title>
<section>
<title>Problem</title>
<para>You want want to load an image into a texture
and scale it, while retaining the underlying image's aspect ratio.</para>
</section>
<section>
<title>Solution</title>
<para>Set the texture to keep the aspect ratio of the
underlying image (so it doesn't distort when it's scaled); use
the actor's request-mode property to set the correct
geometry management (see the discussion section); then
resize the texture along one dimension (height or width).
Now, when an image is loaded into the texture, the image is
scaled to fit the set height or width; the other dimension
is automatically scaled by the same factor so the image fits
the texture:</para>
<informalexample>
<programlisting>
<![CDATA[
ClutterActor *texture;
texture = clutter_texture_new ();
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture), TRUE);
/*
* this setting means the height of the scaled image is based on its width;
* it's not strictly necessary to set this, as this is the default
*/
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_HEIGHT_FOR_WIDTH);
/* set the width, which causes height to be scaled by the same factor */
clutter_actor_set_width (texture, 300);
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
"/path/to/image.jpg",
NULL);
]]>
</programlisting>
</informalexample>
</section>
<section>
<title>Discussion</title>
<para>The request mode for an actor determines how
geometry requisition is performed; in this case, this
includes how scaling is applied if you change the actor's
width or height. There are two possible values for
request-mode:</para>
<orderedlist>
<listitem>
<para>If set to <emphasis>CLUTTER_REQUEST_HEIGHT_FOR_WIDTH</emphasis>
(the default), changing the width causes the height
to be scaled by the same factor as the width.</para>
</listitem>
<listitem>
<para>If set to <emphasis>CLUTTER_REQUEST_WIDTH_FOR_HEIGHT</emphasis>,
changing the height causes the width to be scaled by the
same factor as the height.</para>
</listitem>
</orderedlist>
<para>In the example above, the texture is set to keep its
aspect ratio then fixed to a width of 300 pixels; the
request-mode is set to CLUTTER_REQUEST_HEIGHT_FOR_WIDTH. If a
standard, photo-sized image in landscape orientation were
loaded into it (2848 pixels wide x 2136 high), it would be scaled
down to 300 pixels wide; then, its height would be scaled by the
same factor as the width (i.e. scaled down to 225 pixels).</para>
<para>With request-mode set to CLUTTER_REQUEST_WIDTH_FOR_HEIGHT,
you would get the same effect by setting the height first;
then, computation of the width for the scaled image would be
based on the scaling factor applied to its height instead.</para>
<para>You can work out which side of the source image is longest using
clutter_texture_base_size() to get its width and height. This can
be useful when trying to scale images with different orientations
to fit into uniform rows or columns:</para>
<informalexample>
<programlisting>
<![CDATA[
gint width;
gint height;
clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), &width, &height);
]]>
</programlisting>
</informalexample>
<para>Note that if you explicitly set the size (both width and height)
of a texture with clutter_actor_set_size() (or
with clutter_actor_set_width() and clutter_actor_set_height()), any
image loaded into the texture is automatically stretched/shrunk to
fit the texture. This is the case regardless of any other settings
(like whether to keep aspect ratio).</para>
<para>Also note that a texture won't try to fit itself inside the bounds of
its parent container: so if it's bigger than its container, only part
of it may be visible.</para>
</section>
</section>
<section id="textures-image-loading">
<title>Loading image data into a texture</title>
<section>
<title>Problem</title>
<para>You want to display an image inside a Clutter
application.</para>
</section>
<section>
<title>Solution</title>
<para>Create a ClutterTexture directly from an image file:</para>
<informalexample>
<programlisting>
<![CDATA[
ClutterActor *texture;
GError *error = NULL;
gchar *image_path = "/path/to/image";
texture = clutter_texture_new_from_file (image_path, &error);
if (error != NULL)
{
// handle error
}
]]>
</programlisting>
</informalexample>
<para>Or create a texture and set its source to an image
file:</para>
<informalexample>
<programlisting>
<![CDATA[
ClutterActor *texture;
GError *error = NULL;
gchar *image_path = "/path/to/image";
gboolean loaded;
texture = clutter_texture_new ();
/*
* returns FALSE if file could not be loaded or texture
* could not be set from image data in the file
*/
loaded = clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
image_path,
&error);
if (error != NULL)
{
// handle error
}
]]>
</programlisting>
</informalexample>
</section>
<section>
<title>Discussion</title>
<para>Bear the following in mind when loading images into a
texture:</para>
<itemizedlist>
<listitem>
<para>An image load may fail if:
<itemizedlist>
<listitem>
<para>The file does not exist.</para>
</listitem>
<listitem>
<para>The image format is unsupported: most of the
common bitmap formats (PNG, JPEG, BMP, GIF, TIFF, XPM)
are supported, but more exotic ones may not be.</para>
</listitem>
</itemizedlist>
</para>
</listitem>
<listitem>
<para>Whether you're creating a texture from an image file,
or loading an image from a file into an existing texture,
you should specify the filesystem path to the file, rather
than a URI.</para>
</listitem>
</itemizedlist>
<section>
<title>Synchronous vs. asynchronous image loading</title>
<para>The code examples above show the simplest approach:
loading an image into a texture synchronously. This means that
the application waits for each image to be loaded before continuing;
which is acceptable in this case, but may not be when
loading images into multiple textures.</para>
<para>Another approach is to load data into textures
asynchronously. This requires some extra set up in your code:</para>
<itemizedlist>
<listitem>
<para>Call g_thread_init() (from the GLib library) prior
to calling clutter_init(), so that a local thread is used
to load the file, rather than the main loop. (Note that
this is not necessary if you're using GLib version >= 2.24,
since GObject initializes threading with the type system.)</para>
</listitem>
<listitem>
<para>Set the texture to load data asynchronously.</para>
</listitem>
<listitem>
<para>Connect a callback to the texture's load-finished
signal to handle any errors which occur during loading,
and/or to do extra work if data loads successfully.</para>
</listitem>
</itemizedlist>
<para>The code below shows how to put these together:</para>
<informalexample>
<programlisting>
<![CDATA[
/* callback to invoke when a texture finishes loading image data */
static void
_load_finished_cb (ClutterTexture *texture,
gpointer error,
gpointer user_data)
{
GError *err = (GError *)error;
gchar *image_path = (gchar *)user_data;
if (err != NULL)
g_warning ("Could not load image from file %s; message: %s",
image_path,
err->message);
else
g_debug ("Image loaded from %s", image_path);
}
int
main (int argc, char *argv[])
{
/* initialize GLib's default threading implementation */
g_thread_init (NULL);
clutter_init (&argc, &argv);
/* ... get stage etc. */
ClutterActor *texture;
GError *error = NULL;
texture = clutter_texture_new ();
/* load data asynchronously */
clutter_texture_set_load_async (CLUTTER_TEXTURE (texture), TRUE);
/* connect a callback to the "load-finished" signal */
g_signal_connect (texture,
"load-finished",
G_CALLBACK (_load_finished_cb),
image_path);
/* load the image from a file */
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
image_path,
&error);
/* ... clutter_main () etc. */
}
]]>
</programlisting>
</informalexample>
</section>
<section>
<title>Other ways to load image data into a texture</title>
<para>While it's useful to load image data into a texture directly
from a file, there are occasions where you may have image data
in some other (non-file) format:</para>
<itemizedlist>
<listitem>
<para>Various GNOME libraries provide image data in GdkPixbuf
structures; clutter-gtk has functions for
creating or setting a texture from a GdkPixbuf:
gtk_clutter_texture_new_from_pixbuf()
and gtk_clutter_texture_set_from_pixbuf() respectively.</para>
</listitem>
<listitem>
<para>If you have raw RGB or YUV pixel data, ClutterTexture has
clutter_texture_set_from_rgb_data() and
clutter_texture_set_from_yuv_data() methods for loading it.</para>
</listitem>
</itemizedlist>
</section>
</section>
</section>
</chapter> <!-- textures }}} -->
<!--
<chapter id="animations">
<title>Animations</title>
<epigraph>
<attribution>the author of the epigraph</attribution>
<para>a short epigraph</para>
</epigraph>
<section id="animations-introduction">
<title>Introduction</title>
<para>introduction</para>
</section>
<section>
<title>Inverting Animations</title>
<section>
<title>Problem</title>
<para>You want to have an animation exactly mirroring another one
that you just played.</para>
</section>
<section>
<title>Solution</title>
<para>...</para>
</section>
<section>
<title>Discussion</title>
<para>...</para>
</section>
</section>
</chapter>
-->
2009-09-02 12:13:44 +00:00
<appendix id="contributing"> <!-- contributing {{{ -->
<title>Contributing to this document</title>
<para>This document is written in Docbook XML. The source file for this
document is located in the subdirectory "doc/cookbook" of the source
directory of Clutter.</para>
</appendix> <!-- contributing }}} -->
<!--
<section id="recipe">
<title>Recipe</title>
<section>
<title>Problem</title>
<para>...</para>
</section>
<section>
<title>Solution</title>
<para>...</para>
</section>
<section>
<title>Discussion</title>
<para>...</para>
</section>
</section>
-->
</book>