diff --git a/doc/cookbook/Makefile.am b/doc/cookbook/Makefile.am index d77f4cdc5..b07785b0f 100644 --- a/doc/cookbook/Makefile.am +++ b/doc/cookbook/Makefile.am @@ -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 = \ diff --git a/doc/cookbook/events.xml b/doc/cookbook/events.xml index a4cc5c118..a15c57625 100644 --- a/doc/cookbook/events.xml +++ b/doc/cookbook/events.xml @@ -1,7 +1,7 @@ - + Events @@ -345,4 +345,357 @@ clutter_stage_set_key_focus (stage, actor); + +
+ Detecting mouse scrolling on an actor + +
+ Problem + + 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). +
+ +
+ Solution + + Connect a callback handler to the scroll-event + signal of an actor. + + First, ensure that the actor is reactive (i.e. will + respond to events): + + + + + + + + Next, create a callback handler to examine the scroll + event and respond to it: + + + + + + + + Finally, connect the callback handler to the + scroll-event signal of the actor: + + + + + + + +
+ +
+ Discussion + + 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. + +
+ Creating a scrolling viewport for an actor + + While the simple outline above explains the basics + of how to connect to scroll events, it doesn't do much to + help with really implementing scrolling + over an actor. That's what we'll do in this section. + + + The full code for the example we'll walk through here is + available in this later + section. + + + Scrolling over an actor actually requires coordination + between two components: + + + + + Scrollable actor + 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...). + + + + + Viewport + This displays a cropped view of part of the scrollable + actor, revealing different parts of it as scroll events + occur. + + + + + Here are the steps required to set up the two actors: + + + + Create the scrollable actor; it should be larger + than the viewport. This example uses a ClutterTexture, + but any ClutterActor will work: + + +/* 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 this recipe for more about loading images into textures + */ +clutter_texture_set_from_file (CLUTTER_TEXTURE (texture), + image_file_path, + NULL); + + + + + + + Create the viewport. The simplest way to do + this is with a ClutterGroup: + + + + + + + + The key here is calling + clutter_actor_set_clip_to_allocation (viewport, TRUE). + This configures the viewport 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). + + + + + Put the scrollable actor into the viewport; and + the viewport into its container (in this case, + the default stage): + + + +clutter_container_add_actor (CLUTTER_CONTAINER (viewport), texture); + +clutter_container_add_actor (CLUTTER_CONTAINER (stage), viewport); + + + + + + + Create a callback handler for scroll-event + signals emitted by the viewport: + + + + + + + + The approach taken here is to move the scrollable + actor up, relative to the viewport. Initially, the + scrollable will have a y coordinate value + of 0.0 (aligned to the top of the viewport). + Scrolling up decrements the + y coordinate (down to a minumum of + viewport_height - scrollable_height). 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. + + Scrolling down increments the y coordinate + (but only up to a maximum value of 0.0). + + To see how this works in practice, look at + the code + sample. There, the height of the scrollable actor is + set to 300 and the height of the viewport to + 150. This means that the y + coordinate value for the scrollable actor will vary between + -150.0: 150 (the viewport's height) + - 300 (the scrollable actor's height), making + its base visible and clipping its top; and + 0.0, where its top is visible and its base + clipped. + + + + 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: + + + +g_signal_connect (viewport, + "scroll-event", + G_CALLBACK (_scroll_event_cb), + texture); + + + + + + + Here's a video of the result: + + + + + + + Video showing a scrollable actor + + + +
+
+ +
+ Full example + + + Mouse scrolling over a <type>ClutterActor</type> + + + a code sample should be here... but isn't + + + +
+ +
diff --git a/doc/cookbook/examples/.gitignore b/doc/cookbook/examples/.gitignore index 29c14b0f1..6d18dba94 100644 --- a/doc/cookbook/examples/.gitignore +++ b/doc/cookbook/examples/.gitignore @@ -5,3 +5,4 @@ /textures-sub-texture /layouts-stacking /layouts-stacking-diff-sized-actors +/events-mouse-scroll diff --git a/doc/cookbook/examples/Makefile.am b/doc/cookbook/examples/Makefile.am index 2369e7a0f..026df7d26 100644 --- a/doc/cookbook/examples/Makefile.am +++ b/doc/cookbook/examples/Makefile.am @@ -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 diff --git a/doc/cookbook/examples/events-mouse-scroll.c b/doc/cookbook/examples/events-mouse-scroll.c new file mode 100644 index 000000000..bce9c40b4 --- /dev/null +++ b/doc/cookbook/examples/events-mouse-scroll.c @@ -0,0 +1,126 @@ +#include + +#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; +} diff --git a/doc/cookbook/videos/events-mouse-scroll.ogv b/doc/cookbook/videos/events-mouse-scroll.ogv new file mode 100644 index 000000000..42f42d2ae Binary files /dev/null and b/doc/cookbook/videos/events-mouse-scroll.ogv differ