navigation ://

Learn more about this website

version 4.1



Animate your gtk engine theme

23 November 2005
There are some points you need to do to give your theme engine little bit more eye candy.
So lets start and take a look how we done this in clearlooks-cairo, what is in CVS today.

Some steps you need:
  1. prepare your development environment
  2. make animation optional depends on .gtkrc file in your theme.
  3. create some help functions needed by animation stuff
  4. code how animation should look like

1. Prepare your development environment

I use gedit for fast edition C code files and two gnome-terminal windows. One for build and compile code, another to make tests.
That how it looks: dev_env.png
So you can code in gedit, build you library in one of terminals, and test in other. For that you need to set something in your test-terminal:
$ cd gtk-engines/engines/clearlooks/.libs/
$ export LD_PRELOAD=./libclearlooks.so
$ twf

In that way you can test library without installing it in to the system.
TWF is a test application writen by Remenic.
For complete test you would need to run more that this one application, but for the start to take look how your coded animation looks its enough.

So, if you can get twf start in test-terminal without warnings about loading library, you can start code.

2. Make animation optional depend on theme file.

At start it is good to leave people possibility to dont use animation. We have some solution for this. We use
engine "clearlooks" { animation = TRUE }
code in theme file. For this you need some changes in your theme file parsing engine.
Take a look to this file: clearlooks_rc_style.c . You would need to add TOKEN_ANIMATION to let parser recognize it and case TOKEN_ANIMATION:
token = theme_parse_boolean (settings, scanner, &clearlooks_style->animation);
 break;

Take a look to actual files in clearlooks cvs, or to this patch for more informations: gtkrc_anima-0.1.patch.

3. Create some help functions needed by animation stuff

We moved all needed function to extra file called animation.c . You can just use this file in you engine, or even extend it for your needs.
Let me explain the main idea how it works.
Every time engine redraw some widget, which should be animated it checks if widget already known by animation system. If not it adds it to the table of widgets which need animation. Mostly animation start on some event, like toggle some checkbox, or something like this. In this case we have one way list (GSList) to hold all know widgets are set to signal.
So if you connect some widget animation start with signal, you can be sure animation starts if signal arrive.
In next step you just draw your widget, not depending on if animation is started or not. You just request some information about animation if needed, but you just draw your widget.
In this case you split way how engine works in two ways. One just draw your widget and look if it should add widget to some animations list. And another take care about information needed for animations what would be checked every X-time, and update this information if needed.
That have some special goals. Let say your engine is very busy at the moment, and you cant redraw your widgets such fast, like you need. But second way, do his animation information updates in other place, so it would calculate animation without looking if old animation is already done. In this case, it can happens that some animation steps would just skipped by drawing. With other words, if you define that checkbox animation should be done in 0.5 sek, it would be done in 0.5 sek and don't take anytime longer.

There are some information for all widget we should animate stored:
struct _Widget_Animation {
    gint8 max_frames;
    gint8 frame;
    gint8 loop;
};

Take a closer look:
  • loop - define if animation should be played only once, more than once, or never stop (like for progressbars). So if you set loop = 0 it would never stop, but if you set loop = 3, your widget animation would played 3x times.
  • frame is used for actual count of animation frame. It says widget draw on what place we are now with our animation.
  • max_frames define how match animation frames has current animation. If you have animation loop, at the end of current loop you need to reset animation to start position.

Let explain, how it works on checkboxes. If checkbox is clicked, it take 0.5 seconds to make check disappear slowly. We just set max_frames to 5, and loop to 1. So animation is take place only one time per click on widget.
So if signal arrive engine add widget with this animation information to hash table, and update frame value every 0.1 sek. It just count frame value down and call redraw for that widget in gtk-engine.
If frame reach 0, it looks to loop value if it should repeat animation once more. So it stops animation and remove widget from the hash table, because we set loop to 1.
As i sad, real draw of widget happens in other place. We don't draw widget in this step, we only calculate animation information and call gtk redraw event for actual widget if its needed.
Notice: all animation calculations starts at max_frames and count down every 0.1 sek until frame get value 0, than we look to loop value, and repeat animation starting on max_frames or remove widget from hash table. You can take closer look to it if you get animation.c file.

4. Code how animation should look like

Here comes real cairo code to show you how widget draw can be changed to do animation.
For checkboxes in clearlooks_style.c we have function called: static void draw_check (DRAW_ARGS)
Mostly on top of this function we do:
if(clearlooks_style->animation && GTK_IS_CHECK_BUTTON (widget) && !cl_async_animation_lookup((gconstpointer)widget)  && !g_slist_find (signaled_widgets, (gconstpointer)widget))
    {
            signaled_widgets = g_slist_append (signaled_widgets, widget);
            g_signal_connect ((GObject*)widget, "toggled", G_CALLBACK (cl_checkbox_toggle), widget);
    }
Check if animation is wished, and if widget is not already known by animation stuff.
If we pass that, we connect signal to widget, so every time signal event arrives it call some function, in this case cl_checkbox_toggle, which add widget to hash table as soon as signal is there, and start animation stuff i described in step below.

And than:
if (clearlooks_style->animation && cl_async_animation_lookup((gconstpointer)widget))
    {
        int value = cl_async_animation_getdata((gpointer)widget).frame;
        
        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
            trans = (float)(5-value)/5;
        else
            trans = (float)value/5;
        
        draw_bullet = TRUE;
    }

First you check if animation is there and if this widget is added to animation stuff. Is so, you need to get information in what frame is widget already with cl_async_animation_getdata((gpointer)widget).frame;
We set animation to be from 5 to 0. This mean, int value can have some value between 0 and 5. We only do calculate translucency for bullet, so if checkbox is active, translucency value is fading out trans = (float)(5-value)/5;
If checkbox is not active we need to do fade in in just same way. And than do draw bullet with this transparency value in cairo:
cairo_set_source_rgba (cr, dot->r, dot->g, dot->b, trans);
Don't forget, sometimes redraw of widget takes a loot of time. Especially if you do tabs animation, you not only redraw one tab, but call redraw for whole notebook widget.
Thats all magik. I hope there would be more engines using this. And in the feature i hope GTK+ developers extend GTK+ code for this possibility without doing this hacking.