Merge remote branch 'elliot/cookbook-events-mouse-scroll'

* elliot/cookbook-events-mouse-scroll:
  cookbook: Cleaning up grammar and wording in mouse scroll recipe
  cookbook: Added more explanation about setting y coord on scrollable
  cookbook: Mentioned the animation in the sample code
  cookbook: Included video of the scroll example running
  cookbook: Made stage slightly smaller for scroll event example
  cookbook: Added video showing scrollable actor
  cookbook: Added walk through of code example for mouse scroll
  cookbook: Fixed link to example in mouse scroll recipe
  cookbook: Simplified full scroll example
  cookbook: Improved wording and formatting in mouse scroll intro.
  cookbook: Handle all possible mouse scroll directions
  cookbook: Build mouse scroll example with cookbook
  cookbook: Cleaned up redundant comments in code example
  cookbook: Added xmlns for XInclude to events docbook file
  cookbook: Added basic mouse scroll recipe
This commit is contained in:
Emmanuele Bassi 2010-08-12 18:29:08 +01:00
commit 6c6e93d27a
6 changed files with 484 additions and 1 deletions

View File

@ -52,6 +52,7 @@ VIDEO_FILES = \
videos/animations-rotating-z-centered.ogv \
videos/animations-rotating-container-reverses-direction.ogv \
videos/textures-split-go.ogv \
videos/events-mouse-scroll.ogv \
$(NULL)
EXTRA_DIST = \

View File

@ -1,7 +1,7 @@
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<chapter id="events">
<chapter id="events" xmlns:xi="http://www.w3.org/2003/XInclude">
<title>Events</title>
<epigraph>
@ -345,4 +345,357 @@ clutter_stage_set_key_focus (stage, actor);
</section>
</section>
<section id="events-mouse-scroll">
<title>Detecting mouse scrolling on an actor</title>
<section>
<title>Problem</title>
<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>
</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, 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 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>
<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>
</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 or trackpad), 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 viewport. 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)
*/
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
clutter_actor_set_height (texture, STAGE_HEIGHT);
/*
* load the image file;
* see <link linkend="textures-aspect-ratio">this recipe</link> for more about loading images into textures
*/
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 parts of
its children which fit inside its allocation are visible. This
in turn requires setting an explicit size on the group,
rather than allowing it to size itself to fit its
children (the latter is the default).</para>
</listitem>
<listitem>
<para>Put the scrollable actor into the viewport; and
the viewport 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 <code>scroll-event</code>
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> (aligned to the top of the viewport).
Scrolling up decrements the
<code>y</code> coordinate (down to a minumum of
<code>viewport_height - scrollable_height</code>). This moves
the top of the scrollable actor "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 increments the <code>y</code> coordinate
(but only up to a maximum value of <code>0.0</code>).</para>
<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
<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
<code>0.0</code>, where its top is visible and its base
clipped.</para>
</listitem>
<listitem>
<para>Connect the callback handler to the signal; note
that we pass the scrollable actor (the texture) to the callback,
as we're moving the texture relative to 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>

View File

@ -5,3 +5,4 @@
/textures-sub-texture
/layouts-stacking
/layouts-stacking-diff-sized-actors
/events-mouse-scroll

View File

@ -10,6 +10,7 @@ noinst_PROGRAMS = \
textures-sub-texture \
layouts-stacking \
layouts-stacking-diff-sized-actors \
events-mouse-scroll \
$(NULL)
INCLUDES = \
@ -38,3 +39,4 @@ textures_split_go_SOURCES = textures-split-go.c
textures_sub_texture_SOURCES = textures-sub-texture.c
layouts_stacking_SOURCES = layouts-stacking.c
layouts_stacking_diff_sized_actors_SOURCES = layouts-stacking-diff-sized-actors.c
events_mouse_scroll_SOURCES = events-mouse-scroll.c

View File

@ -0,0 +1,126 @@
#include <clutter/clutter.h>
#define STAGE_HEIGHT 300
#define STAGE_WIDTH STAGE_HEIGHT
#define SCROLL_AMOUNT STAGE_HEIGHT * 0.125
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;
}
int
main (int argc, char *argv[])
{
gchar *image_file_path = TESTS_DATA_DIR "/redhand.png";
if (argc > 1)
{
image_file_path = argv[1];
}
ClutterActor *stage;
ClutterActor *viewport;
ClutterActor *texture;
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
clutter_actor_set_size (stage, STAGE_WIDTH, STAGE_HEIGHT);
/* the scrollable actor */
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 */
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
clutter_actor_set_height (texture, STAGE_HEIGHT);
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
image_file_path,
NULL);
/* the viewport which the box is scrolled within */
viewport = clutter_group_new ();
/* viewport is shorter than the stage */
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);
/* put the texture inside the viewport */
clutter_container_add_actor (CLUTTER_CONTAINER (viewport), texture);
/* add the viewport to the stage */
clutter_container_add_actor (CLUTTER_CONTAINER (stage), viewport);
g_signal_connect (viewport,
"scroll-event",
G_CALLBACK (_scroll_event_cb),
texture);
clutter_actor_show (stage);
clutter_main ();
return 0;
}

Binary file not shown.