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 stage ((this will work even if you set the stage as user resizable, as the constraints are recomputed at each allocation cycle)).
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.