#smoothing #user-input #animation #ui #transition #animator #frame-rate

uianimator

utilities for animations which can elegantly respond to user inputs mid-animation

2 releases

0.1.1 May 27, 2024
0.1.0 May 27, 2024

#9 in #frame-rate

MIT/Apache

13KB
166 lines

uianimator

Smooth, interruptable animations for your UI

This crate provides the Animator trait and some default animators. You can use the get_value and set_target functions on an animator to interact with it.

Why?

UI animations usually suffer from one of two problems:

They cannot be interrupted
If a user opens a sidebar which has a slide-in animation, but they close it before it is fully open, the sidebar might jump to its fully-open state before playing the slide-out animation, or it will immediately start moving in the opposite direction, going from +x speed to -x without any transition.

or they are inaccurate and influenced by frame-/tickrate
The first problem can be solved somewhat easily by storing a speed and position, then using an iterative algorithm to update them. However, this means that running the application at a lower framerate will make the animation slower. You can work around this by using deltaTime, or otherwise moving in bigger steps when more time has passed, but this will still make your animations inconsistent across devices which perform differently.

uianimator solves both of these problems. it doesn't use an iterative approach and all the default animators are designed so that the both your animations current value (position) and the rate at which it changes (speed) will not jump, but instead transition smoothly, so that your animations never seem jerky.

Usage

use std::time::{Duration, Instant};

use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator};

fn main() {
    // start at 0.5 with a speed factor of 2.
    let mut animator = DefaultAnimatorF64Quadratic::new(0.5, 2.0);
    // smoothly transition from 0.5 to 2
    animator.set_target(2.0, Instant::now());
    loop {
        // repeatedly get the animator's current value and print it
        let val = animator.get_value(Instant::now());
        let count = (50.0 * val) as _;
        eprintln!(
            "val: {val:.2} | {}{} |",
            "=".repeat(count),
            " ".repeat(100 - count)
        );
        // once we reach 1, go to 0 instead. this simulates a user interrupting our animation.
        if val > 1.0 {
            animator.set_target(0.0, Instant::now());
        }
        // once we reach 0, exit
        if val == 0.0 {
            break;
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

The initial value of the animator is 0.5. It then starts moving towards 2.0, but never reaches it, because a user interrupts the animation, setting the new target to 0.
This is the program's output (the right line represents the value 2, the left one is 0):

val: 0.50 |=============                                      |
val: 0.51 |=============                                      |
val: 0.54 |==============                                     |
val: 0.59 |===============                                    |
val: 0.66 |=================                                  |
val: 0.75 |===================                                |
val: 0.87 |======================                             |
val: 1.00 |=========================                          |
val: 1.15 |=============================                      |
val: 1.30 |=================================                  |
val: 1.43 |====================================               |
val: 1.55 |=======================================            |
val: 1.64 |=========================================          |
val: 1.71 |===========================================        |
val: 1.76 |============================================       |
val: 1.79 |=============================================      |
val: 1.80 |=============================================      |
val: 1.79 |=============================================      |
val: 1.76 |============================================       |
val: 1.71 |===========================================        |
val: 1.64 |=========================================          |
val: 1.55 |=======================================            |
val: 1.43 |====================================               |
val: 1.30 |=================================                  |
val: 1.15 |=============================                      |
val: 0.98 |========================                           |
val: 0.79 |====================                               |
val: 0.62 |================                                   |
val: 0.47 |============                                       |
val: 0.35 |=========                                          |
val: 0.24 |======                                             |
val: 0.15 |====                                               |
val: 0.08 |==                                                 |
val: 0.03 |=                                                  |
val: 0.01 |                                                   |
val: 0.00 |                                                   |

No runtime deps