halting problem :: And I Believe California Succumbed to the Fault Line

:: ~3 min read

Clutter’s description, and I quote from the website, is:

an open source software library for creating fast, compelling, portable, and dynamic graphical user interfaces.

and yet, the API to build a user interface is static: you create the actor tree, you tell it how to paint its contents, and only then you animate it to create a compelling and dynamic result.

the ethos of an API should be to Do The Right Thing™ by default — and make it harder if not impossible to Do The Wrong Thing™ — so why is it that you have a simple function to make an actor jump at a given position, and you have a really complex function, full of side-effects and options, to make an actor tween between the current position and the desired new one?

Scumbag Steve meme - top caption says: "Promises dynamic user interfaces", bottom caption says: "makes everything static by default"

wouldn’t it be better, and even easier, if the simple function did the tweening, and if the complex function just went away because, well: who needs it, anyway?

so we need to identify properties that should be animated when changed, and we have to make every setter for that property perform a transition from the current value to the desired value, using a predefined duration (that can be changed), and a predefined easing mode (that can be changed). easy, right?

well, sort of. we already have all the pieces in place: we have the timeline, to define the duration and progress of a transition; we have the interval, to define the initial and final states, as well as the interpolation between the two; and we have the introspection capabilities of GObject to determine whether a property can be animated, and how. all we need to do is connect the dots inside ClutterActor. hence, the introduction of ClutterTransition and of the easing state. the former is a simplified version of ClutterAnimation, that ties a ClutterInterval directly into a ClutterTimeline to avoid signal emissions and weird memory management rules; the latter is a way of describing the duration, delay, and easing mode of all subsequent transitions. Clutter will manage everything behind the scenes for you, so you just need to tell it how long, and how paced, are your transitions.

so, in short, this call:


  clutter_actor_animate (actor, CLUTTER_EASE_OUT_CUBIC, 250,
                         "x", 100.0,
                         "y", 100.0,
                         "width", 200.0,
                         "height", 200.0,
                         NULL);

becomes:


  clutter_actor_save_easing_state (actor);
  clutter_actor_set_easing_mode (actor, CLUTTER_EASE_OUT_CUBIC);
  clutter_actor_set_easing_duration (actor, 250);
  clutter_actor_set_position (actor, 100, 100);
  clutter_actor_set_size (actor, 200, 200);
  clutter_actor_restore_easing_state (actor);

which does not seem much, but:

  • you get back all the type safety you need;
  • the code becomes easy to follow;
  • you can use the convenience API instead of a per-property set.

for instance, try throwing things like scaling or rotating:


  ClutterVertex vertex = CLUTTER_VERTEX_INIT (x, y, z);

  clutter_actor_animate (actor, CLUTTER_EASE_OUT_CUBIC, 250,
                         "rotation-angle-y", 360.0,
                         "fixed:rotation-center-y", &vertex,
                         "scale-x", 2.0,
                         "scale-y", 2.0,
                         "fixed:scale-gravity", CLUTTER_GRAVITY_CENTER,
                         NULL);

becomes the much more familiar:


  clutter_actor_save_easing_state (actor);
  clutter_actor_set_scale_with_gravity (actor, 2.0, 2.0, CLUTTER_GRAVITY_CENTER);
  clutter_actor_set_rotation (actor, CLUTTER_Y_AXIS, 360.0, x, y, z);
  clutter_actor_restore_easing_state (actor);

well, I cheated a bit: the easing mode of CLUTTER_EASE_OUT_CUBIC and the easing duration of 250 milliseconds are the default when creating a new easing state — but that’s just another benefit: you don’t need to specify an easing mode and a duration if you want to use the sensible defaults, whereas clutter_actor_animate() forces you to do that every time.

job done!

Darth Vader filling a jug with water filtered from the sea

we still don’t want existing Clutter applications to change behaviour and start animating everything just because you updated Clutter from 1.8 to 1.10; for this reason, all actors start off with an easing state with a duration of zero milliseconds — which means that all transitions happen immediately. at least, this will be true for the 1.x API series: for 2.x, the initial easing state will have a non-zero duration, which means that if you want to make an actor jump at a specified state then you’ll have to begin a zero-milliseconds duration.

well, kind of

Success Kid Meme image; top caption is: "Follows API ethos", bottom caption is: "Makes the World Better"

it’s important to make sure that every time you want to change a user interface you think of it as an animation; this API should immediately give you have a visual feedback of what that change is going to provide — without structuring your code around lots of variadic argument functions, with pretty loose type checking and “interesting” corner cases.

so, for the 1.10 cycle you should start opting in into this way of writing your application, and use easing states instead of using clutter_actor_animate().

context-switch wordpress old-blog

Follow me on Mastodon