Search guides and the APICtrl K
  1. API reference
  2. Native GUI

gfx/animator.h

Bounds that move over time rather than jumping.

#include <sonore/gfx/animator.h>
On this page

Design notes

Why a plugin wants this

A panel that appears instantly reads as a redraw glitch: the eye cannot tell "this is new" from "this was drawn wrong". Two hundred milliseconds of travel is the difference between a plugin that feels built and one that feels like a prototype, and it costs a lerp per frame.

Why the clock is passed in

The same reason TooltipManager takes one: an animation is defined entirely by time, and reading a clock inside would make every rule below untestable except by sleeping. So now is an argument, the peer passes its own, and a test passes whatever it likes and gets exact numbers.

What it does not do

No keyframes, and no property animation beyond bounds and numbers. A plugin animates a panel sliding, a meter's ballistics (which belong in the meter, not here) and little else. Component animators elsewhere are the same shape for the same reason. Springs are here as an easing CURVE (SpringEasingOptions) on a fixed duration, which is how a duration-based animator takes one; a free-running integrator with momentum is AnimatedPosition, in animated_position.h.

Types 4

A cubic Bézier easing, in CSS's parameterisation.

cubic-bezier(x1, y1, x2, y2) with the two endpoints fixed at (0,0) and (1,1), which is what a designer hands over -- every design tool exports exactly this, and the named curves above are a handful of points on the same family.

The awkward part, and the reason this is not one line: the curve is parameterised by an internal t, and what is wanted is y as a function of X -- so x(t) = the input has to be SOLVED before y can be read. Newton, with a bisection fallback for the near-vertical curves where Newton wanders.

A spring, as an easing curve.

Why a spring when Elastic and Back already overshoot

Those are fixed shapes; a spring is a family with physical meaning, and it is what a designer specifying motion in a modern tool hands over -- "a response of 0.4 s with 20 % bounce", or a mass, a stiffness and a damping. The curve is the exact step response of a damped harmonic oscillator that starts at rest: 0 at the start, zero initial velocity, and for an underdamped spring a first overshoot of exp(-pi * zeta / sqrt(1 - zeta^2)), the textbook figure, which is what a test can hold it to.

The reference's options, and the physical ones

withFrequency, withAttenuation and withExtraAttenuationRange are the reference's parameterisation, with its defaults (3, 3, 0.25): FREQUENCY is how many oscillations fit in the animation's duration, ATTENUATION the exponential decay rate of their envelope over that duration. The last fraction of the duration -- the extra attenuation range -- is blended onto 1, so the animation ENDS on its target however lively the spring: a duration-based animator has to land when its time is up.

fromPhysical(mass, stiffness, damping, seconds) and fromResponse(response, bounce, seconds) map a physical spring onto the same curve for an animation of the given length; settlingTime() says how long that spring actually takes, which is the duration to give it if it should not be cut short. Over- and critically-damped springs are handled exactly too -- they just never overshoot.

Moves components' bounds over time.

Does not own anything, like every other container here. A component destroyed mid-flight is dropped at the next tick: each animation watches its component (detail::DeletionWatch). It used to be the caller's job to call cancel() first -- the contract MouseRouter::forget once had, and as easy to miss: a notice closed while it slid in, a panel deleted as it animated out, and the next tick set the bounds of freed memory and called its resized().

Moves a NUMBER over time, not a component's bounds.

Which is most of what an editor animates: an opacity fading in, a meter's peak-hold falling back, a colour sweeping between two states, a knob's glow. Animator next door moves rectangles and cannot express any of it.

The clock is an argument

Same rule as everything else here. tick(now) is called from the editor's 33 ms clock, and every behaviour below -- the easing shape, landing exactly on the target, a delay elapsing -- is an exact test rather than a sleep.

Delay is how things compose

There is no sequence builder, deliberately. An animation can start after a delay, and that one concept expresses both arrangements: two with the same delay run TOGETHER, and one with a delay equal to another's duration runs AFTER it. A builder would be a second way to say the same thing, with its own state machine to get wrong -- and CSS reached the same conclusion.

Functions 1

  • fninline double easeCurve(double t, Easing easing)
    The curve every animator here follows, by name: 0 at t = 0, 1 at t = 1, and between them whatever the name says -- Back and Elastic overshoot, Bounce lands in steps. One function, so the bounds animator and the value animator cannot disagree about what a name means; the bounds animator had its own copy that knew four of the seven and ran the rest linearly.
    gfx::easeCurve · animator.h:67

Enums 1

↑↓ to move↵ to openEvery public name in the headers, and every guide