to continue the series about actions and constraints, here’s an example showing how to use the ClutterDragAction
to create a scrolling container that pans the contents using dragging.
we can start with the usual set up of the stage:
clutter_init (&argc, &argv); stage = clutter_stage_new (); clutter_stage_set_title (CLUTTER_STAGE (stage), "Scrolling"); clutter_actor_set_size (stage, 800, 600); g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); clutter_actor_show (stage);
then we set up the group that will contain the visible portion of the panning content:
scroll = clutter_group_new ();
clutter_container_add_actor (CLUTTER_CONTAINER (stage), scroll);
clutter_actor_set_size (scroll, RECT_WIDTH, RECT_HEIGHT);
constraint = clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)
clutter_actor_add_constraint (scroll, constraint);
constraint = clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)
clutter_actor_add_constraint (scroll, constraint);
clutter_actor_set_clip_to_allocation (scroll, TRUE);
the RECT_WIDTH
and RECT_HEIGHT
are the two size constants for the each “page” of the content. we use ClutterAlignConstraint
s to keep the group centered on the stage1.
the important bit is clutter_actor_set_clip_to_allocation()
which will use (and track) the actor’s allocation as the clipping area.
viewport = clutter_box_new (clutter_box_layout_new ());
clutter_container_add_actor (CLUTTER_CONTAINER (scroll), viewport);
for (i = 0; i < N_RECTS; i++)
{
ClutterColor color;
clutter_color_from_string (&color, rect_color[i]);
rectangle[i] = clutter_rectangle_new_with_color (&color);
clutter_container_add_actor (CLUTTER_CONTAINER (viewport), rectangle[i]);
clutter_actor_set_size (rectangle[i], RECT_WIDTH, RECT_HEIGHT);
}
this is the content area, composed of a ClutterBox
using a ClutterBoxLayout
to lay out a list of rectangles.
let’s start the main loop, and the result should look like this:

now we need to enable the panning logic. to do so, we use the ClutterDragAction
on the viewport actor, and we constrain it to the horizontal axis:
action = clutter_drag_action_new ();
clutter_actor_add_action (viewport, action);
clutter_drag_action_set_drag_axis (CLUTTER_DRAG_ACTION (action), CLUTTER_DRAG_X_AXIS);
clutter_actor_set_reactive (viewport, TRUE);
and that’s it:

well, except that it isn’t really done; the behaviour at the edges of the viewport can lead to simply have no way to pan back, and the whole thing is a bit static. we should add some “kinetic-style” animation depending on the position of the content at the end of the panning action. to do so, we can use the ClutterDragAction::drag-end
signal:
g_signal_connect (action, "drag-end", G_CALLBACK (on_drag_end), NULL);
and have our logic there; first, the edges:
float viewport_x = clutter_actor_get_x (viewport); /* check if we're at the viewport edges */ if (viewport_x > 0) { clutter_actor_animate (viewport, CLUTTER_EASE_OUT_BOUNCE, 250, "x", 0.0, NULL); return; } if (viewport_x < (-1.0f * (RECT_WIDTH * (N_RECTS - 1)))) { clutter_actor_animate (viewport, CLUTTER_EASE_OUT_BOUNCE, 250, "x", (-1.0f * (RECT_WIDTH * (N_RECTS - 1))), NULL); return; }
then the content:
float offset_x; int child_visible; /* animate the viewport to fully show the child once we pass * a certain threshold with the dragging action */ offset_x = fabsf (viewport_x) / RECT_WIDTH + 0.5f; if (offset_x > (RECT_WIDTH * 0.33)) child_visible = (int) offset_x + 1; else child_visible = (int) offset_x; /* sanity check on the children number */ child_visible = CLAMP (child_visible, 0, N_RECTS); clutter_actor_animate (viewport, CLUTTER_EASE_OUT_QUAD, 250, "x", (-1.0f * RECT_WIDTH * child_visible), NULL);
and here‘s the result:
that wasn’t very hard, was it?
I’m going to submit this as a recipe for the Clutter Cookbook; the reference source code is available here.
- this will work even if you set the stage as user resizable, as the constraints are recomputed at each allocation cycle [↩]