Animating a ScrolledWindow

The other day I worked on improving the auto-scroll in Fractal (a super cool GTK+ Matrix Client). While doing this I discovered some nice features in GTK+.

Scrolling the view automatically (e.g. by pressing a button) is quite easy, you just need to set the value of vadjustment or hadjustment and then the view jumps to the new value.
This code moves the view to the bottom (yes, it’s Rust 😎):

if let Some(adj) = view.get_vadjustment() {
  adj.set_value(adj.get_upper() - adj.get_page_size());
}

View contains our ScrolledWindow and upper – page_size is the bottom of our view.

Awesome! We are done here!

But wait! What if we want to have a fancy animation, without the sudden jump? That’s exactly what we wanted in Fractal.

Let’s look at how we can add the smooth animation between the starting point and the end point.

The idea behind this is to update the position of the ScrolledWindow gradually instead of all at once. GTK provides us with a pulse to animate the state change, called tick. So we need to increase the value of the adjustment at each “tick”. A tick is fired every time GTK draws a new frame, so we don’t need to think about keeping in sync with the frame rate. For attaching a callback to the tick we use the function add_tick_callback(). We need also the frame clock, the start “tick”. We get the the clock with get_frame_clock(). This allows us to define the length of the animation, because the end of the transition will be start_clock + animation_duration. Now all we need is to calculate the position of the view at each “tick”.

This code snippet is inspired by the GTK+ source code. It moves a ScrolledWindow to the end of the view and could be adapted to scroll automatically (with an animation) to any point:

fn scroll_down(view: gtk::ScrolledWindow, animate: bool) {
    if let Some(adj) = view.get_vadjustment() {
        if animate {
            if let Some(clock) = view.get_frame_clock() {
                let duration = 200;
                let start = adj.get_value();
                let end = adj.get_upper() -
                          adj.get_page_size();
                let start_time = clock.get_frame_time();
                let end_time = start_time + 1000 * duration;
                view.add_tick_callback(move |_view, clock| {
                    let now = clock.get_frame_time();
                    if now < end_time &&
                       adj.get_value() != end {
                        let mut t = (now - start_time)
                                     as f64 /
                                     (end_time - start_time)
                                     as f64;
                        t = ease_out_cubic(t);
                        adj.set_value(start + 
                                      t * 
                                      (end - start));
                        return glib::Continue(true);
                    }
                    else
                    {
                        adj.set_value (end);
                        return glib::Continue(false);
                    }
                });
            }
        }
        else {
            adj.set_value(adj.get_upper() -
                          adj.get_page_size());
        }
    }
}

Instead of a linear animation I used a cubic easing function (the code was found inside the GTK+ source)

/* From clutter-easing.c, based on Robert Penner's
 * infamous easing equations, MIT license.
 */
fn ease_out_cubic (t: f64) -> f64 {
  let p = t - 1f64;
  return p * p * p + 1f64;
}

This code is from Fractal. The complete commit can be found here. I really hope that this post saves somebody from reading the GTK+ source code like I had to do, to figure out how to animate a simple ScrolledWindow :)

FOSDEM 2018

Last weekend I was in Brussels for FOSDEM, a super awesome conference about free and open source software. Since my first year, three years ago, a few things have changed. This year I went as a speaker and I brought with me a talk about my experience writing Teleport, my first GTK+ application. I really hope I could motivate somebody to start their own project. Also, in the past year my relationship to free software has changed, from just being a user and advocate to an active contributor (primarily to the GNOME Project).

I stayed in Brussels an entire week to save some money on the travel costs and visit Brussels. I ended up staying inside most of the time though, not only to escape the cold and rainy weather, but also because I get super motivated at conferences to work on projects. I mostly worked on the implementation of the redesign of the background settings panel for GNOME, and I really hope we can still get it into GNOME 3.28 (fingers crossed!). In order to build the background panel I had to use jhbuild for the first time, and I had to learn it quickly because I had to finish the patch. So I spent a lot of time getting jhbuild running, while already preparing the talk in my head and going to social dinners and beer events, which was a bit much at times ;)

Sorry that you had to wait, David and Tobias, to go to the Purism dinner (Picture by Tobias)

On Sunday I helped out at the GNOME booth. Luckily in the beginning there where not too many people, most likely they had a hard time getting up after the GNOME beer event the night before :D
Therefore I had time to get comfortable at the booth and to learn what I needed to do.

I had a nice time at the booth, not only with Bastian (Picture by Tobias)

As my talk got closer I got more and more nervous, but at least the previous days I was really calm about it. My talk was at 4 pm on Sunday in the Open Source Design devroom. It went well, except for a pretty bad case of the demo effect: The demo didn’t work on the first try, then my touchpad suddenly stopped working, and the entire time the projector was set at a resolution that cut off part of the slides :/

The beginning of my Teleport talk (Picture by Tobias)

The most important thing I learned this year at FOSDEM (tip for all FOSDEM attendees) was to buy a “MOBIB Basic” card for 5 Euros and charge it with “10 journeys JUMP”s. This is much cheaper than buying single journey tickets, and it can even be used by more than one person at the same time.

All in all I really loved the event. I made a lot of new experiences, met new people and had interesting conversations. I’m already excited to come back to Brussels next year!