Skip to main content

signals/
signals.rs

1use motion_canvas_rs::prelude::*;
2use std::time::Duration;
3
4fn main() {
5    let mut project = Project::default()
6        .with_title("Signals")
7        .with_dimensions(800, 600)
8        .close_on_finish();
9
10    // 1. Create independent signals for our "Coordinate System"
11    // These are NOT tied to any node yet.
12    let x_var = Signal::new(0.0f32);
13    let y_var = Signal::new(0.0f32);
14
15    // 2. Create the Axis lines (Fixed)
16    let x_axis = Line::default()
17        .with_start(Vec2::new(-200.0, 0.0))
18        .with_end(Vec2::new(200.0, 0.0))
19        .with_stroke(Color::rgba8(255, 255, 255, 100), 2.0);
20
21    let y_axis = Line::default()
22        .with_start(Vec2::new(0.0, -200.0))
23        .with_end(Vec2::new(0.0, 200.0))
24        .with_stroke(Color::rgba8(255, 255, 255, 100), 2.0);
25
26    // Group the axes at the center
27    let axes = GroupNode::default()
28        .with_nodes(vec![Box::new(x_axis), Box::new(y_axis)])
29        .with_position(Vec2::new(400.0, 300.0));
30
31    // 3. Create a representation of the point (P)
32    let point = Circle::default()
33        .with_radius(8.0)
34        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
35
36    // 4. Create text labels that show the values
37    let x_label = TextNode::default()
38        .with_text("X: 0")
39        .with_font_size(24.0)
40        .with_fill(Color::WHITE);
41
42    let y_label = TextNode::default()
43        .with_text("Y: 0")
44        .with_font_size(24.0)
45        .with_fill(Color::WHITE);
46
47    // 5. Use .bind() to derive node properties from our variables
48    // This makes node properties react to x_var and y_var.
49
50    // Circle position follows both
51    let y_clone = y_var.clone();
52    let circle_pos_link = point.position.bind(x_var.clone(), move |x| {
53        Vec2::new(x + 400.0, y_clone.get() + 300.0)
54    });
55
56    // X Label follows X but stays at bottom
57    let x_label_pos_link = x_label
58        .position
59        .bind(x_var.clone(), |x| Vec2::new(x + 400.0, 550.0));
60
61    // Y Label stays at left but follows Y
62    let y_label_pos_link = y_label
63        .position
64        .bind(y_var.clone(), |y| Vec2::new(75.0, y + 300.0));
65
66    // Dynamic text update for labels
67    let x_text_link = x_label.text.bind(x_var.clone(), |x| format!("X: {:.1}", x));
68    let y_text_link = y_label.text.bind(y_var.clone(), |y| format!("Y: {:.1}", y));
69
70    // Add everything to the scene
71    project.scene.add(Box::new(axes));
72    project.scene.add(Box::new(point));
73    project.scene.add(Box::new(x_label));
74    project.scene.add(Box::new(y_label));
75
76    // Add the links as "invisible" nodes that just perform the sync
77    project.scene.add(Box::new(circle_pos_link));
78    project.scene.add(Box::new(x_label_pos_link));
79    project.scene.add(Box::new(y_label_pos_link));
80    project.scene.add(Box::new(x_text_link));
81    project.scene.add(Box::new(y_text_link));
82
83    // 6. Animate our independent variables!
84    project.scene.video_timeline.add(chain![
85        // Move X
86        x_var
87            .to(200.0, Duration::from_secs(1))
88            .ease(easings::cubic_out),
89        // Wait
90        wait(Duration::from_millis(500)),
91        // Move Y
92        y_var
93            .to(-150.0, Duration::from_secs(1))
94            .ease(easings::back_out),
95        // Move both together
96        all![
97            x_var
98                .to(-250.0, Duration::from_secs(2))
99                .ease(easings::expo_in_out),
100            y_var
101                .to(100.0, Duration::from_secs(2))
102                .ease(easings::sine_in_out),
103        ],
104        // Reset
105        all![
106            x_var.to(0.0, Duration::from_secs(1)),
107            y_var.to(0.0, Duration::from_secs(1)),
108        ],
109        // Final wait to see the result
110        wait(Duration::from_secs(1)),
111    ]);
112
113    project.show().expect("Failed to render");
114}