mina/lib.rs
1//! Mina is a framework-independent animation library focused on ergonomics, aiming to bring the
2//! simplicity and versatility of CSS transitions and animations to Rust.
3//!
4//! # Features
5//!
6//! - Turn any ordinary `struct` into an animator or animated type.
7//! - Create state-driven animators that automatically blend between states.
8//! - Provides (almost) all standard [easings](https://easings.net/) out of the box, with the option
9//! to add custom curves or functions.
10//! - Define animations using a concise, CSS-like syntax, and state transitions using a style
11//! similar to CSS pseudo-classes.
12//! - Animate any property type that supports [linear interpolation](crate::Lerp).
13//! - Easily specify delayed, repeating or reversing animations.
14//! - Merge heterogeneous animations/transitions into a single timeline; e.g. define a _single_
15//! animation that pulses in and out infinitely but also scales or slides in only once.
16//! - Use with any GUI or creative coding environment -
17//! [integration examples](https://github.com/focustense/mina/tree/main/examples) are provided for
18//! [nannou](https://nannou.cc/), [bevy](https://bevyengine.org/) and
19//! [iced](https://github.com/iced-rs/iced).
20//!
21//! # Timeline Example
22//!
23//! The [`Timeline`] is the most basic abstraction and defines a single, state-independent animation
24//! over a time axis.
25//!
26//! Suppose we want to animate both the size and position of some entity over time. It will be small
27//! at each edge and largest in the middle:
28//!
29//! ```
30//! use mina::prelude::*;
31//!
32//! #[derive(Animate, Clone, Debug, Default, PartialEq)]
33//! struct Style {
34//! x: i32,
35//! size: u32,
36//! }
37//!
38//! let timeline = timeline!(Style 10s
39//! from { x: -200, size: 10 }
40//! 50% { x: 0, size: 20 }
41//! to { x: 200, size: 10});
42//!
43//! let mut style = Style::default();
44//! timeline.update(&mut style, 2.5);
45//! assert_eq!(style, Style { x: -100, size: 15 });
46//! ```
47//!
48//! Note: in the above code, [`Clone`] and [`Default`] are required traits for any timeline-able
49//! type, but [`Debug`] and [`PartialEq`] are only needed for the assertions and are not required
50//! for regular usage.
51//!
52//! `from` and `to` are aliases for `0%` and `100%` respectively. Either may be used, but the former
53//! are more idiomatic and tend to improve readability.
54//!
55//! # Animator Example
56//!
57//! [`StateAnimator`] types own many timelines, as well as the style or other structure being
58//! animated, and are meant to be driven directly by an event loop. Instead of requesting the
59//! properties at a particular time, as in the [`Timeline`] example above, you interact with
60//! animators by notifying them of elapsed time and state changes.
61//!
62//! Suppose we are designing an animated button; when hovered, it receives an elevation, and when
63//! pressed, it slightly increases in size.
64//!
65//! ```
66//! use mina::prelude::*;
67//!
68//! #[derive(Animate, Clone, Debug, Default, PartialEq)]
69//! struct Style {
70//! elevation: f32,
71//! scale: f32,
72//! }
73//!
74//! #[derive(Clone, Default, PartialEq, State)]
75//! enum State {
76//! #[default] Idle,
77//! Hovered,
78//! Pressed,
79//! }
80//!
81//! let mut animator = animator!(Style {
82//! default(State::Idle, { elevation: 0.0, scale: 1.0 }),
83//! State::Idle => 0.25s to default,
84//! State::Hovered => 0.5s to { elevation: 5.0, scale: 1.0 },
85//! State::Pressed => 0.1s to { scale: 1.1 }
86//! });
87//!
88//! assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 }); // Default
89//! animator.advance(1.0); // No change in state
90//! assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 });
91//! animator.set_state(&State::Hovered);
92//! assert_eq!(animator.current_values(), &Style { elevation: 0.0, scale: 1.0 }); // No time elapsed
93//! animator.advance(0.25);
94//! assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.0 });
95//! animator.set_state(&State::Pressed); // Change state before animation is finished
96//! assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.0 }); // No time elapsed
97//! animator.advance(0.05);
98//! assert_eq!(animator.current_values(), &Style { elevation: 2.5, scale: 1.05 });
99//! ```
100//!
101//! The [`Clone`], [`Default`] and [`PartialEq`] traits **are all** required for any type to be used
102//! as an animator state. [`State`] is an alias for
103//! [Enum](https://docs.rs/enum-map/latest/enum_map/trait.Enum.html), and currently required for the
104//! [`EnumStateAnimator`] and [`animator`] macro.
105//!
106//! The `default(state, props)` line is not required, but supported for specific and relatively
107//! common cases where the default resting values _for that animation_ do not match the [`Default`]
108//! implementation for the corresponding `struct`, or in the (less common) case that the default
109//! _state_ for the animation should be different from the default enum member. In the above
110//! example, the derived `Default` implementation would give a `scale` of `0.0`, but the normal
111//! scale of the button should be `1.0`, so we override the default.
112//!
113//! The same `default` term _within_ an animation state has a different meaning, and is interpreted
114//! as "use the default (for this animator) values for this keyframe". This helps avoid repetitive,
115//! copy-paste code for default/idle states that should simply return the widget to its base state.
116//!
117//! Note how the `Hovered` state specifies a `scale` that is the same as the default. The reason for
118//! this is to tell the animator that when transitioning from `Pressed` back to `Hovered`, it should
119//! revert the scale transform used for `Pressed`. Mina does **not** assume that undefined values in
120//! a keyframe should use defaults, and this is a very important property for merged timelines and
121//! more advanced animations in general. If a keyframe is missing one or more properties, those
122//! properties are _ignored_:
123//! - If there are earlier or later keyframes that do specify them, then it will animate smoothly
124//! between those keyframes. This applies to both [`Timeline`] and [`StateAnimator`].
125//! - If any given state does not specify any keyframes at all with some properties, then the
126//! properties will _not animate_ when in that state; they will remain at whichever values the
127//! previous state left them in.
128//!
129//! The actual implementation of `elevation` and `scale` are up to the underlying GUI. Mina doesn't
130//! care about the meaning of these properties, it just animates their values; the plumbing will
131//! vary with the specific GUI in use. Future updates may include standard integrations with those
132//! GUIs, but for now, the [examples](https://github.com/focustense/mina/tree/main/examples)
133//! directory serves as the unofficial integration how-to guide, as well as the repository for more
134//! complex and interesting uses of the API.
135//!
136//! # Event Loop
137//!
138//! Mina doesn't use its own event loop, so that it can instead be integrated into the event loop of
139//! whichever GUI is actually in use. This also allows global customizations - for example, stopping
140//! all animations when a game is paused, or playing them in slow motion during some key event.
141//!
142//! In most cases, establishing the event loop is a one- or two-line function. Refer to the
143//! [examples](https://github.com/focustense/mina/tree/main/examples) for framework-specific
144//! patterns.
145
146pub mod prelude;
147
148pub use mina_core::{
149 animator::{EnumStateAnimator, State, StateAnimator, StateAnimatorBuilder},
150 easing::{Easing, EasingFunction},
151 interpolation::Lerp,
152 timeline::{
153 Keyframe, KeyframeBuilder, MergedTimeline, Repeat, Timeline,
154 TimelineBuilder, TimelineConfiguration,
155 },
156};
157
158#[doc(hidden)]
159pub use mina_core::{
160 time_scale::TimeScale,
161 timeline::{prepare_frame, TimelineBuilderArguments, TimelineOrBuilder},
162 timeline_helpers::SubTimeline,
163};
164
165/// Configures and creates a [`StateAnimator`] for an [`Animate`](macro@Animate) type.
166///
167/// Animators hold one or more [`Timeline`] instances mapped to particular state values and will
168/// automatically switch and blend timelines when the state is changed. The `animator` macro uses a
169/// CSS-like syntax to define the states and timelines.
170///
171/// Use of this macro requires both an [`Animate`] type for the values/timeline and a
172/// [`State`] derived type for the state (note: `State` is a re-export of the `Enum` derive macro
173/// from the [`enum-map`](https://crates.io/crates/enum-map) crate; you will need to add this crate
174/// to the downstream project in order for `State` to compile. In addition, the state type must
175/// implement [`Clone`](std::clone::Clone), [`Default`](std::default::Default) and
176/// [`PartialEq`](std::cmp::PartialEq).
177///
178/// # Example
179///
180/// ```
181/// use mina::prelude::*;
182///
183/// #[derive(Clone, Default, PartialEq, State)]
184/// enum State {
185/// #[default] Idle,
186/// Active,
187/// }
188///
189/// #[derive(Animate, Clone, Debug, Default, PartialEq)]
190/// struct Style {
191/// alpha: f32,
192/// size: u16,
193/// }
194///
195/// let mut animator = animator!(Style {
196/// default(State::Idle, { alpha: 0.5, size: 60 }),
197/// State::Idle => 2s Easing::OutQuad to default,
198/// State::Active => 1s Easing::Linear to { alpha: 1.0, size: 80 }
199/// });
200///
201/// animator.advance(12.0);
202/// assert_eq!(animator.current_values(), &Style { alpha: 0.5, size: 60 });
203/// animator.set_state(&State::Active);
204/// assert_eq!(animator.current_values(), &Style { alpha: 0.5, size: 60 });
205/// animator.advance(0.5);
206/// assert_eq!(animator.current_values(), &Style { alpha: 0.75, size: 70 });
207/// animator.set_state(&State::Idle);
208/// assert_eq!(animator.current_values(), &Style { alpha: 0.75, size: 70 });
209/// animator.advance(0.8);
210/// assert_eq!(animator.current_values(), &Style { alpha: 0.554, size: 62 });
211/// animator.advance(1.2);
212/// assert_eq!(animator.current_values(), &Style { alpha: 0.5, size: 60 });
213/// ```
214pub use mina_macros::animator;
215
216/// Sets up a type for animation.
217///
218/// Animatable types gain two functions:
219/// - `timeline()` creates a [`TimelineBuilder`] that can be used to build a single animation
220/// [`Timeline`]. Timelines provide the interpolates values of the animatable type at any
221/// arbitrary point in time.
222/// - `keyframe()` creates a [`KeyframeBuilder`] which is used to provide [`Keyframe`] instances to
223/// the timeline builder. Keyframes specify the exact values at a specific point in the timeline.
224///
225/// In addition, a specific timeline type is generated with a `Timeline` suffix; for example, if the
226/// name of the type is `Style`, then the generated timeline type will be `StyleTimeline`. This type
227/// will have the same visibility as the animatable type, and can be used directly to store the
228/// timeline, or an animator based on the timeline, without boxing.
229///
230/// Making a type animatable also allows it to be used with the [`animator`](macro@animator) macro.
231///
232/// The following requirements apply to any type decorated with `#[derive(Animate}]`:
233///
234/// 1. Must be a `struct`. Tuple and `enum` types are not supported.
235/// 2. Must implement the [`Clone`](std::clone::Clone) and [`Default`](std::default::Default)
236/// traits.
237/// 3. All _animated_ fields must implement [`Lerp`].
238/// - A blanket implementation is provided for all primitive numeric types.
239/// - Other types may need explicit implementations and/or a newtype for unowned types.
240/// - **To exclude fields** from animation, either because it is not `Lerp`able or simply because
241/// it is intended to be constant, add the `#[animate]` helper attribute to all fields which
242/// _should_ be animated; any remaining fields not decorated will be ignored.
243/// 4. Nested structures, `Option` fields, etc. are allowed, but will be treated as black-box, which
244/// means the actual type of the field (e.g. the entire `struct`) must meet the `Lerp`
245/// requirement above. This can be the desired behavior for a limited number of complex types
246/// such as vectors or colors, but usually flat structs are more appropriate.
247/// 5. Generic types are not supported (for now) at the `struct` level, although the individual
248/// fields can be generic.
249///
250/// # Example
251///
252/// ```
253/// use mina::prelude::*;
254///
255/// #[derive(Animate, Clone, Debug, Default, PartialEq)]
256/// struct Style {
257/// alpha: f32,
258/// size: u16,
259/// }
260///
261/// let timeline = Style::timeline()
262/// .duration_seconds(5.0)
263/// .delay_seconds(1.0)
264/// .keyframe(Style::keyframe(1.0).alpha(1.0).size(25))
265/// .build();
266///
267/// let mut values = Style::default();
268/// timeline.update(&mut values, 3.0);
269/// assert_eq!(values, Style { alpha: 0.4, size: 10 });
270/// ```
271pub use mina_macros::Animate;
272
273/// Configures and creates a [`Timeline`] for an [`Animate`](macro@Animate) type.
274///
275/// Provides a more ergonomic, CSS-like alternative to the builder syntax using
276/// [`TimelineConfiguration`] and [`TimelineBuilder`], producing the same result. Requires an
277/// [`Animate`] type for the values.
278///
279/// # Example
280///
281/// ```
282/// use mina::prelude::*;
283///
284/// #[derive(Animate, Clone, Debug, Default, PartialEq)]
285/// struct Style {
286/// alpha: f32,
287/// size: u16,
288/// }
289///
290/// let timeline = timeline!(Style 2s reverse Easing::Out
291/// from { alpha: 0.5, size: 50 }
292/// to { alpha: 1.0, size: 100 });
293///
294/// let mut values = Style::default();
295/// timeline.update(&mut values, 0.25);
296/// assert_eq!(values, Style { alpha: 0.578125, size: 58 });
297/// timeline.update(&mut values, 0.5);
298/// assert_eq!(values, Style { alpha: 0.75, size: 75 });
299/// timeline.update(&mut values, 1.0);
300/// assert_eq!(values, Style { alpha: 1.0, size: 100 });
301/// timeline.update(&mut values, 1.25);
302/// assert_eq!(values, Style { alpha: 0.921875, size: 92 });
303/// timeline.update(&mut values, 1.5);
304/// assert_eq!(values, Style { alpha: 0.75, size: 75 });
305/// timeline.update(&mut values, 2.0);
306/// assert_eq!(values, Style { alpha: 0.5, size: 50 });
307/// ```
308pub use mina_macros::timeline;