Concepts

Animation System Overview

Makepad's animation system is a declarative animation framework that manages UI animations through the concepts of States and Transitions.

Let's gain a deeper understanding of the entire system through a "typing" animation component.

Key Concepts

Let's first look at the Animator's core data structure:

1pub struct Animator {
2    // Whether to ignore missing animation states
3    pub ignore_missing: bool,
4    // Reference to animation definition in Live DSL
5    pub live_ptr: LiveRef,
6    // Current animation state data
7    pub state: Option<Vec<LiveNode>>,
8    // Next frame scheduler
9    pub next_frame: NextFrame,
10}

This structure appears simple but is actually a very elegant design:

  1. live_ptr - Maintains reference to animation definition in Live DSL, allowing Animator to lookup and load animation state definitions when needed
  2. state - Stores currently executing animation state, including all ongoing interpolation calculations
  3. next_frame - Used for scheduling the next animation frame render

Animation State Management

This system works based on state transitions:

  1. Each state is identified by a pair of LiveId identifiers ([LiveId; 2])
  2. State transitions are handled through two main methods:
    • animate_to(): Smooth transition to new state
    • cut_to(): Direct switch to new state
1impl Animator {
2    // Hard switch to specified state
3    pub fn cut_to(&mut self, cx: &mut Cx, state_pair: &[LiveId; 2], index: usize, nodes: &[LiveNode]) {
4        // Get current state or create new state
5        let mut state = self.swap_out_state().unwrap_or(Vec::new());
6        let track = state_pair[0];
7
8        // Initialize state structure
9        if state.len() == 0 {
10            state.push_live(live!{
11                tracks: {},
12                state: {}
13            });
14        }
15
16        // Update track state
17        state.replace_or_insert_last_node_by_path(0, &[live_id!(tracks).as_field(), track.as_field()],
18            live_object!{
19                [track]: {state_id: (state_pair[1]), ended: 1}
20            }
21        );
22
23        // Apply state values
24        let mut path = Vec::new();
25        path.push(live_id!(state).as_field());
26
27        // ... Code for reading and applying state values ...
28
29        self.swap_in_state(state);
30    }
31
32    // Animate transition to specified state
33    pub fn animate_to(&mut self, cx: &mut Cx, state_pair: &[LiveId; 2], index: usize, nodes: &[LiveNode]) {
34        // Similar to cut_to, but creates interpolation animation
35        let mut state = self.swap_out_state().unwrap_or(Vec::new());
36
37        // ... Set up animation track
38        state.replace_or_insert_last_node_by_path(0, &[live_id!(tracks).as_field(), track.as_field()],
39            live_object!{
40                [track]: {
41                    state_id: (state_pair[1]),
42                    ended: 0,
43                    time: void
44                }
45            }
46        );
47
48        // ... Create interpolation animation
49
50        self.swap_in_state(state);
51        self.next_frame = cx.new_next_frame();
52    }
53}

Key Animation Update Mechanism Code Structure

1impl Animator {
2    pub fn handle_event(&mut self, cx: &mut Cx, event: &Event) -> AnimatorAction {
3        if let Event::NextFrame(nf) = event {
4            // Check if it's our next frame
5            if !nf.set.contains(&self.next_frame) {
6                return AnimatorAction::None
7            }
8
9            // Update all active animation tracks
10            let state_nodes = self.state.as_mut().unwrap();
11            let mut ended = true;
12            let mut redraw = false;
13
14            // ... Iterate and update all animation tracks
15
16            // If animations are still running, request next frame
17            if !ended {
18                self.next_frame = cx.new_next_frame();
19            }
20
21            return AnimatorAction::Animating {redraw}
22        }
23        AnimatorAction::None
24    }
25}

Keyframe Interpolation Calculation

1impl Animator {
2    fn update_timeline_value(
3        cx: &mut Cx,
4        index: usize,
5        nodes: &mut [LiveNode],
6        ext_time: f64
7    ) -> (bool, bool) {
8        // Extract keyframes
9        let mut prev_kf: Option<KeyFrame> = None;
10
11        // Calculate interpolation at current time point
12        for key_frame in key_frames {
13            if time >= prev_kf.time && time <= key_frame.time {
14                let normalized_time = (time - prev_kf.time) /
15                    (key_frame.time - prev_kf.time);
16
17                // Apply easing function
18                let mix = key_frame.ease.map(normalized_time);
19
20                // Interpolate based on value type
21                let new_val = match (prev_kf.value, key_frame.value) {
22                    // Numeric interpolation
23                    (LiveValue::Float64(a), LiveValue::Float64(b)) => {
24                        LiveValue::Float64((b - a) * mix + a)
25                    },
26                    // Color interpolation
27                    (LiveValue::Color(a), LiveValue::Color(b)) => {
28                        LiveValue::Color(Vec4::from_lerp(
29                            Vec4::from_u32(a),
30                            Vec4::from_u32(b),
31                            mix as f32
32                        ).to_u32())
33                    },
34                    // ... Other type interpolations
35                };
36
37                // Update current value
38                nodes[last_child_index].value = new_val;
39                return (ended, redraw)
40            }
41        }
42    }
43}

Animation track (track) system

The animation track (track) system allows for multiple properties to animate independently.