Magnum::Ui::NodeAnimator class new in Git master

Node animator.

Each animation interpolates between given node offset, size and opacity endpoints, optionally modifying node flags and allowing to automatically remove the node once the animation stops. If you need to execute arbitrary extra code during a node animation, use GenericNodeAnimator.

Setting up an animator instance

The animator doesn't have any shared state or configuration, so it's just about constructing it from a fresh AbstractUserInterface::createAnimator() handle and passing it to setNodeAnimatorInstance(). After that, assuming AbstractUserInterface::advanceAnimations() is called in an appropriate place, it's ready to use.

Ui::NodeAnimator& animator = ui.setNodeAnimatorInstance(
    Containers::pointer<Ui::NodeAnimator>(ui.createAnimator()));

Unlike builtin layers or layouters, the default UserInterface implementation doesn't implicitly provide a NodeAnimator instance.

Creating animations

An animation is created by calling create() with a NodeAnimation helper that specifies which properties — node offset, size, opacity or flags — should be animated, an easing function from Animation::Easing or a custom one, time at which it's meant to start, its duration and the NodeHandle it's attached to. Properties not specified in the passed NodeAnimation instance stay untouched by the animation.

The following code animates a popup sliding from the top edge of the screen to the center. Besides that, it enables NodeFlag::NoEvents at the beginning of the animation and clears it again at the end to prevent accidental and likely imprecise interaction while the popup is moving:

Ui::NodeHandle popup = ;
Vector2 popupSize = ui.nodeSize(popup);

animator.create(
    Ui::NodeAnimation{}
        .fromOffset({(ui.size().x() - popupSize.x())*0.5f, -popupSize.y()})
        .toOffset((ui.size() - popupSize)*0.5f)
        .addFlagsBegin(Ui::NodeFlag::NoEvents)
        .clearFlagsEnd(Ui::NodeFlag::NoEvents),
    Animation::Easing::cubicIn, now, 0.5_sec, popup);

If either the source or the target offset, size or opacity value is omitted, the animation picks the value the node has at the time the animation is being played. In the following snippet, a context menu is being closed, shrinking it from its current size to zero. NodeFlag::Clip is enabled so the contents get clipped during resize and, since it becomes invisible after, NodeFlag::Hidden is added at the end:

Ui::NodeHandle contextMenu = ;

animator.create(
    Ui::NodeAnimation{}
        .toSize({0.0f, 0.0f})
        .addFlagsBegin(Ui::NodeFlag::Clip)
        .addFlagsEnd(Ui::NodeFlag::Hidden),
    Animation::Easing::backOut, now, 1.0_sec, contextMenu);

It's also possible to animate just horizontal or vertical offset and size alone, with the other direction staying unchanged, using NodeAnimation::fromOffsetX(), fromOffsetY() etc. Setting both components separately is the same as calling the vector fromOffset() etc. variants.

With NodeAnimation::setRemoveNodeAfter() an animation can remove the node afterwards, which is useful for example to automatically remove a one-time notification once it fades out. In this case, it fades to fully transparent after a ten-second delay:

Ui::NodeHandle tooltip = ;

animator.create(
    Ui::NodeAnimation{}
        .toOpacity(0.0f)
        .setRemoveNodeAfter(true),
    Animation::Easing::exponentialIn, now + 10.0_sec, 0.3_sec, tooltip);

Reversible animations

In cases where for example opening and closing a menu is animated the same way, just in reverse, it's possible to create a single animation and drive it either forward or backward. In the following case, the menu is initially NodeFlag::Hidden, slides from the top and is not reacting to events until the animation stops. To make the animation reusable, it's marked with AnimationFlag::KeepOncePlayed so it doesn't get removed after first play.

Ui::NodeHandle menu = ui.createNode(, Ui::NodeFlag::Hidden);

Ui::AnimationHandle menuAnimation = animator.create(
    Ui::NodeAnimation{}
        .fromOffsetY(-ui.nodeSize(menu).y())
        .toOffsetY(0.0f)
        .clearFlagsBegin(Ui::NodeFlag::Hidden)
        .addFlagsBegin(Ui::NodeFlag::NoEvents)
        .clearFlagsEnd(Ui::NodeFlag::NoEvents),
    Animation::Easing::elasticIn, now, 0.5_sec, menu,
    Ui::AnimationFlag::KeepOncePlayed);

When AnimationFlag::Reverse is added and the animation is played, it'll first add NodeFlag::NoEvents, slide in the other direction and finally removes NoEvents again and makes the node hidden:

animator.addFlags(menuAnimation, Ui::AnimationFlag::Reverse);
animator.play(menuAnimation, now);

Furthermore, such an animation can be reversed during the time it's played using the addFlags() / clearFlags() overloads that additionally take current time, which will make the animation continue back from where it ended up being at given time. The following code will first attempt to make the animation play backward assuming it's still playing, and if it's not it plays it from the start:

animator.addFlags(menuAnimation, Ui::AnimationFlag::Reverse, now);
if(animator.state(menuAnimation) != Ui::AnimationState::Playing)
    animator.play(menuAnimation, now);

Animation lifetime and node attachment

As with all other animations, they're implicitly removed once they're played. Pass AnimationFlag::KeepOncePlayed to create() or addFlags() to disable this behavior.

As the animations are associated with nodes they animate, when the node the animation is attached to is removed, the animation gets removed as well. If you want to preserve the animation when the node is removed, call attach(AnimationHandle, NodeHandle) with NodeHandle::Null to detach it from the node before removing. After that, or if you have called create() with NodeHandle::Null in the first place, the animation will still play but affect nothing.

Note that if NodeAnimation::setRemoveNodeAfter() is enabled, the animation gets implicitly removed along with the node when it stops, even with AnimationFlag::KeepOncePlayed set.

Multiple animations affecting a single node

Currently, if multiple animations simultaenously affect the same property of the same node, the behavior is unspecified and will likely result in the animations overwriting each other's output. There's no guarantee about the order in which the animations are applied to a particular node, nor there's any merging done.

On the other hand, having one NodeAnimator animation affect for example just an offset and another just a size or opacity will work correctly if both animations are coming from NodeAnimator. One practical use case is having a different easing function for each. There's however no way to guarantee this when the node animations are combined with GenericNodeAnimator or custom animators.

Base classes

class AbstractNodeAnimator new in Git master
Base for node animators.

Constructors, destructors, conversion operators

NodeAnimator(AnimatorHandle handle) explicit
Constructor.
NodeAnimator(const NodeAnimator&) deleted
Copying is not allowed.
NodeAnimator(NodeAnimator&&) noexcept
Move constructor.

Public functions

auto operator=(const NodeAnimator&) -> NodeAnimator& deleted
Copying is not allowed.
auto operator=(NodeAnimator&&) -> NodeAnimator& noexcept
Move assignment.
auto create(const NodeAnimation& animation, Float(*)(Float) easing, Nanoseconds start, Nanoseconds duration, NodeHandle node, UnsignedInt repeatCount = 1, AnimationFlags flags = {}) -> AnimationHandle
Create an animation.
auto create(const NodeAnimation& animation, Float(*)(Float) easing, Nanoseconds start, Nanoseconds duration, NodeHandle node, AnimationFlags flags) -> AnimationHandle
Create an animation.
void remove(AnimationHandle handle)
Remove an animation.
void remove(AnimatorDataHandle handle)
Remove an animation assuming it belongs to this animator.
auto offsets(AnimationHandle handle) const -> Containers::Pair<Vector2, Vector2>
Animation source and target offsets.
auto offsets(AnimatorDataHandle handle) const -> Containers::Pair<Vector2, Vector2>
Animation source and target offsets assuming it belongs to this animator.
auto sizes(AnimationHandle handle) const -> Containers::Pair<Vector2, Vector2>
Animation source and target sizes.
auto sizes(AnimatorDataHandle handle) const -> Containers::Pair<Vector2, Vector2>
Animation source and target sizes assuming it belongs to this animator.
auto opacities(AnimationHandle handle) const -> Containers::Pair<Float, Float>
Animation source and target opacities.
auto opacities(AnimatorDataHandle handle) const -> Containers::Pair<Float, Float>
Animation source and target opacities assuming it belongs to this animator.
auto flagsAdd(AnimationHandle handle) const -> Containers::Pair<NodeFlags, NodeFlags>
Node flags to add at animation begin and end.
auto flagsAdd(AnimatorDataHandle handle) const -> Containers::Pair<NodeFlags, NodeFlags>
Node flags to add at animation begin and end assuming it belongs to this animator.
auto flagsClear(AnimationHandle handle) const -> Containers::Pair<NodeFlags, NodeFlags>
Node flags to clear at animation begin and end.
auto flagsClear(AnimatorDataHandle handle) const -> Containers::Pair<NodeFlags, NodeFlags>
Node flags to clear at animation begin and end assuming it belongs to this animator.
auto hasRemoveNodeAfter(AnimationHandle handle) const -> bool
Whether the node the animation is assigned to is removed when the animation stops.
auto hasRemoveNodeAfter(AnimatorDataHandle handle) const -> bool
Whether the node the animation is assigned to is removed when the animation stops assuming it belongs to this animator.
auto easing(AnimationHandle handle) -> auto
Animation easing function.
auto easing(AnimatorDataHandle handle) -> auto
Animation easing function assuming it belongs to this animator.

Function documentation

Magnum::Ui::NodeAnimator::NodeAnimator(AnimatorHandle handle) explicit

Constructor.

Parameters
handle Handle returned by AbstractUserInterface::createAnimator()

Magnum::Ui::NodeAnimator::NodeAnimator(NodeAnimator&&) noexcept

Move constructor.

Performs a destructive move, i.e. the original object isn't usable afterwards anymore.

AnimationHandle Magnum::Ui::NodeAnimator::create(const NodeAnimation& animation, Float(*)(Float) easing, Nanoseconds start, Nanoseconds duration, NodeHandle node, UnsignedInt repeatCount = 1, AnimationFlags flags = {})

Create an animation.

Parameters
animation Offset, size, opacity and flag animation properties
easing Easing function between 0.0f and 1.0f, used for offset, size and opacity. Pick one from Animation::Easing or supply a custom one.
start Time at which the animation starts. Use Nanoseconds::max() for creating a stopped animation.
duration Duration of a single play of the animation
node Node the animation is attached to. Use NodeHandle::Null to create an animation that isn't attached to any node.
repeatCount Repeat count. Use 0 for an indefinitely repeating animation.
flags Flags

Expects that easing is not nullptr if animation affects node offset, size or opacity. Delegates to AbstractAnimator::create(Nanoseconds, Nanoseconds, NodeHandle, UnsignedInt, AnimationFlags), see its documentation for more information.

AnimationHandle Magnum::Ui::NodeAnimator::create(const NodeAnimation& animation, Float(*)(Float) easing, Nanoseconds start, Nanoseconds duration, NodeHandle node, AnimationFlags flags)

Create an animation.

Same as calling create(const NodeAnimation&, Float(*)(Float), Nanoseconds, Nanoseconds, NodeHandle, UnsignedInt, AnimationFlags) with repeatCount set to 1.

void Magnum::Ui::NodeAnimator::remove(AnimationHandle handle)

Remove an animation.

Delegates to AbstractAnimator::remove(AnimationHandle).

void Magnum::Ui::NodeAnimator::remove(AnimatorDataHandle handle)

Remove an animation assuming it belongs to this animator.

Like remove(AnimationHandle) but delegates to AbstractAnimator::remove(AnimatorDataHandle) instead.

Containers::Pair<Vector2, Vector2> Magnum::Ui::NodeAnimator::offsets(AnimationHandle handle) const

Animation source and target offsets.

Expects that handle is valid. Components that are NaN are taken from the AbstractUserInterface at the time the animation starts. If a component is a NaN in both the source and the target offset, it's not animated at all.

Containers::Pair<Vector2, Vector2> Magnum::Ui::NodeAnimator::offsets(AnimatorDataHandle handle) const

Animation source and target offsets assuming it belongs to this animator.

Like offsets(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

Containers::Pair<Vector2, Vector2> Magnum::Ui::NodeAnimator::sizes(AnimationHandle handle) const

Animation source and target sizes.

Expects that handle is valid. Components that are NaN are taken from the AbstractUserInterface at the time the animation starts. If a component is a NaN in both the source and the target size, it's not animated at all.

Containers::Pair<Vector2, Vector2> Magnum::Ui::NodeAnimator::sizes(AnimatorDataHandle handle) const

Animation source and target sizes assuming it belongs to this animator.

Like sizes(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

Containers::Pair<Float, Float> Magnum::Ui::NodeAnimator::opacities(AnimationHandle handle) const

Animation source and target opacities.

Expects that handle is valid. Values that are NaN are taken from the AbstractUserInterface at the time the animation starts. If both the source and the target opacity is a NaN, it's not animated at all.

Containers::Pair<Float, Float> Magnum::Ui::NodeAnimator::opacities(AnimatorDataHandle handle) const

Animation source and target opacities assuming it belongs to this animator.

Like opacities(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

Containers::Pair<NodeFlags, NodeFlags> Magnum::Ui::NodeAnimator::flagsAdd(AnimationHandle handle) const

Node flags to add at animation begin and end.

Expects that handle is valid.

Containers::Pair<NodeFlags, NodeFlags> Magnum::Ui::NodeAnimator::flagsAdd(AnimatorDataHandle handle) const

Node flags to add at animation begin and end assuming it belongs to this animator.

Like flagsAdd(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

Containers::Pair<NodeFlags, NodeFlags> Magnum::Ui::NodeAnimator::flagsClear(AnimationHandle handle) const

Node flags to clear at animation begin and end.

Expects that handle is valid.

Containers::Pair<NodeFlags, NodeFlags> Magnum::Ui::NodeAnimator::flagsClear(AnimatorDataHandle handle) const

Node flags to clear at animation begin and end assuming it belongs to this animator.

Like flagsClear(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

bool Magnum::Ui::NodeAnimator::hasRemoveNodeAfter(AnimationHandle handle) const

Whether the node the animation is assigned to is removed when the animation stops.

Expects that handle is valid.

bool Magnum::Ui::NodeAnimator::hasRemoveNodeAfter(AnimatorDataHandle handle) const

Whether the node the animation is assigned to is removed when the animation stops assuming it belongs to this animator.

Like hasRemoveNodeAfter(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.

auto Magnum::Ui::NodeAnimator::easing(AnimationHandle handle)

Animation easing function.

Expects that handle is valid. The returned pointer is only guaranteed to not be nullptr if given animation affects node offset, size or opacity.

auto Magnum::Ui::NodeAnimator::easing(AnimatorDataHandle handle)

Animation easing function assuming it belongs to this animator.

Like easing(AnimationHandle) const but without checking that handle indeed belongs to this animator. See its documentation for more information.