2010-07-01 10:29:46 +00:00
|
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
|
|
|
2010-08-11 10:03:02 +00:00
|
|
|
<chapter id="events" xmlns:xi="http://www.w3.org/2003/XInclude">
|
2010-07-01 10:29:46 +00:00
|
|
|
<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
|
2010-11-25 18:13:51 +00:00
|
|
|
the source of the event to, following the parent-child
|
2010-07-01 10:29:46 +00:00
|
|
|
relationship, the stage.</para></listitem>
|
|
|
|
</orderedlist>
|
|
|
|
|
|
|
|
<para>At any point during the event emission sequence a handler of either
|
2012-01-03 12:10:28 +00:00
|
|
|
the captured-event or the event signals can stop it, by returning a boolean
|
|
|
|
value of <emphasis>true</emphasis>, which means that the event has been
|
|
|
|
handled. If an event hasn't been handled, a boolean value of
|
|
|
|
<emphasis>false</emphasis> should be returned instead.</para>
|
|
|
|
|
|
|
|
<note><para>Clutter provides two useful macros to avoid remembering which
|
|
|
|
boolean value should be used in an event signal handler:
|
|
|
|
CLUTTER_EVENT_PROPAGATE, equivalent to FALSE; and CLUTTER_EVENT_STOP,
|
|
|
|
equivalent to TRUE.</para></note>
|
2010-07-01 10:29:46 +00:00
|
|
|
</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
|
2010-07-06 15:00:33 +00:00
|
|
|
<type>ClutterBindingPool</type> to declaratively assign
|
|
|
|
actions to specific key and modifier combinations.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
</orderedlist>
|
|
|
|
|
|
|
|
<para>Each solution is covered below.</para>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution 1</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<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>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>First, connect an actor's
|
|
|
|
<emphasis>key-press-event</emphasis> signal to a callback:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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
|
2010-07-06 15:00:33 +00:00
|
|
|
the <keycap>Shift</keycap> and/or <keycap>Ctrl</keycap>
|
|
|
|
key were down:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
2010-07-19 11:52:00 +00:00
|
|
|
<![CDATA[
|
2010-07-01 10:29:46 +00:00
|
|
|
static gboolean
|
|
|
|
_key_press_cb (ClutterActor *actor,
|
2010-07-19 11:52:00 +00:00
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data)
|
2010-07-01 10:29:46 +00:00
|
|
|
{
|
|
|
|
guint keyval = clutter_event_get_key_symbol (event);
|
|
|
|
|
2010-07-19 11:52:00 +00:00
|
|
|
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);
|
|
|
|
|
2010-09-10 17:21:45 +00:00
|
|
|
if (CLUTTER_KEY_Up == keyval)
|
2010-07-01 10:29:46 +00:00
|
|
|
{
|
2010-07-19 11:52:00 +00:00
|
|
|
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");
|
2010-07-06 15:00:33 +00:00
|
|
|
|
2010-07-01 10:29:46 +00:00
|
|
|
/* The event was handled, and the emission should stop */
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP;
|
2010-07-01 10:29:46 +00:00
|
|
|
}
|
2010-07-06 15:00:33 +00:00
|
|
|
|
2010-07-01 10:29:46 +00:00
|
|
|
/* The event was not handled, and the emission should continue */
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_PROPAGATE;
|
2010-07-01 10:29:46 +00:00
|
|
|
}
|
2010-07-19 11:52:00 +00:00
|
|
|
]]>
|
2010-07-01 10:29:46 +00:00
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-06 15:11:46 +00:00
|
|
|
<note>
|
|
|
|
<para>Clutter provides a range of key value definitions
|
2010-09-10 17:21:45 +00:00
|
|
|
(like <constant>CLUTTER_KEY_Up</constant>, used above). These are
|
2010-07-06 15:11:46 +00:00
|
|
|
generated from the list in the
|
|
|
|
<ulink url="http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h">X.Org source code</ulink>
|
2010-09-10 17:21:45 +00:00
|
|
|
(replace "XK" with "CLUTTER_KEY" in the definitions there to get the
|
|
|
|
Clutter equivalents; alternatively, look at the
|
2010-07-06 15:11:46 +00:00
|
|
|
<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>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution 2</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Assign actions to an actor's <type>ClutterBindingPool</type>.
|
2010-07-01 10:29:46 +00:00
|
|
|
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
|
2010-07-06 15:00:33 +00:00
|
|
|
handle different key presses. See the
|
|
|
|
<link linkend="events-handling-key-events-discussion">Discussion</link>
|
|
|
|
section for more details.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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,
|
2010-07-06 15:00:33 +00:00
|
|
|
we're using the binding pool for the default
|
|
|
|
<type>ClutterStage</type>:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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
|
2010-07-06 15:00:33 +00:00
|
|
|
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>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
clutter_binding_pool_install_action (binding_pool,
|
2010-09-10 17:21:45 +00:00
|
|
|
"move-up", /* identifier */
|
|
|
|
CLUTTER_KEY_Up, /* up arrow pressed */
|
|
|
|
0, /* no modifiers pressed */
|
2010-07-01 10:29:46 +00:00
|
|
|
G_CALLBACK (_move_up),
|
2010-09-10 17:21:45 +00:00
|
|
|
NULL, /* no user data passed */
|
2010-07-01 10:29:46 +00:00
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<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>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
clutter_binding_pool_install_action (binding_pool,
|
|
|
|
"move-up-shift-control",
|
2010-09-10 17:21:45 +00:00
|
|
|
CLUTTER_KEY_Up,
|
2010-07-01 10:29:46 +00:00
|
|
|
CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK,
|
|
|
|
G_CALLBACK (_move_up_shift_control),
|
|
|
|
NULL,
|
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>The function called when an action is activated looks
|
|
|
|
like this (for <function>_move_up()</function>):</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Then bind the <emphasis>key-press-event</emphasis> signal
|
|
|
|
for the actor (in our case, the stage) to a callback:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Now, when a key + modifiers that have been bound to an action
|
|
|
|
are pressed on the actor, the appropriate action is activated.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<section id="events-handling-key-events-discussion">
|
2010-07-01 10:29:46 +00:00
|
|
|
<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
|
2010-07-06 15:00:33 +00:00
|
|
|
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>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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>
|
2010-07-06 15:00:33 +00:00
|
|
|
<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>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>By default, the stage receives all key events.
|
|
|
|
To make another actor receive key events, use
|
2010-07-06 15:00:33 +00:00
|
|
|
<function>clutter_stage_set_key_focus()</function>:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<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>
|
2010-08-11 09:34:49 +00:00
|
|
|
|
|
|
|
<section id="events-mouse-scroll">
|
2010-08-12 09:16:56 +00:00
|
|
|
<title>Detecting mouse scrolling on an actor</title>
|
2010-08-11 09:34:49 +00:00
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
2010-08-12 09:16:56 +00:00
|
|
|
<para>You want to detect when the mouse is scrolled on an
|
|
|
|
actor (e.g. the pointer is over an actor when a mouse
|
|
|
|
wheel is scrolled).</para>
|
2010-08-11 09:34:49 +00:00
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
2010-08-11 10:14:54 +00:00
|
|
|
<para>Connect a callback handler to the <code>scroll-event</code>
|
|
|
|
signal of an actor.</para>
|
2010-08-11 09:34:49 +00:00
|
|
|
|
|
|
|
<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>
|
|
|
|
|
2010-08-12 09:16:56 +00:00
|
|
|
<para>Next, create a callback handler to examine the scroll
|
2010-08-11 09:34:49 +00:00
|
|
|
event and respond to it:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
static gboolean
|
|
|
|
_scroll_event_cb (ClutterActor *actor,
|
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
2010-08-12 09:16:56 +00:00
|
|
|
/* determine the direction the mouse was scrolled */
|
2010-08-11 09:34:49 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP; /* event has been handled */
|
2010-08-11 09:34:49 +00:00
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
2010-08-12 09:16:56 +00:00
|
|
|
|
|
|
|
<para>Finally, connect the 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>
|
|
|
|
|
2010-08-11 09:34:49 +00:00
|
|
|
</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
|
2010-08-12 09:16:56 +00:00
|
|
|
right scrolling (e.g. a trackball mouse or trackpad), left and
|
|
|
|
right scroll events may also be emitted.</para>
|
2010-08-11 09:34:49 +00:00
|
|
|
|
|
|
|
<section>
|
2010-08-11 15:54:28 +00:00
|
|
|
<title>Creating a scrolling viewport for an actor</title>
|
2010-08-11 09:34:49 +00:00
|
|
|
|
|
|
|
<para>While the simple outline above explains the basics
|
|
|
|
of how to connect to scroll events, it doesn't do much to
|
2010-08-11 15:54:28 +00:00
|
|
|
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
|
2010-08-12 09:16:56 +00:00
|
|
|
than the viewport. This example uses a <type>ClutterTexture</type>,
|
2010-08-11 15:54:28 +00:00
|
|
|
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
|
2010-08-12 09:16:56 +00:00
|
|
|
* (STAGE_HEIGHT is define'd at the top of the file)
|
2010-08-11 15:54:28 +00:00
|
|
|
*/
|
|
|
|
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
|
|
|
|
clutter_actor_set_height (texture, STAGE_HEIGHT);
|
|
|
|
|
2010-08-12 09:16:56 +00:00
|
|
|
/*
|
|
|
|
* load the image file;
|
|
|
|
* see <link linkend="textures-aspect-ratio">this recipe</link> for more about loading images into textures
|
|
|
|
*/
|
2010-08-11 15:54:28 +00:00
|
|
|
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
|
2010-08-12 09:16:56 +00:00
|
|
|
that any of its children are clipped: i.e. only parts of
|
|
|
|
its children which fit inside its allocation are visible. This
|
2010-08-11 15:54:28 +00:00
|
|
|
in turn requires setting an explicit size on the group,
|
|
|
|
rather than allowing it to size itself to fit its
|
2010-08-12 09:16:56 +00:00
|
|
|
children (the latter is the default).</para>
|
2010-08-11 15:54:28 +00:00
|
|
|
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
2010-08-12 09:16:56 +00:00
|
|
|
<para>Put the scrollable actor into the viewport; and
|
|
|
|
the viewport into its container (in this case,
|
2010-08-11 15:54:28 +00:00
|
|
|
the default stage):</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
2012-01-24 15:12:41 +00:00
|
|
|
clutter_actor_add_child (viewport, texture);
|
2010-08-11 15:54:28 +00:00
|
|
|
|
2012-01-24 15:12:41 +00:00
|
|
|
clutter_actor_add_child (stage, viewport);
|
2010-08-11 15:54:28 +00:00
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
2010-08-12 09:16:56 +00:00
|
|
|
<para>Create a callback handler for <code>scroll-event</code>
|
|
|
|
signals emitted by the viewport:</para>
|
2010-08-11 15:54:28 +00:00
|
|
|
|
|
|
|
<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)
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP;
|
2010-08-11 15:54:28 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-08-11 16:14:09 +00:00
|
|
|
/* animate the change to the scrollable's y coordinate */
|
2010-08-11 15:54:28 +00:00
|
|
|
clutter_actor_animate (scrollable,
|
|
|
|
CLUTTER_EASE_OUT_CUBIC,
|
|
|
|
300,
|
|
|
|
"y", y,
|
|
|
|
NULL);
|
|
|
|
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP;
|
2010-08-11 15:54:28 +00:00
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</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
|
2010-08-12 09:16:56 +00:00
|
|
|
of <code>0.0</code> (aligned to the top of the viewport).
|
|
|
|
Scrolling up decrements the
|
2010-08-11 15:54:28 +00:00
|
|
|
<code>y</code> coordinate (down to a minumum of
|
|
|
|
<code>viewport_height - scrollable_height</code>). This moves
|
2010-08-12 09:16:56 +00:00
|
|
|
the top of the scrollable actor "outside" the clip area of the
|
2010-08-11 15:54:28 +00:00
|
|
|
viewport; simultaneously, more of the bottom part of the
|
|
|
|
scrollable moves into the clip area, becoming visible.</para>
|
|
|
|
|
2010-08-12 09:16:56 +00:00
|
|
|
<para>Scrolling down increments the <code>y</code> coordinate
|
2010-08-11 15:54:28 +00:00
|
|
|
(but only up to a maximum value of <code>0.0</code>).</para>
|
2010-08-12 09:05:27 +00:00
|
|
|
|
|
|
|
<para>To see how this works in practice, look at
|
|
|
|
<link linkend="events-mouse-scroll-example">the code
|
|
|
|
sample</link>. There, the height of the scrollable actor is
|
|
|
|
set to <code>300</code> and the height of the viewport to
|
|
|
|
<code>150</code>. This means that the <code>y</code>
|
|
|
|
coordinate value for the scrollable actor will vary between
|
2010-08-12 09:16:56 +00:00
|
|
|
<code>-150.0</code>: <code>150</code> (the viewport's height)
|
|
|
|
<code>- 300</code> (the scrollable actor's height), making
|
|
|
|
its base visible and clipping its top; and
|
2010-08-12 09:05:27 +00:00
|
|
|
<code>0.0</code>, where its top is visible and its base
|
|
|
|
clipped.</para>
|
2010-08-11 15:54:28 +00:00
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>Connect the callback handler to the signal; note
|
2010-08-12 09:16:56 +00:00
|
|
|
that we pass the scrollable actor (the texture) to the callback,
|
|
|
|
as we're moving the texture relative to the viewport to
|
2010-08-11 15:54:28 +00:00
|
|
|
create the scrolling effect:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
g_signal_connect (viewport,
|
|
|
|
"scroll-event",
|
|
|
|
G_CALLBACK (_scroll_event_cb),
|
|
|
|
texture);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
</orderedlist>
|
|
|
|
|
2010-08-11 16:12:42 +00:00
|
|
|
<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>
|
|
|
|
|
2010-08-11 09:34:49 +00:00
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Full example</title>
|
|
|
|
|
|
|
|
<example id="events-mouse-scroll-example">
|
2010-08-11 15:54:28 +00:00
|
|
|
<title>Mouse scrolling over a <type>ClutterActor</type></title>
|
2010-08-11 09:34:49 +00:00
|
|
|
<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>
|
2010-08-20 12:54:04 +00:00
|
|
|
|
|
|
|
<section id="events-pointer-motion">
|
|
|
|
<title>Detecting pointer movements on an actor</title>
|
|
|
|
|
|
|
|
<section id="events-pointer-motion-problem">
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want to be able to tell when the pointer (e.g. associated
|
|
|
|
with a mouse or touches on a screen) enters, leaves, or moves over
|
|
|
|
an actor.</para>
|
|
|
|
|
|
|
|
<para>Example use cases include:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>Adding a tooltip or hover effect to an actor when
|
|
|
|
a pointer moves onto it.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>Tracing the path of the pointer over an actor (e.g.
|
|
|
|
in a drawing application).</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="events-pointer-motion-solution">
|
|
|
|
<title>Solution</title>
|
|
|
|
|
|
|
|
<para>Connect to the pointer motion signals emitted by the actor.</para>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Responding to crossing events</title>
|
|
|
|
|
|
|
|
<para>To detect the pointer crossing the boundary of an actor
|
|
|
|
(entering or leaving), connect to the <code>enter-event</code>
|
|
|
|
and/or <code>leave-event</code> signals. For example:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
ClutterActor *actor = clutter_texture_new ();
|
|
|
|
|
|
|
|
/* ...set size, color, image etc., depending on the actor... */
|
|
|
|
|
|
|
|
/* make the actor reactive: see <link linkend="events-pointer-motion-discussion">Discussion</link> for more details */
|
|
|
|
clutter_actor_set_reactive (actor, TRUE);
|
|
|
|
|
|
|
|
/* connect to the signals */
|
|
|
|
g_signal_connect (actor,
|
|
|
|
"enter-event",
|
|
|
|
G_CALLBACK (_pointer_enter_cb),
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
g_signal_connect (actor,
|
|
|
|
"leave-event",
|
|
|
|
G_CALLBACK (_pointer_leave_cb),
|
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>The signature for callbacks connected to each of these
|
|
|
|
signals is:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
gboolean
|
|
|
|
_on_crossing (ClutterActor *actor,
|
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data)
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>In the callback, you can examine the event to get the
|
|
|
|
coordinates where the pointer entered or left the actor. For
|
|
|
|
example, <function>_pointer_enter_cb()</function> could
|
|
|
|
follow this template:</para>
|
|
|
|
|
|
|
|
<informalexample id="events-pointer-motion-callback-example">
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
/* the event passed to the callback is of type ClutterCrossingEvent */
|
|
|
|
static gboolean
|
|
|
|
_pointer_enter_cb (ClutterActor *actor,
|
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
/* get the coordinates where the pointer crossed into the actor */
|
|
|
|
gfloat stage_x, stage_y;
|
|
|
|
clutter_event_get_coords (event, &stage_x, &stage_y);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* as the coordinates are relative to the stage, rather than
|
|
|
|
* the actor which emitted the signal, it can be useful to
|
|
|
|
* transform them to actor-relative coordinates
|
|
|
|
*/
|
|
|
|
gfloat actor_x, actor_y;
|
|
|
|
clutter_actor_transform_stage_point (actor,
|
|
|
|
stage_x, stage_y,
|
|
|
|
&actor_x, &actor_y);
|
|
|
|
|
|
|
|
g_debug ("pointer at stage x %.0f, y %.0f; actor x %.0f, y %.0f",
|
|
|
|
stage_x, stage_y,
|
|
|
|
actor_x, actor_y);
|
|
|
|
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP;
|
2010-08-20 12:54:04 +00:00
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>See <link linkend="events-pointer-motion-example-1">the
|
|
|
|
code example in the appendix</link> for an example of how
|
|
|
|
you can implement a hover effect on a "button" (rectangle
|
|
|
|
with text overlay) using this approach.</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Responding to motion events</title>
|
|
|
|
|
|
|
|
<para>Motion events occur when a pointer moves over an actor;
|
|
|
|
the actor emits a <code>motion-event</code> signal when this
|
|
|
|
happens. To respond to motion events, connect to this signal:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
/* set up the actor, make reactive etc., as above */
|
|
|
|
|
|
|
|
/* connect to motion-event signal */
|
|
|
|
g_signal_connect (actor,
|
|
|
|
"motion-event",
|
|
|
|
G_CALLBACK (_pointer_motion_cb),
|
|
|
|
transitions);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>The signature of the callback is the same as for
|
|
|
|
the <code>enter-event/leave-event</code> signals, so you can use
|
|
|
|
<link linkend="events-pointer-motion-callback-example">code
|
|
|
|
similar to the above</link> to handle it. However, the
|
|
|
|
type of the event is a <type>ClutterMotionEvent</type>
|
|
|
|
(rather than a <type>ClutterCrossingEvent</type>).</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="events-pointer-motion-discussion">
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
|
|
|
<para>A few more useful things to know about pointer motion
|
|
|
|
events:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>Each crossing event is accompanied by a motion event at
|
|
|
|
the same coordinates.</para>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>Before an actor will emit signals for pointer events,
|
|
|
|
it needs to be made reactive with:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
clutter_actor_set_reactive (actor, TRUE);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>A pointer event structure includes other data. Some
|
|
|
|
examples:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
/* keys and mouse buttons pressed down when the pointer moved */
|
|
|
|
ClutterModifierType modifiers = clutter_event_get_state (event);
|
|
|
|
|
|
|
|
/* time (since the epoch) when the event occurred */
|
|
|
|
guint32 event_time = clutter_event_get_time (event);
|
|
|
|
|
|
|
|
/* actor where the event originated */
|
|
|
|
ClutterActor *actor = clutter_event_get_actor (event);
|
|
|
|
|
|
|
|
/* stage where the event originated */
|
|
|
|
ClutterStage *stage = clutter_event_get_stage (event);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>There's no need to cast the event to use these
|
|
|
|
functions: they will work on any <type>ClutterEvent</type>.</para>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para>The coordinates of an event (as returned by
|
|
|
|
<function>clutter_event_get_coords()</function>) are relative
|
|
|
|
to the stage where they originated, rather than the actor. Unless
|
|
|
|
the actor is the same size as the stage, you'll typically want
|
|
|
|
the actor-relative coordinates instead. To get those, use
|
|
|
|
<function>clutter_actor_transform_stage_point()</function>.</para>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>The <link linkend="events-pointer-motion-example-4">simple
|
|
|
|
scribble application</link> gives a more
|
|
|
|
thorough example of how to integrate pointer events into a
|
|
|
|
Clutter application (in this case, for drawing on a
|
|
|
|
<type>ClutterTexture</type>).</para>
|
|
|
|
|
|
|
|
<para>The effect of actor depth on pointer motion events is
|
|
|
|
worth slightly deeper discussion, and is covered next.</para>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Pointer events on actors at different depths</title>
|
|
|
|
|
|
|
|
<para>If you have actors stacked on top of each other, the
|
|
|
|
reactive actor nearest the "top" is the one
|
|
|
|
which emits the signal (when the pointer crosses into or moves
|
|
|
|
over it). "Top" here means either at the top of
|
|
|
|
the depth ordering (if all actors are at the same depth)
|
|
|
|
or the closest to the view point (if actors have different
|
|
|
|
depths in the <code>z</code> axis).</para>
|
|
|
|
|
|
|
|
<para>Here's an example of three rectangles overlapping each
|
|
|
|
other:</para>
|
|
|
|
|
|
|
|
<screenshot>
|
|
|
|
<mediaobject>
|
|
|
|
<imageobject>
|
|
|
|
<imagedata format="PNG"
|
|
|
|
fileref="images/events-pointer-motion-stacking.png" />
|
|
|
|
</imageobject>
|
|
|
|
<alt>
|
|
|
|
<para>Pointer events in actors with different depth ordering</para>
|
|
|
|
</alt>
|
|
|
|
</mediaobject>
|
|
|
|
</screenshot>
|
|
|
|
|
|
|
|
<para>The rectangles are all at the same point on the
|
|
|
|
<code>z</code> axis but stacked (different positions in the depth
|
|
|
|
order). They have the following properties:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>The <emphasis>red</emphasis> rectangle is lowest down
|
|
|
|
the depth ordering and reactive. Pointer motion signals are
|
|
|
|
emitted by this actor when the pointer crosses or moves on the
|
|
|
|
area of the rectangle <emphasis>not</emphasis> overlapped by the
|
|
|
|
green rectangle.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>The <emphasis>green</emphasis> rectangle is in the
|
|
|
|
middle of the depth ordering and reactive. This actor emits
|
|
|
|
events over its whole surface, even though it is overlapped
|
|
|
|
by the blue rectangle (as the blue rectangle is not
|
|
|
|
reactive).</para>
|
|
|
|
<para>Even if the blue rectangle were fully opaque, a pointer
|
|
|
|
crossing into or moving on the green rectangle's area (even if
|
|
|
|
obscured by the blue rectangle) would still cause a signal
|
|
|
|
to be emitted.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>The <emphasis>blue</emphasis> rectangle is at the top
|
|
|
|
of the depth ordering and <emphasis>not</emphasis> reactive.
|
|
|
|
This actor doesn't emit any pointer motion signals and doesn't
|
|
|
|
block events from occurring on any other actor.</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>See <link linkend="events-pointer-motion-example-3">the
|
|
|
|
sample code in the appendix</link> for more details.</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Full examples</title>
|
|
|
|
|
|
|
|
<example id="events-pointer-motion-example-1">
|
|
|
|
<title>Simple button with a hover animation (change in opacity
|
|
|
|
as the pointer enters and leaves it)</title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-pointer-motion-crossing.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
<example id="events-pointer-motion-example-2">
|
|
|
|
<title>Detecting pointer motion on a <type>ClutterRectangle</type></title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-pointer-motion.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
<example id="events-pointer-motion-example-3">
|
|
|
|
<title>How actors influence pointer events on each other</title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-pointer-motion-stacked.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
<example id="events-pointer-motion-example-4">
|
|
|
|
<title>Scribbling on a <type>ClutterTexture</type> in response
|
|
|
|
to pointer events</title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-pointer-motion-scribbler.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
2010-09-30 10:12:14 +00:00
|
|
|
|
|
|
|
<section id="events-buttons">
|
|
|
|
<title>Making an actor respond to button events</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want an actor to respond to button events. These might
|
|
|
|
be buttons on an input device like a mouse;
|
|
|
|
or input events caused by other means, like touches on a screen.</para>
|
|
|
|
|
|
|
|
<para>Some examples of where this is useful:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>For implementing button widgets which respond to
|
|
|
|
button clicks.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>To make actor selections by mouse click (e.g.
|
|
|
|
as part of a drawing application).</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>To recognise a button press followed by pointer
|
|
|
|
motion and button release (e.g. to implement drag
|
|
|
|
and drop or kinetic animations).</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
|
|
|
<para>Connect a handler to the <emphasis>button-press-event</emphasis>
|
|
|
|
and/or <emphasis>button-release-event</emphasis> signals of an
|
|
|
|
actor.</para>
|
|
|
|
|
|
|
|
<note>
|
|
|
|
<para>The <emphasis>button-press-event</emphasis> is emitted
|
|
|
|
when a button is pressed (not necessarily released) on a reactive
|
|
|
|
actor; the <emphasis>button-release-event</emphasis> when a
|
|
|
|
button is released on a reactive actor (even if the button was
|
|
|
|
pressed down somewhere else).</para>
|
|
|
|
</note>
|
|
|
|
|
|
|
|
<para>First, ensure the actor is reactive:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
clutter_actor_set_reactive (actor, TRUE);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Next, create a function to handle the signal(s) you are
|
|
|
|
interested in. The function signature is the same for both the
|
|
|
|
press and release signals:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
gboolean
|
|
|
|
callback_function (ClutterActor *actor,
|
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>You can use a single function as the
|
|
|
|
callback for both signals (or write a different one for each signal).
|
|
|
|
Here's an example function which can be used as a callback
|
|
|
|
for both press and release signals, as it simply pulls data
|
|
|
|
out of the event and displays it:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
/* <varname>event</varname> is a <type>ClutterButtonEvent</type>
|
|
|
|
* for both the press and the release signal; it contains
|
|
|
|
* data about where the event occurred
|
|
|
|
*/
|
|
|
|
static gboolean
|
|
|
|
button_event_cb (ClutterActor *actor,
|
|
|
|
ClutterEvent *event,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
gfloat x, y;
|
|
|
|
gchar *event_type;
|
|
|
|
guint button_pressed;
|
|
|
|
ClutterModifierType state;
|
|
|
|
gchar *ctrl_pressed;
|
|
|
|
guint32 click_count;
|
|
|
|
|
|
|
|
/* where the pointer was (relative to the stage)
|
|
|
|
* when the button event occurred; use
|
|
|
|
* <function>clutter_actor_transform_stage_point()</function>
|
|
|
|
* to transform to actor-relative coordinates
|
|
|
|
*/
|
|
|
|
clutter_event_get_coords (event, &x, &y);
|
|
|
|
|
|
|
|
/* check whether it was a press or release event */
|
|
|
|
event_type = "released";
|
|
|
|
if (clutter_event_type (event) == CLUTTER_BUTTON_PRESS)
|
|
|
|
event_type = "pressed";
|
|
|
|
|
|
|
|
/* which button triggered the event */
|
|
|
|
button_pressed = clutter_event_get_button (event);
|
|
|
|
|
|
|
|
/* keys down when the event occurred;
|
|
|
|
* this is a bit mask composed of the bits for each key held down
|
|
|
|
* when the button was pressed or released; see the
|
|
|
|
* <type>ClutterModifierType</type> enum in the Clutter API docs
|
|
|
|
* for a list of the available modifiers
|
|
|
|
*/
|
|
|
|
state = clutter_event_get_state (event);
|
|
|
|
|
|
|
|
ctrl_pressed = "ctrl not pressed";
|
|
|
|
if (state & CLUTTER_CONTROL_MASK)
|
|
|
|
ctrl_pressed = "ctrl pressed";
|
|
|
|
|
|
|
|
/* click count */
|
|
|
|
click_count = clutter_event_get_click_count (event);
|
|
|
|
|
|
|
|
g_debug ("button %d was %s at %.0f,%.0f; %s; click count %d",
|
|
|
|
button_pressed,
|
|
|
|
event_type,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
ctrl_pressed,
|
|
|
|
click_count);
|
|
|
|
|
2012-01-03 12:10:28 +00:00
|
|
|
return CLUTTER_EVENT_STOP;
|
2010-09-30 10:12:14 +00:00
|
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Finally, connect the signals to the function(s):</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
/* connect the press event */
|
|
|
|
g_signal_connect (actor,
|
|
|
|
"button-press-event",
|
|
|
|
G_CALLBACK (button_event_cb),
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
/* connect the release event */
|
|
|
|
g_signal_connect (actor,
|
|
|
|
"button-release-event",
|
|
|
|
G_CALLBACK (button_event_cb),
|
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Pressing or releasing a button on the actor will now
|
|
|
|
trigger a call to the <function>button_event_cb()</function>
|
|
|
|
function. See <link linkend="events-buttons-example-1">the full
|
|
|
|
example</link> for more details.</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
|
|
|
<para>Properties of the <type>ClutterButtonEvent</type>
|
|
|
|
emitted by both signals should be examined using the
|
|
|
|
<function>clutter_event_*</function> functions (rather than struct
|
|
|
|
members directly), as in the example above. While most of these
|
|
|
|
functions are self-explanatory, a couple require more explanation:
|
|
|
|
see the sections below.</para>
|
|
|
|
|
|
|
|
<para>Also covered below is an alternative approach to handling a
|
|
|
|
button press followed by a release on a single actor: by adding
|
|
|
|
a <type>ClutterClickAction</type> to an actor. See
|
|
|
|
<link linkend="events-buttons-clutterclickaction">this section</link>
|
|
|
|
for details.</para>
|
|
|
|
|
|
|
|
<para>Finally, a <link linkend="events-buttons-example-3">longer
|
|
|
|
example</link> is included, showing how to make use of button press,
|
|
|
|
button release and pointer events in a simple drawing application.</para>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Click count</title>
|
|
|
|
|
|
|
|
<para>The click count records the number of times a press/release
|
|
|
|
pair occurred in sequence. You can retrieve it via the
|
|
|
|
<function>clutter_event_get_click_count()</function> function.</para>
|
|
|
|
|
|
|
|
<note>
|
|
|
|
<para>A press/release pair is effectively a click, so this term
|
|
|
|
will be used from now on throughout this section,
|
|
|
|
to make the explanation simpler. However, the click count has
|
|
|
|
nothing to do with <type>ClutterClickActions</type>, described
|
|
|
|
<link linkend="events-buttons-clutterclickaction">later</link>.
|
|
|
|
</para>
|
|
|
|
</note>
|
|
|
|
|
|
|
|
<para>For clicks to be considered part of the same sequence (for
|
|
|
|
the purposes of counting), all the clicks after the first one
|
|
|
|
must occur within the global <varname>double_click_distance</varname>
|
|
|
|
(pixels) of the first click; and the time between click
|
|
|
|
<code>n</code> and click <code>n+1</code> must be <code><=</code>
|
|
|
|
the global <varname>double_click_time</varname> (milliseconds).</para>
|
|
|
|
|
|
|
|
<para>The clicks <emphasis>do not</emphasis> have to occur on
|
|
|
|
the same actor: providing they occur within the double click
|
|
|
|
distance and time, they are counted as part of the same click
|
|
|
|
sequence. Also note that the clicks don't even have to happen
|
|
|
|
on a reactive actor: providing they happen somewhere on the
|
|
|
|
stage, they will still increment the click count.</para>
|
|
|
|
|
|
|
|
<para>The default double click time and distance are
|
|
|
|
stored in the <type>ClutterSettings</type> associated
|
|
|
|
with an application. You can get/set their values like this:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
gint double_click_distance;
|
|
|
|
gint double_click_time;
|
|
|
|
|
|
|
|
ClutterSettings *settings = clutter_settings_get_default ();
|
|
|
|
|
|
|
|
/* get double click settings */
|
|
|
|
g_object_get (settings,
|
|
|
|
"double-click-distance", &double_click_distance,
|
|
|
|
"double-click-time", &double_click_time,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
/* set */
|
|
|
|
g_object_set (settings,
|
|
|
|
"double-click-distance", 50,
|
|
|
|
"double-click-time", 1000,
|
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Button numbering</title>
|
|
|
|
|
|
|
|
<para><function>clutter_event_get_button()</function> returns
|
|
|
|
an integer representing the pressed or released button.</para>
|
|
|
|
|
|
|
|
<para>In the case of a standard scroll mouse, the numbers
|
|
|
|
returned are reliable across different hardware models:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
2012-01-27 09:46:51 +00:00
|
|
|
<para>1 = left mouse button in a right-handed configuration,
|
|
|
|
or the right mouse button in a left-handed configuration</para>
|
2010-09-30 10:12:14 +00:00
|
|
|
</listitem>
|
|
|
|
<listitem>
|
2012-01-27 09:46:51 +00:00
|
|
|
<para>2 = scroll wheel button</para>
|
2010-09-30 10:12:14 +00:00
|
|
|
</listitem>
|
|
|
|
<listitem>
|
2012-01-27 09:46:51 +00:00
|
|
|
<para>3 = right mouse button in a right-handed configuration,
|
|
|
|
or the left mouse button in a left-handed configuration</para>
|
2010-09-30 10:12:14 +00:00
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
2012-01-27 09:46:51 +00:00
|
|
|
<note><para>Clutter provides symbolic names for the three buttons
|
|
|
|
above: CLUTTER_BUTTON_PRIMARY, CLUTTER_BUTTON_MIDDLE, and
|
|
|
|
CLUTTER_BUTTON_SECONDARY.</para></note>
|
|
|
|
|
2010-09-30 10:12:14 +00:00
|
|
|
<para>For mice with more buttons, or other types of
|
|
|
|
input devices, the mappings may not be so
|
|
|
|
straightforward: you may have to experiment to see
|
|
|
|
which button returns which value.</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="events-buttons-clutterclickaction">
|
|
|
|
<title><type>ClutterClickAction</type></title>
|
|
|
|
|
|
|
|
<para><type>ClutterActions</type> add flexible event handling
|
|
|
|
to <type>ClutterActors</type>. They recognise and abstract
|
|
|
|
common sequences of low-level events into a single, more easily
|
|
|
|
managed high-level event. In the case of a
|
|
|
|
<type>ClutterClickAction</type>, the abstraction is over
|
|
|
|
a press followed by a release on a single actor. This is
|
|
|
|
achieved by "synthesising" the press and release signals on
|
|
|
|
the actor: in other words, the action captures those
|
|
|
|
two signals when emitted by a single actor; and, once captured, the
|
|
|
|
action emits a single <emphasis>clicked</emphasis> signal
|
|
|
|
<emphasis>instead</emphasis> of the two signals being
|
|
|
|
emitted by the actor.</para>
|
|
|
|
|
|
|
|
<para>The pointer can move off the actor between the press and
|
|
|
|
release, but the press and release must both occur on the same
|
|
|
|
actor, with no intervening presses or releases on other
|
|
|
|
actors. In addition, there are no maximum distance or time
|
|
|
|
constraints on the press and release.</para>
|
|
|
|
|
|
|
|
<para>If a press occurs and you want to force it to be released
|
|
|
|
(e.g. to break a pointer grab after a certain length of
|
|
|
|
time has elapsed), use
|
|
|
|
<function>clutter_click_action_release()</function>.</para>
|
|
|
|
|
|
|
|
<para>On the down side, the <emphasis>clicked</emphasis> signal
|
|
|
|
doesn't present the same detailed <type>ClutterButtonEvent</type>
|
|
|
|
to the handler. So, for example, you can't get a click count from a
|
|
|
|
<type>ClutterClickAction</type> (though you could count
|
|
|
|
the clicks yourself, of course); and you don't have access
|
|
|
|
to the coordinates where the press or release occurred.</para>
|
|
|
|
|
|
|
|
<para>To add a click action to a <type>ClutterActor</type>:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
ClutterAction *action = clutter_click_action_new ();
|
|
|
|
clutter_actor_add_action (actor, action);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<note>
|
|
|
|
<para>An actor must still be set to reactive so that its
|
|
|
|
signals can be routed to a click action.</para>
|
|
|
|
</note>
|
|
|
|
|
|
|
|
<para>Create a handler function (note the function
|
|
|
|
signature is different from the one for the press or
|
|
|
|
releas signal handler):</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
void
|
|
|
|
clicked_cb (ClutterClickAction *action,
|
|
|
|
ClutterActor *actor,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
/* display the number of the clicked button (equivalent
|
|
|
|
* to the number returned by clutter_event_get_button())
|
|
|
|
*/
|
|
|
|
g_debug ("Button %d clicked", clutter_click_action_get_button (action));
|
|
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Connect the signal to the handler:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
g_signal_connect (action,
|
|
|
|
"clicked",
|
|
|
|
G_CALLBACK (clicked_cb),
|
|
|
|
NULL);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>The <link linkend="events-buttons-example-2">example
|
|
|
|
code</link> gives a bit more detail about how to use click
|
|
|
|
actions.</para>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Full examples</title>
|
|
|
|
|
|
|
|
<example id="events-buttons-example-1">
|
|
|
|
<title>Examining properties of a <type>ClutterButtonEvent</type></title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-buttons.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
<example id="events-buttons-example-2">
|
|
|
|
<title>Using <type>ClutterClickAction</type> to capture
|
|
|
|
button events on an actor</title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-buttons-click.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
<example id="events-buttons-example-3">
|
|
|
|
<title>Using button and pointer events for drawing</title>
|
|
|
|
<para>This code was inspired by
|
|
|
|
<ulink url="http://git.clutter-project.org/cluttersmith/">ClutterSmith</ulink>
|
|
|
|
</para>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/events-buttons-lasso.c" parse="text">
|
|
|
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
2010-07-01 10:29:46 +00:00
|
|
|
</chapter>
|