GNOME Bugzilla – Bug 444659
Add GtkTimeline to ease animations development
Last modified: 2014-08-30 06:35:04 UTC
Currently one has to develop animations by hand with g_timeout_add() and controlling the "gtk-enable-animations" setting. This is ok for simple animations, but some sugar could be added on top of this to ease more complex animations and make this also available to GTK+ and its users. The object I'm proposing is inspired on the ClutterTimeline and QTimeline APIs, with it one can start, pause and rewind an animation, besides other things like changing direction, make it loop or set a function to allow non-linear progress through the timeline (think for example in the gnome-panel hide/show animation). It also integrates with the "gtk-enable-animations" setting by just jumping to the last frame [1]. Being GtkTimeline available, for example GTK engines and composited metacity could make more fluid animations and keep more control over them. I'm attaching gtktimeline.[ch] and some glue, it's just missing some more docs love. [1] There are some possible disadvantages here, such as chaining to another timeline when one is finished, but it should be enough for most expectations.
Created attachment 89459 [details] gtktimeline.c
Created attachment 89460 [details] gtktimeline.h
Created attachment 89462 [details] [review] glue patch
looks very interesting (also saw the patch attached to #328090). one thing that we noticed in clutter is that when you start adding many timelines, which are usually running at he same priority, you might end up with starvation of the time slice in the main loop with a consequent drop in frames. this happens only when the number of animated objects is high and the timelines are not shared between them and run for a "long" time or are looping. this might not be a problem for gtk+, though. a possible solution, which we are probing for clutter, is having our own scheduler object and having all timeline objects running from a single or a few timeout sources.
Oh, yeah... that's a possibility, it'd be interesting to get some numbers for an increasing number of timelines. Even if something like this is included in GTK+, I'd expect it's use to be sparse enough inside GTK+, but we clearly need to take this into account for other possible use cases.
If we run into performance problems with lots of timelines, maybe it is time to look at bug 143061
Can you also attach the source of the simple demos that you had on your blog ?
Some comments (the usual suspects when people do their first animations): You most likely want to trigger all updates at the same time. So you want a global timeout handler that updates all timelines. "global" in the Gtk context probably means per-screen or per-toplevel. This avoids weird behaviour when exhausting the CPU. (like when having 3 clearlooks progressbars and having their animation update in turn). You don't want to set a framerate per timeline. Ideally you don't want to set a framerate at all. You want "as fast as possible". This takes care of "dropping frames" on CPU starvation automatically. This usually means using an idle function for the update. You can set a maximum framerate (25 or 40ms depending on the type of animations you do) to avoid eating laptop battery. If you want to refer to discrete units inside a timeline, use something like milliseconds, not frames. It is easier to understand and gets rid of the idea that every frame must be shown seperately. Another thing is that the 2 points above easily allow setting the speed of animations. This is not usually an important thing, but can be an important feature for testsuites or slow-motion demos. Not sure about a11y. Another thing people do wrong that is not a problem here but important nonetheless: never compute the current state with gettimeofday(). Use a function on the timeline for this. Often animations compute the current state in the expose callback. I bet you can imagine how this looks when only half of the screen gets redrawn. The last thing of course is that animations should be part of the theme, not part of the widget. You may want to fade the states in the path bar instead of moving it or have a sinus-like movement. (Yes I know, this requires a better themeing framework.)
Created attachment 89477 [details] timeline demo
I wanted to add a little tiny bit of knowledge to this conversation, just to get people thinking. Vista (Windows, you know, that other OS) has this now in the form of WPF. They also now have an X-like server architecture, where client apps connect to and draw upon. The key thing is the clients don't draw animations. They upload timelines and vector/graphics data to the server which draws animations. After the client has uploaded the animation, it is done. It is no longer involved except to be notified periodically how the animation is progressing, or to alter or cancel it. This works with remote desktop also. The animation is uploaded to the client connecting to remote desktop, and the animation happens on the client side. The animation does not cause any constant bandwidth usage. Now, I only bring this up just so that it's kept in mind: when considering designing a canvas and timelines, it might be beneficial to have the idea that those timelines and vectors could be hoisted out of the client application and placed directly on the server. Maybe someday we'll have this ability on X. Maybe we'll want to use Gtk animations on Vista (and have to hoist them out of Gtk into the Vista animation thing.) Just something to keep in mind.
Seems almost like it should be GTimeline and part of the main loop API? (with a hook in GTK to set the xsetting) Specifically would be useful for canvases that don't depend on gtk ;-)
A couple of comments: I agree with Havoc about putting this at the glib level. It's unfortunate that our configuration system is broken, but this shouldn't be in GTK+ just to be able to access gtk-enable-animations. Looking at both QTimeLine and ClutterTimeline APIs I'd rather go with QTimeLine approach of the duration in seconds being the basic value set by the programmer to control the timeline. IMHO the ideal behavior would be being able to say something like "run this timeline for N seconds giving me as much updates as possible", with the option to limit the number updates per second (to save power consumption or scenarios where more than some number of updates per second simply doesn't make sense). One tricky thing I see here is how to ensure a timeline runs for a determinate amount of time if the update function goes in an idle handler and you avoid checking the current time on each update, as Benjamin suggests. Benjamin?
For a start: If you haven't, please have a look at applications that deal primarily with time-based stuff and ideally write code for it that really makes use of it. Ideally look at some heavy games like Unreal (the Unrealscript part has source available) or probably Quake. Coding Unreal (I once coded a mod for my brother) really changed how I handle the concept of time in my applications. To me, both QTimeLine and ClutterTimeline look like simple "I need to animate one value" APIs, which are fine if you really just want to animate one thing in your application, but will probably cause issues when there are 100 actors. ("Actor" is the term used by UnrealScript for objects that act over time.) I'll try to summarize the general behaviour of the Unreal engine now. First, please decouple time from the clock in your mind. The value that g_get_current_time() returns is totally irrelevant. Time is global. It advances in concrete steps. This is done by a global instance. It advances time by an amount (a float value) it determines itself. There is no real-world equivalent to describe the units for this time. It is modeled after seconds in Unreal, but it doesn't have to. It just advances. Whenever time advances, a function is called for every Actor with this prototype: void tick (float deltaTime) What to do in this callback is totally up to the actor. So how would I implement this in glib? I'd have a GTimeManager object (probably needs a smarter name) that manages time. It can have some properties (maximum-framerate, speed, priority, whatever). You can advance time on it. Or you can attach it to a main loop to make it do this automatically. The time manager also emits one signal: tick (float deltaTime). How would I implement this in gtk? I'd have either a global Gtk time manager or (more likely) one time manager per settings object that is attached to the main loop. Whenever I want to animate something, I'd basically do a g_signal_connect (manager, "tick", my_callback, my_data) and do my stuff in that callback. When I'm done with the animation, I g_signal_handler_disconnect() it. I haven't really thought about how to expose this externally or internally and if it makes sense to have some objects that manage animations of widget properties or if everything should just g_signal_connect (). As a closing mark, I think we should really get together at Guadec and do a BOF and have people explain their views on how this is all supposed to work. Another thing that should be really interesting is getting people with knowledge about animations (game programmers?) to tell their views. Does anyone know someone he could poke about joining this discussion?
Being a former games programmer and a part-time Flash programmer I think I can contribute to the discussion: Generally a timeline consists of several objects: 1. The time manager that lives in the idle loop and tries to get called as often as possible and update attached animations 2. The animations that are attached to the time manager 2.1. The actors that are part of the animation 2.2. The callback that does the job of altering the actor properties over time This is true for almost all situations where the animation is time-based rather than frame-based and this is the case here. I think the most useful approach would be to make the time manager aware of the animations. From the animator's POV it's more useful for the callback to give you the fraction of the duration rather than an absolute time value (or time delta). This way you get 0.0 when the animation starts, 0.5 when it's half-done and 1.0 when it's over, no matter what duration was set. This in turn makes it possible to create external "animator" objects that connect themselves as callbacks and do the animating: x = new gtkAnim.linearAnimator(myWidget, [{'attr': 'width', 'start_value': 10, 'end_value': 30}, {'attr': 'height', 'start_value': 10, 'end_value': 20}], duration = 2.0) y = new gtkAnim.sineAnimator(myOtherWidget, [{'attr': 'height', 'start_value': 10, 'end_value': 20}], duration = 5.0) This gives you the possibility to attach any animation to any object without the need of listening to tick signals, keeping track of current time versus duration etc.
To add more thoughts: Time manager should at least guarantee that each animation callbach gets the "1.0" notification so it knows the animation is done and a chained animation can start if applicable. The "done" signal does not have to be accurate if there is not enough idle processing power to do a smooth transition but it must eventually arrive.
(In reply to comment #11) > Seems almost like it should be GTimeline and part of the main loop API? (with a > hook in GTK to set the xsetting) I must admit this was my initial reaction to but I reconsidered after realizing that it means it would either have to live in libgobject or be made a non-object. Given that it's a fairly small object I'm not sure there is enough benefit to have it in GLib to loose out on keeping it an object with signals. > Specifically would be useful for canvases that don't depend on gtk ;-) Should be code that is fairly safe to copy & paste though.
Created attachment 94384 [details] updated gtktimeline.c I'm attaching a new iteration on the code, the timeline is now time-bound instead of frame-bound, it uses internally a GTimer to calculate the ellapsed time and pass the progress based on the ellapsed time since the animation began and the total time it's told to long. Once the animation has run during (or longer than) the time specified, a progress of 1.0 is sent to the "frame" signal and the animation is finished.
Created attachment 94385 [details] updated gtktimeline.h updated header, with s/n_frames/duration/
(In reply to comment #16) > (In reply to comment #11) > > Specifically would be useful for canvases that don't depend on gtk ;-) > Should be code that is fairly safe to copy & paste though. Please, no more libegg copypasting madness ;)
(In reply to comment #19) > (In reply to comment #16) > > (In reply to comment #11) > > > Specifically would be useful for canvases that don't depend on gtk ;-) > > Should be code that is fairly safe to copy & paste though. > > Please, no more libegg copypasting madness ;) This is quite different from libegg as this would be one canvas not depending on GTK+ making use of it rather than all applications using GTK+ copy&pasting code from libegg.
(In reply to comment #20) > This is quite different from libegg as this would be one canvas not depending > on GTK+ making use of it rather than all applications using GTK+ copy&pasting > code from libegg. For a second I was afraid you were suggesting making the same for timelines :) (In reply to comment #17) > I'm attaching a new iteration on the code, the timeline is now time-bound > instead of frame-bound, it uses internally a GTimer to calculate the ellapsed > time and pass the progress based on the ellapsed time since the animation began > and the total time it's told to long. I like the latest version very much.
To add the fun part, see the following for a number of ready tweening algorithms (written in ActionScript but should be easily portable to GTK and implementable as in comment #14): http://www.senocular.com/flash/actionscript.php?file=ActionScript_2.0/com/senocular/Ease.as
Created attachment 95196 [details] update Some fixes on gtktimeline implementation * not create timer always on start function * stop timer on pause function * stop timer in rewind function if not have a idle function running * stop timer on finish
I wasn't able to compile gtktimeline, gcc was complaining about some missing types (GtkTimelineProgressType for instance). Could it be you forgot to update the GTK+ glue patch?
Did you apply both the the "glue patch" as well? You need to apply, "glue patch" and then copy in gtktimeline.h (updated gtktimeline.h) and gtktimeline.c (update). I haven't tried it with latest version of GTK+ trunk though.
Created attachment 96500 [details] [review] updated glue Sorry about that
Created attachment 107173 [details] [review] [PATCH] GtkTimeline this is a porting of the ClutterTimeline for GTK+. actually, it's even more than bleeding edge, as it has the markers API which hasn't landed yet in Clutter trunk. this timeline implementation is frame based, even though accessible with duration in milliseconds; this proved to be a nice concept in Clutter and allowed more flexibility than a millisecond-only timeline. the Clutter timeline might drop frames, in case the animation is unable to keep up; this leads to more predictable animations and reduces the possibility of a blocked UI. like the previous implementation, the Clutter timeline has a direction and a delay. the markers API allows attaching a string identifier to a specific frame or time, thus allowing the creationg of trees of timelines - like ClutterScore in Clutter - so that chained effects can be handled in a fairly natural way. the API is fully documented; I can work on adding test unit as well.
by the way: what's missing is the ClutterTimeoutPool which I mentioned in comment 4. at the time I'm writing this comment, the TimeoutPool has proven to be a nice way to keep the timelines in check, and would be fairly easy to add it to GLib as well.
Created attachment 107175 [details] [review] [PATCH] GtkTimeline there was a spurious chunck adding documentation to gtkcontainer.c. this attachment removes it.
Created attachment 107211 [details] [review] [PATCH] GtkTimeline cleaned up patch, with Since tags and fixed spacing.
Emmanuele: How does this implementation behave when a named frame is skipped? Does it guarantee signaling even if no rendering happens? (Especially crucial for "finished" callbacks) Can it be easily used for interpolations (where fraction of duration is needed instead of frames/time passed)? Sorry for asking but I'm at the office and can't read the whole patch.
(In reply to comment #31) > Emmanuele: > > How does this implementation behave when a named frame is skipped? Does it > guarantee signaling even if no rendering happens? (Especially crucial for > "finished" callbacks) the ::completed signal is always guaranteed to be emitted even if the last frame has been dropped; the ::marker-reached signal is also guaranteed to be emitted. the ::new-frame signal will not be emitted for dropped frames - which is the whole point of dropping frames to avoid a slow animation blocking the whole UI. > Can it be easily used for interpolations (where fraction of duration is needed > instead of frames/time passed)? yes, there's API to get the progress in the [0.0, 1.0] interval. you can also retrieve the time elapsed since the last frame (and the eventual number of dropped frames); the markers can be set to use frames or milliseconds.
Hmm, I sort of miss the point where a frame based approach would be better than a progress based one, could you provide some test cases where is that approach more suitable? With the progress based approach, you can do interesting things like assigning non-linear functions to calculate the progress, something which seems to be totally left out to the users of your api. Also, having the progress being passed directly in the ::new-frame signal can be used more naturally when passing values to functions, etc... Well, you have the gtk_time_line_get_progress() function, but having the frame number in that signal seems more like an anecdotal value to me. The markers idea seems cool though :)
Right idea, bad implementation. Usually you want to modify multiple object properties on an animation step, like x-position, y-position, translucency, scale. The suggested API allows manipulation of only one property for each callback. This is fine for languages with lambda expressions, like Python. This is wrong for more conservative languages like C or C++. So I'd suggest a different API: class GtkAnimation { /** * Starts the animation. Progress is reset to zero and notify::progress * signals are emitted regularly. The is_running property returns true. */ void start(); /** * Stops the animation. The is_running property is reset to false. * One last notify::progress signal is emitted to allow the client * to finish its animation. */ void stop(); /** * Current progress of the animation as value in seconds. * Monitor this property (notify::progress signal) to get notified * when to render the next frame. */ property double progress { get; set; } /** * Returns true, when the animation is running. * Running animations regularly emit notify::progress signals. */ property boolean is_running { get; } /** * Preferred frame rate of the animation in frames per second. */ property double frame_rate { get; set; } } The fancy transformations demonstrated by the suggested GtkTimeline object would be handled by separate GtkTransformation objects instead: interface GtkTransformation { double get_value(double progress); } Examples would be: class GtkLinearTransformation implements GtkTransformation { GtkLinearTransformation(double offset, double duration) { __offset = offset; __duration = duration; } double get_value(double progress) { double value = (progress - __offset) / duration; return value - floor(value); } } Transformation objects would be responsible by themself to map the progress value into a reasonable value range. This design allows to apply multiple transformations to one single animation. Question is how to stop animations. Once possiblitly is to let the user stop it from the notify::progress handler. Another one is to add a duration property to the GtkAnimation class.
(In reply to comment #34) > Right idea, bad implementation. a timeline is usually one part of an animation framework, not the only part. > Usually you want to modify multiple object properties on an animation step, > like x-position, y-position, translucency, scale. The suggested API allows > manipulation of only one property for each callback. where did you infer this? you can modify multiple properties from each ::new-frame callback - which is exactly like canvases like clutter and pigment implement their animation frameworks. the simplest animation can be done with a timeline running for 6 seconds at 60 frames per second static void on_new_frame (ClutterTimeline *timeline, guint frame_number, gpointer user_data) { FooWidget *foo = user_data; /* this maps to one degree given the number of frames in the timeline, * but it can also be computed */ gdouble angle = (gdouble) frame_number; /* this is the normalized value, between [0., 1.] */ gdouble progress = clutter_timeline_get_progress (timeline); foo_widget_rotate (foo, angle); foo_widget_set_opacity (foo, progress); } this will rotate FooWidget of 360 degrees in the given time, and set its opacity at the same time. animation frameworks, as I said, are developed on top of this, to make it easier to map to properties; something like: GtkTimeline * gtk_widget_tween (GtkWidget *widget, guint duration_msecs, GtkProgressFunc progress_func, const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED; where GtkProgressFunc is defined as: gdouble (* GtkProgressFunc) (GtkTimeline *timeline, gpointer user_data); and called like: gtk_widget_tween (foo, 2000, GTK_LINEAR_PROGRESS, /* property, min, max */ "rotation", 0, 360, "opacity", 0.0, 1.0, NULL); could even be enough as a first, rough pass as a simple, one API call animation framework. or you could take the clutter approach, and have specialized and stackable classes that are applied on top of widgets. but this bug is not about "what API the animation framework should have": it's "add timelines to ease the animations development".
Hmm... just seeing that GtkAnimation mostly is covered by g_timeout_add already. So instead of GtkAnimation a simple g_timeout_get_progress() function would be suffiction: /** * g_timeout_get_progress: * * @source_id: the source id of the timeout * returns: the number of seconds since installing the timeout. */ gdouble g_timeout_get_progress(guint source_id);
(In reply to comment #35) > (In reply to comment #34) > > Right idea, bad implementation. > > a timeline is usually one part of an animation framework, not the only part. > > > Usually you want to modify multiple object properties on an animation step, > > like x-position, y-position, translucency, scale. The suggested API allows > > manipulation of only one property for each callback. > > where did you infer this? you can modify multiple properties from each > ::new-frame callback - which is exactly like canvases like clutter and pigment > implement their animation frameworks. I was repeatly told, that GtkAnimation is needed as replacement for g_timeout_add() due the transformation aspect: Transform seconds since start of animation into values using linear, radial, whatever transformations. The suggested GtkAnimation supports this for one single value, which makes it quite limited in use. I absolutely do not see the advantage of using GtkTimeline instead triggering redraws from some timeout callback, as long as GtkTimeline supports only one single transformation.
Don't know if that could be of any use, but I have started to work on a separate library (the PAF Animation Framework, see http://live.gnome.org/PAF). For now, I have only implemented the PafClock and PafTicker objects, but I think they provide functionalities similar to that of GtkTimeline. The code can be found on launchpad: https://launchpad.net/paf. A gtk-doc of the API is available at http://emont.org/paf/api/.
I just wanted to add some notes on the animation support and the roles of Timelines, now that we reviewed that part in Clutter for 1.0. for reference, see these two blog posts from Owen: http://blog.fishsoup.net/2009/05/28/frames-not-idles/ http://blog.fishsoup.net/2009/06/02/timing-frame-display/ and the source code for the Master Clock: http://git.clutter-project.org/cgit.cgi?url=clutter/tree/clutter/clutter-master-clock.c after a couple of years of using Timeout-like sources (with frame-drop support in case the source callback was taking too long) we found out that using the main loop from different timelines ended up conflicting with redraws/relayouts, event handling and other sources like video playback. for this reason, Clutter switched to a single main loop source, called the Master Clock; the clock works by checking if any stage (the top-level container) requires a redraw; if nothing does, the source skips the prepare and waits until asked again. if something does require a redraw, the source: 1. processes the events that have been queued 2. advances every timeline by the time elapsed since the last redraw 3. performs a size request/size allocation cycle, if required by any event handler or by any animation 4. redraws all the stages 5. waits for the vertical blanking of the display all the event handling for application code is done inside (1); all the animations are updated in (2), and thus Timelines are not Timeout sources anymore; the waiting in (5) is either done by the driver or, if the driver does not support automatic blocking during the buffer swapping phase, Clutter waits a pre-definite amount of millisecond depending on a default framerate. Clutter also allows to get notification between phases (2) and (3) with "repaint functions", which allow applications (and libraries) to inject code right before the stage is relayed out and repainted (we use these functions to perform asynchronous texture uploading after file loading and deconding done in a different thread). as a general issue, even before animations, I think gtk+ should move to a similar system; this would also allow integration with Clutter's own redraw cycle when we start embedding GTK+ widgets inside the scenegraph.
What's the status on this? I wanted to use a simple animation object to make transitions in gnome-settings-daemon better (bug 672186), but couldn't find anything in GTK+ itself. From having used it, I know that clutter has some API like this (ClutterTransition) even though I'm looking for something that's not tied to the redraw clock. Owen, Emmanuele?
I don't see GTK providing an animation or timeline API. And that's because I see GTK as a toolkit, not as an animation framework. I can see GTK using an animation framework (like the one in Clutter) in the future, but I definitely don't want to provide one. In particular, I don't think GTK is the right place to provide an API for your use case in bug 672186 where you don't want to hook into the restyle/relayout/redraw cycle of the toolkit.
I agree with Benjamin, and as I also explained in comment 39: a separate object for keeping the time without hooking it into the frame processing is pointless. for that to happen, we need Owen's work on the frame clock. providing an animation framework to programmatically add (unstylable) animations may well also be out of scope for GTK+; if we go for stylable animations, then the only way to provide those would be to use CSS transitions and animations (coupled with events/signals for transition-begin/-end), and not a full-fledged API with classes like ClutterTimeline and ClutterTransition.
frameclock is there now
reflecting comment 41