Building Basic Animation Component
Basic Structure Design
Let's first look at how to build a basic animation component:
1// 1. First declare the component structure
2#[derive(Live, Widget)]
3pub struct TypingAnimation {
4 #[deref] view: View, // Base view component
5 #[animator] animator: Animator, // Animation controller
6
7 // Animation parameters
8 #[live(0.65)] animation_duration: f64,
9 #[live(10.0)] swing_amplitude: f64,
10 #[live(3.0)] swing_base: f64,
11
12 // Runtime state
13 #[rust] next_frame: Option<NextFrame>,
14 #[rust] animation_start_time: f64,
15 #[rust] is_animating: bool,
16}
This structure provides the necessary infrastructure for our animation component.
Animation State Definition
In Makepad, we use the live_design! macro to declare animation states:
1live_design! {
2 TypingAnimation = {{TypingAnimation}} {
3 // Basic property configuration
4 width: Fit,
5 height: Fit,
6
7 // Animation state definition
8 animator: {
9 // Animation state group
10 circle1 = {
11 default: down, // Default state
12 down = { // Down movement state
13 redraw: true,
14 from: {all: Forward {duration: 0.325}}
15 ease: InOutQuad
16 apply: {content = { circle1 = { margin: {top: 10.0} }}}
17 }
18 up = { // Up movement state
19 redraw: true,
20 from: {all: Forward {duration: 0.325}}
21 ease: InOutQuad
22 apply: {content = { circle1 = { margin: {top: 3.0} }}}
23 }
24 }
25 }
26 }
27}
In Makepad's Live DSL, animator is a special property used to define component animation states and behaviors. Let's dive deep into its syntax and structure.
- Animation state groups serve as namespaces for organizing related animation states. A state group is effectively an animation track (track). When you want to animate different properties of the same object simultaneously, you need to create separate tracks for each animated property.
1animator: {
2 circle1_position = { // Position track
3 default: down,
4 down = { /* Control position animation states */ }
5 }
6 circle1_opacity = { // Opacity track
7 default: visible,
8 hidden = { /* Control opacity animation states */ }
9 }
10}
- Each state contains three main parts:
- from - Defines animation timing characteristics
- ease - Defines animation easing function
- apply - Defines final property values for the state
1// from
2from: {
3 all: Forward {duration: 0.2} // Play forward once
4 all: Reverse { // Play in reverse once
5 duration: 0.2,
6 end: 1.0
7 }
8 all: Loop { // Loop playback
9 duration: 0.2,
10 end: 1.0
11 }
12 all: BounceLoop { // Loop back and forth
13 duration: 0.2,
14 end: 1.0
15 }
16 all: Snap // Instant switch, no animation
17}
18// ease
19// - `OutQuad`/`OutCubic`: Suitable for natural motion
20// - `InOutQuad`: Suitable for reversible animations
21// - `Linear`: Suitable for continuous animations like rotation
22ease: Linear // Linear
23ease: InQuad // Quadratic acceleration
24ease: OutQuad // Quadratic deceleration
25ease: InOutQuad // Quadratic ease-in-out
26ease: Bezier { // Bezier curve
27 cp0: 0.0,
28 cp1: 0.0,
29 cp2: 1.0,
30 cp3: 1.0
31}
32
33// apply
34apply: {
35 opacity: 1.0,
36 scale: 1.2,
37 color: #f00,
38 position: vec2(100.0, 0.0)
39}
Implementing Animation Logic
The core of animation lies in state management and frame updates:
1impl Widget for TypingAnimation {
2 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3 match event {
4 Event::NextFrame(ne) => {
5 // Handle animation frame updates
6 if let Some(next_frame) = self.next_frame {
7 if ne.set.contains(&next_frame) {
8 self.update_animation(cx, ne.time);
9 self.next_frame = Some(cx.new_next_frame());
10 }
11 }
12 }
13 // ... Other event handling
14 }
15 }
16}
Note: In Makepad, animations should be based on NextFrame, not Timer. This is because Timer relies on underlying OS APIs, which poses cross-platform risks.
Methods to trigger animations in code:
1// Instant state switch
2self.animator_cut(cx, &[id!(hover), id!(on)]);
3
4// Animate transition to state
5self.animator_play(cx, &[id!(hover), id!(on)]);
6
7// Toggle state based on condition
8self.animator_toggle(
9 cx,
10 is_hovered, // Condition
11 Animate::Yes, // Whether animation is needed
12 &[id!(hover), id!(on)], // State when condition is true
13 &[id!(hover), id!(off)] // State when condition is false
14);
Handling animation events:
1fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
2 // Handle animation system events
3 if self.animator_handle_event(cx, event).must_redraw() {
4 // Animation needs redraw
5 self.redraw(cx);
6 }
7
8 // Check animation state
9 if self.animator_in_state(cx, &[id!(hover), id!(on)]) {
10 // Currently in hover state
11 }
12}
Animation Control Interface
It's good practice to provide control interfaces for animation components:
1impl TypingAnimationRef {
2 // Start animation
3 pub fn start(&self, cx: &mut Cx) {
4 if let Some(mut inner) = self.borrow_mut() {
5 inner.start(cx);
6 }
7 }
8
9 // Stop animation
10 pub fn stop(&self, cx: &mut Cx) {
11 if let Some(mut inner) = self.borrow_mut() {
12 inner.stop(cx);
13 }
14 }
15
16 // Configure animation parameters
17 pub fn set_animation_speed(&self, cx: &mut Cx, duration: f64) {
18 if let Some(mut inner) = self.borrow_mut() {
19 inner.animation_duration = duration;
20 }
21 }
22}