Crash Years

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 ClutterAlignConstraints 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:

Panning Container

does not do much

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:

it's done! ship 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.

  1. this will work even if you set the stage as user resizable, as the constraints are recomputed at each allocation cycle []
This entry was posted in Uncategorized. Bookmark the permalink.

6 Responses to Crash Years

  1. Pingback: Tweets that mention ยป Crash Years Context Switch -- Topsy.com

  2. erlehmann says:

    Are you aware of the fact that your Video file is delivered with “Content-Type: audio/ogg” ?

  3. ebassi says:

    @erlehmann yes — blogo seems to set that automatically even for videos. that’s why there’s a link to download it

  4. erlehmann says:

    Iceweasel plays it anyway. Still, for video better use .ogv as extension.

  5. ebassi says:

    @erlehmann I would have used ogv — if blogo accepted it when uploading.

  6. erlehmann says:

    Hmmm, file a bug then ?

Comments are closed.