Skip to content

Animating views#

AUI has two ways to make things move: animators attached to a view, and animating a property yourself.

Animators#

An animator (AAnimator) transforms how a view is painted over a period of time. Create one, set the duration in seconds and assign it to the view. The view is repainted continuously until the animation finishes.

auto animator = _new<ATranslationAnimator>(glm::vec2 { -100, 0 });   // slide in from the left
animator->setDuration(0.3f);
view->setAnimator(animator);

The other stock animators are ARotationAnimator (between two AAngleRadians, i.e., 0_deg and 90_deg) and ASizeAnimator. AAnimator::combine({ a, b }) runs several at once; setRepeating(true) makes the animation loop (useful for a busy indicator like ASpinner); setCurve replaces the easing. Ready-made curves live in aui::animation_curves (Linear, CubicBezier, Standard).

Animators change only the painting: the layout and hit testing of the view are not affected, except for ASizeAnimator, which really changes the size.

A custom animator is a class overriding doAnimation(view, theta, renderer) where theta goes from 0 to 1.

Animating a property#

Anything that is a function of a property can be animated by changing that property over time. Drive it with an ATimer, and let reactive expressions or signals do the rest. The timer example (7GUIs) updates the current time ten times a second, and a progress bar follows:

class TimerWindow : public AWindow {
public:
    TimerWindow() : AWindow("AUI - 7GUIs - Timer", 300_dp, 50_dp) {
        setContents(Centered {
          Vertical::Expanding {
            Horizontal {
              Label { "Elapsed Time:" },
              Centered::Expanding {
                _new<AProgressBar>() AUI_LET {
                        it & mElapsedTimeRatio;
                        it->setCustomStyle({ Expanding { 1, 0 } });
                    },
              },
            } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
            Label { AUI_REACT("{:.1f}s"_format(duration_cast<milliseconds>(*mElapsedTime).count() / 1000.f)) },
            Horizontal {
              Label { "Duration:" },
              /// [timer_slider]
              Slider {
                .value = AUI_REACT(float(mDuration->count()) / float(MAX_DURATION.count())),
                .onValueChanged =
                    [this](aui::float_within_0_1 newValue) {
                        mDuration =
                            high_resolution_clock::duration(long(float(MAX_DURATION.count()) * float(newValue)));
                    },
              } AUI_OVERRIDE_STYLE { Expanding { 1, 0 } },
              /// [timer_slider]
            } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
            _new<AButton>("Reset Timer") AUI_OVERRIDE_STYLE {
                  Expanding { 1, 0 },
                } AUI_LET { connect(it->clicked, me::reset); },
          } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
        });

        connect(mTimer->fired, me::update);
        mTimer->start();
    }

private:
    _<ATimer> mTimer = _new<ATimer>(100ms);
    high_resolution_clock::time_point mStartTime = high_resolution_clock::now();
    AProperty<high_resolution_clock::time_point> mCurrentTime;
    AProperty<high_resolution_clock::duration> mDuration = 30s;

    APropertyPrecomputed<high_resolution_clock::duration> mElapsedTime = [&] {
        return std::min(mCurrentTime - mStartTime, *mDuration);
    };

    APropertyPrecomputed<aui::float_within_0_1> mElapsedTimeRatio = [&] {
        return float(mElapsedTime->count()) / float(mDuration->count());
    };

    void update() { mCurrentTime = high_resolution_clock::now(); }

    void reset() { mStartTime = high_resolution_clock::now(); }
};

For indeterminate waiting, ASpinner and ASpinnerV2 rotate by themselves; use AProgressBar when you know the progress.

Performance#

Every frame of an animation is a repaint. Keep animated areas small and do not animate in a view that is expensive to paint; consider Render to texture to cache a complex subtree.