Crate mina

source ·
Expand description

Mina is a framework-independent animation library focused on ergonomics, aiming to bring the simplicity and versatility of CSS transitions and animations to Rust.

Features

  • Turn any ordinary struct into an animator or animated type.
  • Create state-driven animators that automatically blend between states.
  • Provides (almost) all standard easings out of the box, with the option to add custom curves or functions.
  • Define animations using a concise, CSS-like syntax, and state transitions using a style similar to CSS pseudo-classes.
  • Animate any property type that supports linear interpolation.
  • Easily specify delayed, repeating or reversing animations.
  • Merge heterogeneous animations/transitions into a single timeline; e.g. define a single animation that pulses in and out infinitely but also scales or slides in only once.
  • Use with any GUI or creative coding environment - integration examples are provided for nannou, bevy and iced.

Timeline Example

The Timeline is the most basic abstraction and defines a single, state-independent animation over a time axis.

Suppose we want to animate both the size and position of some entity over time. It will be small at each edge and largest in the middle:

use mina::prelude::*;

#[derive(Animate, Clone, Debug, Default, PartialEq)]
struct Style {
  x: i32,
  size: u32,
}

let timeline = timeline!(Style 10s
    from { x: -200, size: 10 }
    50% { x: 0, size: 20 }
    to { x: 200, size: 10});

let mut style = Style::default();
timeline.update(&mut style, 2.5);
assert_eq!(style, Style { x: -100, size: 15 });

Note: in the above code, Clone and Default are required traits for any timeline-able type, but Debug and PartialEq are only needed for the assertions and are not required for regular usage.

from and to are aliases for 0% and 100% respectively. Either may be used, but the former are more idiomatic and tend to improve readability.

Animator Example

StateAnimator types own many timelines, as well as the style or other structure being animated, and are meant to be driven directly by an event loop. Instead of requesting the properties at a particular time, as in the Timeline example above, you interact with animators by notifying them of elapsed time and state changes.

Suppose we are designing an animated button; when hovered, it receives an elevation, and when pressed, it slightly increases in size.

use mina::prelude::*;

#[derive(Animate, Clone, Debug, Default, PartialEq)]
struct Style {
    elevation: f32,
    scale: f32,
}

#[derive(Clone, Default, PartialEq, State)]
enum State {
    #[default] Idle,
    Hovered,
    Pressed,
}

let mut animator = animator!(Style {
    default(State::Idle, { elevation: 0.0, scale: 1.0 }),
    State::Idle => 0.25s to default,
    State::Hovered => 0.5s to { elevation: 5.0, scale: 1.0 },
    State::Pressed => 0.1s to { scale: 1.1 }
});

assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 }); // Default
animator.advance(1.0); // No change in state
assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 });
animator.set_state(&State::Hovered);
assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 }); // No time elapsed
animator.advance(0.25);
assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.0 });
animator.set_state(&State::Pressed); // Change state before animation is finished
assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.0 }); // No time elapsed
animator.advance(0.05);
assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.05 });

The Clone, Default and PartialEq traits are all required for any type to be used as an animator state. State is an alias for Enum, and currently required for the EnumStateAnimator and animator macro.

The default(state, props) line is not required, but supported for specific and relatively common cases where the default resting values for that animation do not match the Default implementation for the corresponding struct, or in the (less common) case that the default state for the animation should be different from the default enum member. In the above example, the derived Default implementation would give a scale of 0.0, but the normal scale of the button should be 1.0, so we override the default.

The same default term within an animation state has a different meaning, and is interpreted as “use the default (for this animator) values for this keyframe”. This helps avoid repetitive, copy-paste code for default/idle states that should simply return the widget to its base state.

Note how the Hovered state specifies a scale that is the same as the default. The reason for this is to tell the animator that when transitioning from Pressed back to Hovered, it should revert the scale transform used for Pressed. Mina does not assume that undefined values in a keyframe should use defaults, and this is a very important property for merged timelines and more advanced animations in general. If a keyframe is missing one or more properties, those properties are ignored:

  • If there are earlier or later keyframes that do specify them, then it will animate smoothly between those keyframes. This applies to both Timeline and StateAnimator.
  • If any given state does not specify any keyframes at all with some properties, then the properties will not animate when in that state; they will remain at whichever values the previous state left them in.

The actual implementation of elevation and scale are up to the underlying GUI. Mina doesn’t care about the meaning of these properties, it just animates their values; the plumbing will vary with the specific GUI in use. Future updates may include standard integrations with those GUIs, but for now, the examples directory serves as the unofficial integration how-to guide, as well as the repository for more complex and interesting uses of the API.

Event Loop

Mina doesn’t use its own event loop, so that it can instead be integrated into the event loop of whichever GUI is actually in use. This also allows global customizations - for example, stopping all animations when a game is paused, or playing them in slow motion during some key event.

In most cases, establishing the event loop is a one- or two-line function. Refer to the examples for framework-specific patterns.

Modules

  • Includes the types commonly used for building animations.

Macros

Structs

Enums

Traits

  • Provides an easing function, AKA animation timing function, for non-linear interpolation of values, typically along some curve.
  • Builder interface for creating a typed Keyframe.
  • Trait for a type that supports the standard lerp (linear interpolation) operation.
  • Enum mapping type.
  • Animates a collection of values over time, automatically selecting the correct animation based on current state and blending with any previous animation still in progress.
  • An animator timeline.
  • Trait for a builder that creates typed Timeline instances.

Type Definitions

Derive Macros

  • Sets up a type for animation.
  • Derive macro generating an implementation of trait Enum.