tween and easing
import tween from "tween";
import easing from "easing";
var x = tween.from_f64(0.0);
x.to(100.0, 300, easing.Ease.CubicOut); // 0 -> 100 in 300 ms
x.to(80.0, 150, easing.Ease.QuadInOut); // then settle back to 80
let now = x.step(16.0); // where it is 16 ms laterA tween is a value moving through points over time: each leg has its
own duration in whole milliseconds and its own easing curve. You step it
by the time that passed (an f64 of milliseconds, a frame’s dt_ms) and
read where it is. It is a plain value (no heap):
keep it in a var and copy it like a struct. To animate a component, step
its tweens in frame (Animating a component).
Tweens
tween has Tween_f64, Tween_f32, Tween_i64, Tween_i32 and
Tween_color, made with from_f64(v) and the like.
| Method | Does |
|---|---|
t.to(v, ms, ease) | A leg to v, ms long, eased by an easing.Ease |
t.to_curve(v, ms, f) | The same with a curve of your own, fn(f64) -> f64 |
t.hold(ms) | Stay at the last point for ms |
t.step(dt) -> T | Move dt milliseconds (f64) on and give the value |
t.seek(ms) -> T | Jump to ms (f64) from the start |
t.value() -> T | The value where it is |
t.done() -> bool | At the end, with no repeats left |
t.repeat(n, yoyo) | Play n more times (-1: forever), turning round each time with yoyo |
t.retarget(v, ms, ease) | Start again from the value shown, toward v |
t.backward(), t.forward(), t.restart() | Run toward the start or the end; back to the start |
t.duration(), t.position(), t.progress(), t.point() | Total ms (i64), where it is in ms (f64), 0.0 to 1.0, the leg it is on |
A tween holds up to 16 points, its start included. retarget is what a
transition wants: when the target changes half way, the value goes on
from where it is instead of jumping.
var pulse = tween.from_color(#368CE2FF);
pulse.to(#6FB0F0FF, 400, easing.Ease.SineInOut);
pulse.repeat(-1, true); // back and forth, foreverYour own types
A struct of f64, f32, i64, i32 and color fields tweens with all
its fields together:
pub struct Vec2 { x: f64; y: f64; }
@tape { tween.gen_tween(Vec2); } // Tween_Vec2, from_Vec2, lerp_Vec2
var pos = from_Vec2(Vec2 { x: 0.0; y: 0.0; });
pos.to(Vec2 { x: 200.0; y: 50.0; }, 500, easing.Ease.QuadOut);The struct must be pub (the generator declares pub functions over it).
A field of another type keeps the start’s value.
Easing curves
easing.Ease names the curves, the set easings.net shows, plus CSS’s:
| In | Out | InOut | |
|---|---|---|---|
| Quad, Cubic, Quart, Quint | QuadIn … | QuadOut … | QuadInOut … |
| Sine, Expo, Circ | SineIn … | SineOut … | SineInOut … |
| Back (overshoots a little) | BackIn | BackOut | BackInOut |
| Elastic (springs past the end) | ElasticIn | ElasticOut | ElasticInOut |
| Bounce | BounceIn | BounceOut | BounceInOut |
CSS’s ease | CssIn | CssOut | CssInOut (and Css) |
with Linear and Stepped (holds the start until the end). Each is also
a function, easing.cubic_out(t), and easing.cubic_bezier(x1, y1, x2, y2, t) is any CSS cubic-bezier(). easing.apply(e, t) gives a curve
at t; easing.lerp_f64, lerp_i64 and lerp_color mix two values.
A curve can go outside 0..1 on the way (Back, Elastic): a value
overshoots its end, and comes back.
Animating a component
component Toast {
@tape { ui.animated(); }
var y: i64 = -40;
var slide: tween.Tween_i64 = tween.from_i64(-40);
fn show() {
slide.retarget(0, 250, easing.Ease.BackOut);
ui.animate(__self);
}
fn frame(dt_ms: f64) -> bool {
y = slide.step(dt_ms);
return !slide.done(); // false: no more frames
}
view { ... } // uses y
}@tape { ui.animated(); } gives a component a frame(dt_ms: f64) -> bool method. After ui.animate(comp), ui calls it once a frame (every
16 ms) with the milliseconds since the last frame, as an f64 with
the fractions the real clock measured, until it returns
false, ui.stop_animation(comp), or the component is removed. Assign
what the view or paint reads there, and it repaints. Nothing runs while
nothing animates. ui.animating(comp) tells whether it does.
Headless, screen.advance(ms) runs the frames on its own clock, so an
animation is tested exactly:
var sc = ui.headless(toast, 300, 100) or { return 1; };
toast.show();
sc.advance(125); // half way
sc.advance(1000); // doneWidget transitions
widgets.Button, TextInput, Checkbox and Slider fade between their
state colors with transition (milliseconds, 0 by default: at once) and
transition_ease:
widgets.Button { label: "Save"; transition: 150; transition_ease: easing.Ease.CssOut; }or for every button of a module:
#style
widgets.Button { transition: 150; }The fill, border and text move to the hovered, pressed, focused or disabled look (yours or the theme’s) over that time, starting from the color shown when the state changes again half way.
Last modified: