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

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.

Constraints

last time, I described the Effects in Clutter as anything that should affect the way an actor paints itself without sub-classing.

this kind of modification of the behaviour of an Actor can also be applied to other areas, and leads to defining three different kinds of modifiers:

  • Effects, which modify the way an actor paints itself
  • Actions, which modify the way an actor responds to user events
  • Constraints, which modify the way an actor is positioned or sized

being capable of being dragged using the pointer can be defined as an action: it changes the way an Actor responds to user events (button press, motion, button release). in the same way, update the X position using the X position of another actor is a constraint placed on an Actor.

Clutter already has signals, properties and virtual functions for dealing with these modifiers, and they can be seen as the building blocks. What’s missing is a set of classes that wrap those building blocks into something that leads to run-time composition of effects, actions and constraints on top of an existing Actor class.

last week I started working on the implementation of these modifiers, and now I’m pretty close to a finalized API for Clutter 1.4. the usage of an action is pretty simple:

  ClutterAction *action = clutter_drag_action_new ();
  g_signal_connect (action, "drag-motion",
                    G_CALLBACK (on_drag_motion),
                    NULL);

  clutter_actor_add_action (some_actor, action)
  clutter_actor_set_reactive (actor, TRUE);

the simplest implementation of a ClutterDragAction::drag-motion signal handler is:

  static void
  on_drag_motion (ClutterDragAction *action,
                  ClutterActor      *actor,
                  float              delta_x,
                  float              delta_y)
  {
    clutter_actor_move_by (actor, delta_x, delta_y);
  }

there’s no fundamental difference for the constraints API; for instance, this positions an actor in the middle of another:

  ClutterConstraint *constraint;

  constraint = clutter_align_constraint_new (parent, CLUTTER_ALIGN_X_AXIS, 0.5);
  clutter_actor_add_constraint (some_actor, constraint);

  constraint = clutter_align_constraint_new (parent, CLUTTER_ALIGN_Y_AXIS, 0.5);
  clutter_actor_add_constraint (some_actor, constraint);

it’s important to note that parent in this case can be any actor, and not necessarily the scene graph parent of some_actor.

this example binds the X coordinate of an actor to the X coordinate of another one, while keeping it Y aligned at the center of its parent:

  ClutterConstraint *constraint;

  constraint = clutter_bind_constraint_new (source, CLUTTER_BIND_X, 0.0);
  clutter_actor_add_constraint_with_name (some_actor, "x-bind", source);

  constraint = clutter_align_constraint_new (parent, CLUTTER_ALIGN_Y_AXIS, 0.5);
  clutter_actor_add_constraint_with_name (some_actor, "y-bind", constraint);

Update: I’ve updated the example to use the newly added add_constraint_with_name() instead of set_name()+add_constraint(); I also changed the constraint names.

now, I’ve added a call to clutter_actor_meta_set_name() in the mix because I might decide to animate the offset property of the ClutterBindConstraint using a specific syntax for ClutterAnimation ((the change is contained in ClutterActor so I’m not over-complicating the parsing code in the animation class itself; which also means that ClutterAnimator and the upcoming ClutterState classes will be able to use the same syntax to address action, effect and constraint properties)):

  float new_offset = clutter_actor_get_width (source) + h_padding;
  clutter_actor_animate (some_actor, CLUTTER_EASE_OUT_CUBIC, 500,
                         "@constraints.x-bind.offset", new_offset,
                         "opacity", 128,
                         NULL);

the new syntax is still type checked, so you’ll get warnings if you’re using the wrong type or the wrong interval ((I’m still considering using a dotted notation; I might switch to a colon notation, or a slash notation. and yes: I know that it looks like the syntax for key access in CoreAnimation; it turns out that there aren’t many new ways to access an item in a path)).

caveat: obviously, a constraint only applies to an actor that is in a fixed positioning layout manager.

the new classes and API are available in the wip/actor-actions branch here and wip/constraints branch here; there are also a couple of interactive tests that show you how actions and constraints can be used with existing actors. if all goes according to plan, I’ll merge them by the end of the week — following Frederic’s blog post, I want to make a 1.3 snapshot release in time for GNOME 2.31.2.

Control

three years ago Neil and I wrote the Model API, to be included in Clutter. We tried encode in the design what we learned from the experience with GtkTreeModel, and while it could be said that there are shortcomings (a base class instead of an interface, no bulk operations, some corner cases in the iterators API) I think we managed well enough not to repeat the same issues (boxed types, volatile iterators, and conflating a Tree and a List API into one) on top of those.

still, we made what I now think is the same design mistake all over again: we tried to provide a way to write MVC applications with Clutter, and we ended up collapsing the model with the controller — that is: we added a new data storage class that notifies you when something changed inside it ((gtk+, in a way, allows you to bolt a controller on top< of a data storage, but you still need the data storage to be a class inside the type system otherwise you won’t be able to implement the GtkTreeModel interface)).

I now think it’s a mistake trying to conflate the data storage with the actual object notifying the views about changes: the controller should just be notified by the model and notify the views

GLib already provides a lot of data storage types: GArray, GPtrArray, GHashTable, etc. — it would seem sensible to just use them and just wrap the insertion and removal functions instead of:

  • create a GObject wrapper around a data structure;
  • wrap insertion, removal and iteration operations;
  • add specialized code and signals to handle the changes and notify the views;

on top of these, if you want to write a generic storage you’ll have to:

  • make every entry point a virtual function, to allow sub-classes overriding or providing a default behaviour;
  • provide a generic iterator API;
  • wrap everything with GValues;

in essence, the complexity of the storage quickly balloons out of control — and all because you wanted to notify another object that you added a new item inside a GPtrArray ((and no: I don’t think that keeping the complexity under check by losing generality is a good trade-off; it’s just going to bite you in the rear later on)).

CONTROL! - by fatbwoy, CC-by-nd-2.0

wouldn’t it be good to have the “notify somebody that $SOMETHING changed inside a data storage” thing nailed down with a generic API?

I did think it would be good, so I spent some free cycles last week to implement a generic Controller — the thing that notifies a view that something changed. it requires minimal additions to already existing data storage types provided by GLib — to the point that you don’t really need a GObject wrapper around your model altogether.

the overall design is explained on the GNOME wiki, so I won’t rewrite it down here; and yes: if, by a cursory glance, it looks a lot like a certain platform’s API it’s because I think it’s a good representation of a correct approach.

the code lives in the GNOME Git repository; it currently has a stub LGPLv2.1 license because I think it should be seen as a 1700 lines patch to GObject/GIO under version control, and not as a stand-alone project ((it also gave me the chance to play with non-recursive autotools layouts, but that was just a side-effect)).

there are some things left to do, notably a GObjectController which I think I described to a colleague as GObject::notify on PCP; for that to happen, though, I’ll need some changes in GObject itself.

Going to GUADEC

both my talk proposals made the cut for GUADEC 2010 ((for total disclosure, yes: I was in the papers committee, just like I’ve been in 2008 and 2009; no: I did not review my proposals, for obvious reasons; and let me tell you: the competition was fierce — at least 75% of the ~90 submissions were so good that I honestly thought that I wouldn’t have a chance to get a talk in, let alone two)).

my first submission is a GUADEC tradition since 2008 : the state of the union talk for Clutter. the talk last year at GCDS got great feedback, and the modified version at LCA did too, which means that I’ll have to work really hard to improve it. luckily, with all the good stuff going in Clutter 1.4 it won’t be hard. :-)

the second submission is called So you think you can release and it is about project and release management, especially for libraries approaching the much dreaded “1.0” release. this talk has a funny background; I actually thought it up, and started writing as a wiki page, around 3am in my hotel room at LCA 2010, the day before giving my Clutter talk there. I literally woke up with the slides in my head, and when I started writing them down it felt like transcribing a talk that I had already given.

so, long story short:

obviously, though, I’m not going to GUADEC just because I have to give a talk: all the accepted submissions are so good that it’s definitely a conference worth attending if you’re interested not just in the GNOME project, but also in the Linux-based desktop and mobile environments — plus I get to see, and listen to, and talk to people that I only meet once a year or so, and meet the new faces in the GNOME community ((plus, this is going to be my last GUADEC as a non-member of the Old Farts club :-) )).

Apology Song

this much is clear: I suck at the whole blogging thing. I was planning on writing up what awesome new features we were planning to add in Clutter 1.2 and before I knew it I was releasing 1.2.0 — all without actually writing a single blog post. no wonder that people drops by on the #clutter IRC channel and doesn’t know about Layout Managers, or the new Animator class.

anyway, for Clutter 1.4 I’m not going to repeat this mistake, so I’m going to talk about new features as they land in the Git repository.

let’s take this commit from Neil: cogl: Support retained paths. this commit not only adds the ability to keep around a path defined using the Cogl API: it’s a step in the direction of making Clutter fully retained ((we actually are mostly retained; we have a couple of areas where we don’t but those are getting fixed in a backward compatible way wherever they might involve third party code)), even at its lowest levels, so that we can apply some heuristics to optimize the painting of the scene graph. pretty exciting stuff, especially for our friends on embedded platforms or on underpowered GPUs.

on the high-level side, the ebassi/wip/actor-effects branch contains some new API and classes to attach an effect to an actor; by “effect” I mean anything that should affect the way an actor paints itself — similarly to what you might want to do in a handler for the paint signal, but cleaner. for instance, we can create an off-screen buffer and render an actor (and all of its contents) to that, and then apply a fragment shader — like this desaturation effect. this should deprecate the clunky ClutterShader API, and make applying and animating a fragment shader easy peasy.

we’re planning more, much more for the 1.4 release, though — including a new Clutter website ((if you jump in #clutter I can give you a sneak peek to the wiki ;-) )).

Remember to breathe

summit: my second GNOME Summit was a great experience. being a small, hack-oriented conference makes it possible to have a lot of work done — or discussed; I must thank everyone that helped organize it: it was great. I also have to thank Paul for letting me attend it even with a looming deadline at work. :-)

the Clutter 1.2 requests session was pretty good: I got nice feedback from the current users of Clutter in the GNOME platform, both for the planned features and for what is needed. I also was able to talk about GTK+ 3.0 planning outside of the IRC meetings — which started again, so make sure to watch the Wiki page and attend every other Tuesday.

clutter: most of the Clutter discussion has already been summed up by Jason. I’m going to start releasing snapshots of the 1.1 development cycle by the end of this week, to let people test drive the new APIs; feedback is much appreciated.

autotools: I had to catch a red-eye flight to go back from Boston, and I had only 22% of battery left on my laptop, so I decided to go easy on the plane hacking side of things; I just got around to split out the GLib marshallers and enumeration GType generation rules that everyone is copying between projects; those rules now live in two files that you can copy and include at your heart’s content. they work like this:

# glib-genmarshal rules
glib_marshal_list = $(srcdir)/clutter-marshal.list
glib_marshal_prefix = clutter_marshal
include $(top_srcdir)/build/autotools/Makefile.am.marshal

# glib-mkenums rules
glib_enum_h = clutter-enum-types.h
glib_enum_c = clutter-enum-types.c
glib_enum_headers = $(source_h)
include $(top_srcdir)/build/autotools/Makefile.am.enums

and they will also handle the clean and dist rules for you. it would be good to have something like this in gnome-common as well.

json-glib: a couple of weeks ago I released version 0.8; it contains a “fix” for the fact that integers in JSON are defined (or, well, undefined) as machine integers; this allowed braindamaged web services to overflow 32 bits and expect that stuff would not break. nice one. unfortunately, C is a little bit more concerned — and I’d never thought I’d say this — about types and their sizes, so suddenly stuff would start to break; I automatically promoted every integer to 64 bits internally, so this should give us a little bit of wiggle room, at least for the time being. but seriously: incremental integers for unique id without a known length limit? not good design.

Clutter Q&A

since there are free slots here at GCDS I was wondering if somebody wanted to have a sit down and ask questions about Clutter; bugs and feature requests for 1.2 are also welcomed. just send me an email at: ebassi at gnome dot org.

A Year of Clutter

so, GUADEC/GCDS is now halfway through, and it’s been a pretty cool conference so far. obviously, lots of talks and loads of people to meet and to talk to.

Rob did a roundup of the talks that the Intel/Moblin contingent has done and will do here.

yesterday, like Josh and Rob ((also known as “the yummy Rob Bradford”)), I had my talk on a technology used by Moblin and that can be (or are already) shared with the GNOME Mobile and Desktop platforms.

I talked about Clutter and the 1.0 release; what does it mean, what will be the future direction and some of the highlights of the Clutter library in its current state. instead of using my 30 minutes to do a big talk, I decided to split them into four, five minutes lightning talks, plus an introduction and a conclusion (still five minutes each). I think it worked out pretty well, given the feedback, and I had much more fun while writing it and while delivering it. I’ll put it online on the moblin.org website as soon as the network connection I have access to gets more reliable.

other talks worth of mention: obviously, the gnome-shell and the zeitgest ones, and alexl’s client-side-windows talk. great work by everyone involved — you are all my heroes.

The Wanting Comes in Waves

so, we’re finally free to show what we’ve been doing for the past six months:

it’s been a great ride — and it’s just getting started. we’ve been pushing Clutter forward and center of an entire platform user experience, and it was up to to the task to a degree that excited me, and made me incredibly proud.

the Moblin 2.0 UI is also one of the reasons we delayed the 1.0 release — the other being that we wanted to be confident in the API, since we’re going to be committing to it for the next two to three years. the wait is almost over: we’re planning a 1.0 release of Clutter by the end of May ((hopefully, it’ll all be fixed by that time :-) )). prepare yourself to have muchy more fun with Clutter!

The Hazards of Love

while I’m rebuilding my jhbuild setup in order to roll out gnome-utils 2.27.1 ((hopefully before the cut-off time for the 2.27.1 release)) I started pondering on a couple of questions:

  1. why are we still shipping the dictionary applet?
  2. and, more importantly: why are we still shipping a DICT protocol client?

okay, I wrote them both — and I was just trying to save them from the horrid death-by-code-rotting fate they were condemned to — but at the time I did not stop and consider why ((the software engineering and programming challenge were, foolishly, all I was interested in; I was young and eager to prove myself)).

who in their right mind would still use a DICT client — when it’s not perfectly clear ((go on, look at the two dictionary providers we install with gnome-dictionary, and consider that those two have been the same since 2.12, that is seven development cycles ago)) that only a few DICT servers are alive enough to be useful, and mostly for the english-speaking only world?

so, I ask the interwebs: what kind of electronic dictionary do you commonly use when you need one? do you use Wiktionary and Wikipedia? do you use another web service ((with a public API, hopefully))? or do you use something local, with files you update semi-regularly? what do you use when you need to translate something?

I’d like to address this issue during the 2.27 cycle because I don’t want to let gnome-dictionary end up like gfloppy — a survivor of a different era that only recently we were able to just remove for something far more powerful and useful.