a74d585fc5
Put in comments to make it clear where the animation for the mouse scroll is coming from in the sample code.
687 lines
24 KiB
XML
687 lines
24 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="events" xmlns:xi="http://www.w3.org/2003/XInclude">
|
|
<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
|
|
<type>ClutterBindingPool</type> 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 <emphasis>key-press-event</emphasis>
|
|
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
|
|
<emphasis>key-press-event</emphasis> 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 <keycap>Shift</keycap> and/or <keycap>Ctrl</keycap>
|
|
key were down:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static gboolean
|
|
_key_press_cb (ClutterActor *actor,
|
|
ClutterEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
guint keyval = clutter_event_get_key_symbol (event);
|
|
|
|
ClutterModifierType state = clutter_event_get_state (event);
|
|
gboolean shift_pressed = (state & CLUTTER_SHIFT_MASK ? TRUE : FALSE);
|
|
gboolean ctrl_pressed = (state & CLUTTER_CONTROL_MASK ? TRUE : FALSE);
|
|
|
|
if (CLUTTER_Up == keyval)
|
|
{
|
|
if (shift_pressed & ctrl_pressed)
|
|
g_debug ("Up and shift and control pressed");
|
|
else if (shift_pressed)
|
|
g_debug ("Up and shift pressed");
|
|
else
|
|
g_debug ("Up pressed");
|
|
|
|
/* The event was handled, and the emission should stop */
|
|
return TRUE;
|
|
}
|
|
|
|
/* The event was not handled, and the emission should continue */
|
|
return FALSE;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<note>
|
|
<para>Clutter provides a range of key value definitions
|
|
(like <constant>CLUTTER_Up</constant>, 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
|
|
<filename>clutter-keysyms.h</filename> header file for the
|
|
list).</para>
|
|
|
|
<para><constant>CLUTTER_SHIFT_MASK</constant>,
|
|
<constant>CLUTTER_CONTROL_MASK</constant> and other modifiers are
|
|
defined in the <type>ClutterModifierType</type> enum.</para>
|
|
</note>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution 2</title>
|
|
|
|
<para>Assign actions to an actor's <type>ClutterBindingPool</type>.
|
|
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
|
|
<link linkend="events-handling-key-events-discussion">Discussion</link>
|
|
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
|
|
<type>ClutterStage</type>:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
ClutterBindingPool *binding_pool;
|
|
GObjectClass *stage_class;
|
|
|
|
stage_class = CLUTTER_STAGE_GET_CLASS (stage);
|
|
binding_pool = clutter_binding_pool_get_for_class (stage_class);
|
|
</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
|
|
<function>_move_up()</function> 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 arrow +
|
|
<keycap>Shift</keycap> + <keycap>Ctrl</keycap> to an action
|
|
which calls <function>_move_up_shift_control()</function> 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 <function>_move_up()</function>):</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 <emphasis>key-press-event</emphasis> signal
|
|
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 + modifiers that have been bound to an action
|
|
are pressed on the actor, the appropriate action is activated.</para>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
<section id="events-handling-key-events-discussion">
|
|
<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 <function>clutter_binding_pool_find()</function>
|
|
(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 <type>ClutterKeyEvent</type> contains only a
|
|
<emphasis>single</emphasis> key value, plus possibly one
|
|
or more modifier keys (like <keycap>Shift</keycap>,
|
|
<keycap>Ctrl</keycap>, <keycap>Alt</keycap> 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
|
|
<function>clutter_stage_set_key_focus()</function>:</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>
|
|
|
|
<section id="events-mouse-scroll">
|
|
<title>Detecting mouse wheel scrolling on an actor</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want to detect when the mouse wheel is scrolled on an
|
|
actor.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>Connect a callback handler to the <code>scroll-event</code>
|
|
signal of an actor.</para>
|
|
|
|
<para>First, ensure that the actor is reactive (i.e. will
|
|
respond to events):</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
clutter_actor_set_reactive (actor, TRUE);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Next, connect a callback handler to the
|
|
<code>scroll-event</code> signal of the actor:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
g_signal_connect (actor,
|
|
"scroll-event",
|
|
G_CALLBACK (_scroll_event_cb),
|
|
NULL);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Finally, create a callback handler to examine the scroll
|
|
event and respond to it:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static gboolean
|
|
_scroll_event_cb (ClutterActor *actor,
|
|
ClutterEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
/* determine the direction the mouse wheel was scrolled */
|
|
ClutterScrollDirection direction;
|
|
direction = clutter_event_get_scroll_direction (event);
|
|
|
|
/* replace these stubs with real code to move the actor etc. */
|
|
switch (direction)
|
|
{
|
|
case CLUTTER_SCROLL_UP:
|
|
g_debug ("Scrolled up");
|
|
break;
|
|
case CLUTTER_SCROLL_DOWN:
|
|
g_debug ("Scrolled down");
|
|
break;
|
|
case CLUTTER_SCROLL_RIGHT:
|
|
g_debug ("Scrolled right");
|
|
break;
|
|
case CLUTTER_SCROLL_LEFT:
|
|
g_debug ("Scrolled left");
|
|
break;
|
|
}
|
|
|
|
return TRUE; /* event has been handled */
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>A standard mouse wheel will only return up and
|
|
down movements; but in cases where the mouse has left and
|
|
right scrolling (e.g. a trackball mouse), left and right scroll
|
|
events may also be emitted.</para>
|
|
|
|
<section>
|
|
<title>Creating a scrolling viewport for an actor</title>
|
|
|
|
<para>While the simple outline above explains the basics
|
|
of how to connect to scroll events, it doesn't do much to
|
|
help with <emphasis>really</emphasis> implementing scrolling
|
|
over an actor. That's what we'll do in this section.</para>
|
|
|
|
<note>
|
|
<para>The full code for the example we'll walk through here is
|
|
available in <link linkend="events-mouse-scroll-example">this later
|
|
section</link>.</para>
|
|
</note>
|
|
|
|
<para>Scrolling over an actor actually requires coordination
|
|
between two components:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<formalpara>
|
|
<title>Scrollable actor</title>
|
|
<para>An actor which is too large to fit on the stage
|
|
or inside the area of the UI assigned to it (otherwise
|
|
there's no need to scroll over it...).</para>
|
|
</formalpara>
|
|
</listitem>
|
|
<listitem>
|
|
<formalpara>
|
|
<title>Viewport</title>
|
|
<para>This displays a cropped view of part of the scrollable
|
|
actor, revealing different parts of it as scroll events
|
|
occur.</para>
|
|
</formalpara>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>Here are the steps required to set up the two actors:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>Create the scrollable actor; it should be larger
|
|
than the scrollview. This example uses a <type>ClutterTexture</type>,
|
|
but any <type>ClutterActor</type> will work:</para>
|
|
<informalexample>
|
|
<programlisting>
|
|
/* get image file path, set up stage etc. */
|
|
|
|
ClutterActor *texture;
|
|
texture = clutter_texture_new ();
|
|
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture),
|
|
TRUE);
|
|
|
|
/*
|
|
* set the texture's height so it's as tall as the stage
|
|
* (STAGE_HEIGHT is define'd at the top of the file);
|
|
* see <link linkend="textures-aspect-ratio">this recipe</link>
|
|
* for more about loading images into textures
|
|
*/
|
|
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
|
|
clutter_actor_set_height (texture, STAGE_HEIGHT);
|
|
|
|
/* load the image file */
|
|
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
image_file_path,
|
|
NULL);
|
|
</programlisting>
|
|
</informalexample>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>Create the viewport. The simplest way to do
|
|
this is with a <type>ClutterGroup</type>:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
ClutterActor *viewport;
|
|
viewport = clutter_group_new ();
|
|
|
|
/* viewport is _shorter_ than the stage (and the texture) */
|
|
clutter_actor_set_size (viewport, STAGE_WIDTH, STAGE_HEIGHT * 0.5);
|
|
|
|
/* align the viewport to the center of the stage's y axis */
|
|
clutter_actor_add_constraint (viewport,
|
|
clutter_align_constraint_new (stage, CLUTTER_BIND_Y, 0.5));
|
|
|
|
/* viewport needs to respond to scroll events */
|
|
clutter_actor_set_reactive (viewport, TRUE);
|
|
|
|
/* clip all actors inside the viewport to that group's allocation */
|
|
clutter_actor_set_clip_to_allocation (viewport, TRUE);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>The key here is calling
|
|
<code>clutter_actor_set_clip_to_allocation (viewport, TRUE)</code>.
|
|
This configures the <varname>viewport</varname> group so
|
|
that any of its children are clipped: i.e. only the area of
|
|
the children which fits inside the group is visible. This
|
|
in turn requires setting an explicit size on the group,
|
|
rather than allowing it to size itself to fit its
|
|
children (the default).</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Put the scrollable actor into the scroll view and
|
|
the scroll view into its container (in this case,
|
|
the default stage):</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
clutter_container_add_actor (CLUTTER_CONTAINER (viewport), texture);
|
|
|
|
clutter_container_add_actor (CLUTTER_CONTAINER (stage), viewport);
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Create a callback handler for scroll event signals
|
|
emitted by the viewport:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static gboolean
|
|
_scroll_event_cb (ClutterActor *viewport,
|
|
ClutterEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
ClutterActor *scrollable = CLUTTER_ACTOR (user_data);
|
|
|
|
gfloat viewport_height = clutter_actor_get_height (viewport);
|
|
gfloat scrollable_height = clutter_actor_get_height (scrollable);
|
|
|
|
/* no need to scroll if the scrollable is shorter than the viewport */
|
|
if (scrollable_height < viewport_height)
|
|
return TRUE;
|
|
|
|
gfloat y = clutter_actor_get_y (scrollable);
|
|
|
|
ClutterScrollDirection direction;
|
|
direction = clutter_event_get_scroll_direction (event);
|
|
|
|
switch (direction)
|
|
{
|
|
case CLUTTER_SCROLL_UP:
|
|
y -= SCROLL_AMOUNT;
|
|
break;
|
|
case CLUTTER_SCROLL_DOWN:
|
|
y += SCROLL_AMOUNT;
|
|
break;
|
|
|
|
/* we're only interested in up and down */
|
|
case CLUTTER_SCROLL_LEFT:
|
|
case CLUTTER_SCROLL_RIGHT:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* the CLAMP macro returns a value for the first argument
|
|
* that falls within the range specified by the second and
|
|
* third arguments
|
|
*
|
|
* we allow the scrollable's y position to be decremented to the point
|
|
* where its base is aligned with the base of the viewport
|
|
*/
|
|
y = CLAMP (y,
|
|
viewport_height - scrollable_height,
|
|
0.0);
|
|
|
|
/* animate the change to the scrollable's y coordinate */
|
|
clutter_actor_animate (scrollable,
|
|
CLUTTER_EASE_OUT_CUBIC,
|
|
300,
|
|
"y", y,
|
|
NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>The approach taken here is to move the scrollable
|
|
actor up, relative to the viewport. Initially, the
|
|
scrollable will have a <code>y</code> coordinate value
|
|
of <code>0.0</code> (it is aligned to the top of the viewport).
|
|
Scrolling up subtracts from the
|
|
<code>y</code> coordinate (down to a minumum of
|
|
<code>viewport_height - scrollable_height</code>). This moves
|
|
the top of the scrollable "outside" the clip area of the
|
|
viewport; simultaneously, more of the bottom part of the
|
|
scrollable moves into the clip area, becoming visible.</para>
|
|
|
|
<para>Scrolling down adds to the <code>y</code> coordinate
|
|
(but only up to a maximum value of <code>0.0</code>).</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Connect the callback handler to the signal; note
|
|
that we pass the scrollable (the texture) to the callback,
|
|
as we're moving the texture inside the viewport to
|
|
create the scrolling effect:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
g_signal_connect (viewport,
|
|
"scroll-event",
|
|
G_CALLBACK (_scroll_event_cb),
|
|
texture);
|
|
</programlisting>
|
|
</informalexample>
|
|
</listitem>
|
|
|
|
</orderedlist>
|
|
|
|
<para>Here's a video of the result:</para>
|
|
|
|
<inlinemediaobject>
|
|
<videoobject>
|
|
<videodata fileref="videos/events-mouse-scroll.ogv"/>
|
|
</videoobject>
|
|
<alt>
|
|
<para>Video showing a scrollable actor</para>
|
|
</alt>
|
|
</inlinemediaobject>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Full example</title>
|
|
|
|
<example id="events-mouse-scroll-example">
|
|
<title>Mouse scrolling over a <type>ClutterActor</type></title>
|
|
<programlisting>
|
|
<xi:include href="examples/events-mouse-scroll.c" parse="text">
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
</xi:include>
|
|
</programlisting>
|
|
</example>
|
|
</section>
|
|
|
|
</section>
|
|
</chapter>
|