Skip to main content

Project

Struct Project 

Source
pub struct Project {
Show 16 fields pub width: u32, pub height: u32, pub fps: u32, pub title: String, pub scene: BaseScene, pub output_path: PathBuf, pub use_cache: bool, pub use_ffmpeg: bool, pub use_gpu: bool, pub background_color: Color, pub close_on_finish: bool, pub current_time: Duration, pub paused: bool, pub speed: f32, pub timeline: Timeline, pub cache_write_interval: u32,
}
Expand description

The central configuration and state for a motion canvas animation.

Project stores all metadata about the animation, including dimensions, frame rate, and export settings. It also owns the BaseScene which contains the actual animation nodes.

Fields§

§width: u32

Target width of the animation.

§height: u32

Target height of the animation.

§fps: u32

Frames per second.

§title: String

Human-readable title of the project.

§scene: BaseScene

The root scene orchestration node.

§output_path: PathBuf

Directory where exported frames and videos will be saved.

§use_cache: bool

Whether to use frame-level caching during export.

§use_ffmpeg: bool

Whether to automatically attempt FFmpeg encoding after export.

§use_gpu: bool

Whether to use GPU acceleration for rendering.

§background_color: Color

Background clear color for the animation.

§close_on_finish: bool

If true, the playback window will close automatically when the animation ends.

§current_time: Duration

The current playback/export time.

§paused: bool

Playback state (paused or playing).

§speed: f32

Playback speed multiplier.

§timeline: Timeline

The master playback timeline state.

§cache_write_interval: u32

The number of frames between incremental cache manifest writes to disk.

Implementations§

Source§

impl Project

Source

pub fn new(width: u32, height: u32) -> Self

Creates a new project with the given dimensions and default settings.

Examples found in repository?
examples/world_map.rs (line 433)
429fn main() {
430    let w = 800u32;
431    let h = 600u32;
432
433    let mut project = Project::new(w, h)
434        .with_fps(60)
435        .with_title("World Map")
436        .with_background(SKY_BG)
437        .close_on_finish();
438
439    // ── Map (rendered at native SVG size for crispness) ──
440    let map = SvgNode::default()
441        .with_position(Vec2::new(MAP_CX, MAP_CY))
442        .with_path("./examples/images/world.svg")
443        .with_size(Vec2::new(MAP_W, MAP_H))
444        .with_opacity(0.0);
445
446    // ── Clouds — 16 total from 3 source PNGs with variants ──
447    let mut cloud_nodes: Vec<ImageNode> = Vec::new();
448    for def in &CLOUDS {
449        let mut node = ImageNode::default()
450            .with_position(Vec2::new(def.x, def.y))
451            .with_path(&format!("./examples/images/{}", def.img))
452            .with_size(Vec2::new(def.w, def.h))
453            .with_opacity(def.opacity);
454        if def.flip_x {
455            node = node.with_scale_xy(Vec2::new(-1.0, 1.0));
456        }
457        cloud_nodes.push(node);
458    }
459
460    // ── Camera ──
461    let camera = CameraNode::default()
462        .with_size(Vec2::new(w as f32, h as f32))
463        .with_position(Vec2::new(MAP_CX, MAP_CY))
464        .with_zoom(1.0)
465        .with_centered(true);
466
467    // ── Plane ──
468    let plane = SvgNode::default()
469        .with_position(Vec2::new(LANDMARKS[0].x, LANDMARKS[0].y))
470        .with_path("./examples/images/plane.svg")
471        .with_size(Vec2::new(12.0, 12.0))
472        .with_anchor(Vec2::new(2.0, -2.65))
473        .with_scale(0.0)
474        .with_opacity(0.0);
475
476    // ── Landmark pins + name labels ──
477    let mut pins: Vec<Circle> = Vec::new();
478    let mut name_labels: Vec<TextNode> = Vec::new();
479    for lm in &LANDMARKS {
480        let pin = Circle::default()
481            .with_position(Vec2::new(lm.x, lm.y))
482            .with_radius(0.0)
483            .with_fill(Palette::RED)
484            .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 180), 2.0)
485            .with_opacity(0.0);
486        pins.push(pin);
487
488        // Country name below pin
489        let name_lbl = TextNode::default()
490            .with_position(Vec2::new(lm.x, lm.y + 12.0))
491            .with_text(lm.name)
492            .with_font_size(10.0)
493            .with_fill(Palette::DARK_GRAY)
494            .with_opacity(0.0);
495        name_labels.push(name_lbl);
496    }
497
498    // ── Route lines (straight between consecutive landmarks) ──
499    let mut route_lines: Vec<Line> = Vec::new();
500    for i in 0..LANDMARKS.len() - 1 {
501        let from = &LANDMARKS[i];
502        let line = Line::default()
503            .with_start(Vec2::new(from.x, from.y))
504            .with_end(Vec2::new(from.x, from.y))
505            .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 180), 2.0)
506            .with_opacity(0.0);
507        route_lines.push(line);
508    }
509
510    // ── Build Scene Tree ──
511    // Order: map → route lines → plane → pins → labels (pins on top of lines)
512    let mut camera_children: Vec<Box<dyn Node>> = Vec::new();
513    camera_children.push(Box::new(map.clone()));
514    // Route lines first (behind pins)
515    for rl in &route_lines {
516        camera_children.push(Box::new(rl.clone()));
517    }
518    // Plane between routes and pins
519    camera_children.push(Box::new(plane.clone()));
520    // Pins on top of route lines
521    for pin in &pins {
522        camera_children.push(Box::new(pin.clone()));
523    }
524
525    for nl in &name_labels {
526        camera_children.push(Box::new(nl.clone()));
527    }
528
529    // Clouds on top of everything (so they cover the map initially)
530    for cn in &cloud_nodes {
531        camera_children.push(Box::new(cn.clone()));
532    }
533
534    let camera = camera.with_nodes(camera_children);
535    project.scene.add(&camera);
536
537    // ═══════════════════════════════════════════════════
538    //  ANIMATION TIMELINE
539    // ═══════════════════════════════════════════════════
540
541    // Phase 1: Intro — clouds everywhere, then zoom in / scatter clouds / reveal map
542    let mut phase1_anims: Vec<AnyAnimation> = Vec::new();
543
544    // Camera zoom
545    phase1_anims.push(
546        camera
547            .zoom
548            .to(2.25, Duration::from_secs(3))
549            .ease(easings::cubic_in_out)
550            .into(),
551    );
552    // Map fade in
553    phase1_anims.push(
554        map.opacity
555            .to(1.0, Duration::from_secs(2))
556            .ease(easings::cubic_out)
557            .into(),
558    );
559
560    // Scatter all clouds
561    for (i, cn) in cloud_nodes.iter().enumerate() {
562        let def = &CLOUDS[i];
563        phase1_anims.push(
564            cn.position
565                .to(Vec2::new(def.exit_x, def.exit_y), Duration::from_secs(3))
566                .ease(easings::cubic_in)
567                .into(),
568        );
569        phase1_anims.push(
570            cn.opacity
571                .to(0.0, Duration::from_secs(2))
572                .ease(easings::cubic_in)
573                .into(),
574        );
575    }
576
577    let phase1_intro: AnyAnimation = chain![
578        // Everything happens at once: zoom, clouds scatter, map reveals, title appears
579        all(phase1_anims),
580        wait(Duration::from_millis(500)),
581    ];
582
583    // Phase 2: Pan camera to first landmark and show plane
584    let first = &LANDMARKS[0];
585    let phase2_start: AnyAnimation = chain![
586        camera
587            .position
588            .to(Vec2::new(first.x, first.y), Duration::from_secs(2))
589            .ease(easings::cubic_in_out),
590        camera
591            .zoom
592            .to(5.0, Duration::from_secs(1))
593            .ease(easings::cubic_out),
594        // Show first pin + labels
595        all![
596            pins[0].opacity.to(1.0, Duration::from_millis(400)),
597            pins[0]
598                .radius
599                .to(3.0, Duration::from_millis(600))
600                .ease(easings::elastic_out),
601            name_labels[0].opacity.to(1.0, Duration::from_millis(400)),
602        ],
603        // Show plane
604        plane.opacity.to(1.0, Duration::from_millis(300)),
605        wait(Duration::from_millis(800)),
606    ];
607
608    // Phase 3: Tour through each country (scale in -> move -> scale out -> show landmark)
609    let mut tour_legs: Vec<AnyAnimation> = Vec::new();
610    for i in 0..LANDMARKS.len() - 1 {
611        let from = &LANDMARKS[i];
612        let to = &LANDMARKS[i + 1];
613        let dur = tour_duration(i);
614
615        // Compute heading angle from straight line
616        let dx = to.x - from.x;
617        let dy = to.y - from.y;
618        let angle = dy.atan2(dx) + (std::f32::consts::PI);
619
620        let leg: AnyAnimation = chain![
621            // 1. Scale plane in at the start of the leg
622            all![
623                plane
624                    .rotation
625                    .to(angle, Duration::from_millis(200))
626                    .ease(easings::cubic_out),
627                plane
628                    .scale
629                    .to(Vec2::splat(1.0), Duration::from_millis(300))
630                    .ease(easings::cubic_out),
631                // Hide current landmark name as we depart
632                name_labels[i]
633                    .opacity
634                    .to(0.0, Duration::from_millis(200))
635                    .ease(easings::cubic_out),
636            ],
637            // 2. Move plane to destination
638            all![
639                // Show route line
640                route_lines[i].opacity.to(1.0, Duration::from_millis(100)),
641                // Draw line to destination
642                route_lines[i]
643                    .end
644                    .to(Vec2::new(to.x, to.y), dur)
645                    .ease(easings::cubic_in_out),
646                all![
647                    // Move plane to destination
648                    plane
649                        .position
650                        .to(Vec2::new(to.x, to.y), dur)
651                        .ease(easings::cubic_in_out),
652                    // Camera follows to destination
653                    camera
654                        .position
655                        .to(Vec2::new(to.x, to.y), dur.add(Duration::from_millis(500)))
656                        .ease(easings::cubic_in_out),
657                ]
658            ],
659            // 3. Scale plane out at the destination
660            plane
661                .scale
662                .to(Vec2::splat(0.0), Duration::from_millis(300))
663                .ease(easings::cubic_in),
664            // 4. Reveal destination pin + name
665            all![
666                pins[i + 1].opacity.to(1.0, Duration::from_millis(300)),
667                pins[i + 1]
668                    .radius
669                    .to(3.0, Duration::from_millis(500))
670                    .ease(easings::elastic_out),
671                name_labels[i + 1]
672                    .opacity
673                    .to(1.0, Duration::from_millis(300)),
674            ],
675            // 5. Wait a bit for viewer to read
676            wait(Duration::from_millis(600)),
677            // 6. Name disappears before plane scales in for next leg
678            name_labels[i + 1]
679                .opacity
680                .to(0.0, Duration::from_millis(200))
681                .ease(easings::cubic_out),
682        ];
683        tour_legs.push(leg);
684    }
685
686    let phase3_tour = chain(tour_legs);
687
688    // Phase 4: Zoom out to show full map
689    let phase4_finale: AnyAnimation = chain![
690        // Hide the last landmark's name
691        name_labels[LANDMARKS.len() - 1]
692            .opacity
693            .to(0.0, Duration::from_millis(200)),
694        wait(Duration::from_millis(300)),
695        all![
696            camera
697                .position
698                .to(Vec2::new(MAP_CX, MAP_CY), Duration::from_secs(3))
699                .ease(easings::cubic_in_out),
700            camera
701                .zoom
702                .to(1.0, Duration::from_secs(3))
703                .ease(easings::sine_in_out),
704        ],
705        // Pulse all pins (dynamic loop)
706        {
707            let mut pulse_anims: Vec<AnyAnimation> = Vec::new();
708            for pin in &pins {
709                pulse_anims.push(
710                    pin.radius
711                        .to(10.0, Duration::from_millis(300))
712                        .ease(easings::elastic_out)
713                        .into(),
714                );
715            }
716            sequence(Duration::from_millis(60), pulse_anims)
717        },
718        plane.opacity.to(0.0, Duration::from_millis(500)),
719        wait(Duration::from_secs(3)),
720    ];
721
722    project.scene.video_timeline.add(chain![
723        phase1_intro,
724        phase2_start,
725        phase3_tour,
726        phase4_finale,
727    ]);
728
729    project.show().expect("Failed to render");
730}
Source§

impl Project

Source

pub fn with_fps(self, fps: u32) -> Self

Sets the target frames per second.

Examples found in repository?
examples/math_animation.rs (line 7)
4fn main() {
5    // 1. Initialize Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_dimensions(500, 500)
9        .with_title("Math Animation")
10        .close_on_finish();
11
12    // 2. Create MathNode (Typst syntax)
13    let tex = MathNode::default()
14        .with_position(Vec2::new(250.0, 250.0))
15        .with_equation("y = a x^2")
16        .with_font_size(48.0)
17        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2));
18    project.scene.add(&tex);
19
20    // 3. Define Animation Sequence
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                tex.tex("y = a x^2 + b x", Duration::from_secs(1)),
25                tex.tex("e^(i pi) + 1 = 0", Duration::from_secs(1)),
26                tex.tex("y = a x^2", Duration::from_secs(1)),
27            ]
28        },
29        None,
30    ));
31
32    // 4. Run interactive preview
33    project.show().expect("Failed to render");
34}
More examples
Hide additional examples
examples/color_interpolation.rs (line 7)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(300, 300)
7        .with_fps(60)
8        .with_title("Color Interpolation")
9        .with_cache(true)
10        .close_on_finish();
11
12    let circle = Circle::default()
13        .with_position(Vec2::new(150.0, 150.0))
14        .with_radius(50.0)
15        .with_fill(Color::RED); // Red
16
17    project.scene.add(&circle);
18
19    let duration = Duration::from_secs(1);
20
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                circle.fill_paint.to(Paint::Solid(Color::YELLOW), duration),
25                circle.fill_paint.to(Paint::Solid(Color::GREEN), duration),
26                circle.fill_paint.to(Paint::Solid(Color::BLUE), duration),
27                circle.fill_paint.to(Paint::Solid(Color::RED), duration),
28            ]
29        },
30        None,
31    ));
32
33    // Show
34    project.show().expect("Failed to render");
35}
examples/getting_started.rs (line 7)
4fn main() {
5    // 1. Initialize the Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_cache(true)
9        .with_title("Getting Started")
10        .close_on_finish();
11
12    // 2. Define Nodes
13    let circle = Circle::default()
14        .with_position(Vec2::new(400.0, 300.0))
15        .with_radius(50.0)
16        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
17
18    let text = TextNode::default()
19        .with_position(Vec2::new(400.0, 450.0))
20        .with_text("Hello Rust")
21        .with_font_size(40.0)
22        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White-ish
23
24    // 3. Add Nodes to the Scene
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 4. Add Animations to the Timeline
29    project.scene.video_timeline.add(all![
30        circle.radius.to(100.0, Duration::from_secs(1)),
31        text.position
32            .to(Vec2::new(400.0, 500.0), Duration::from_secs(1)),
33    ]);
34
35    // 5. Show
36    project.show().expect("Failed to render");
37}
examples/export.rs (line 7)
4fn main() {
5    // 1. Initialize for Export
6    let mut project = Project::default()
7        .with_fps(30)
8        .with_ffmpeg(true)
9        .with_title("Export")
10        .with_output_path("output")
11        .close_on_finish();
12
13    // 2. Setup Nodes
14    let circle = Circle::default()
15        .with_position(Vec2::new(400.0, 300.0))
16        .with_radius(50.0)
17        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)); // Blue
18
19    let text = TextNode::default()
20        .with_position(Vec2::new(400.0, 50.0))
21        .with_text("Export Demo")
22        .with_font_size(40.0)
23        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White
24
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 3. Define Animations (Color and Font Size)
29    project.scene.video_timeline.add(all![
30        // Circle color and size
31        circle
32            .fill_paint
33            .to(
34                Paint::Solid(Color::rgb8(0xf2, 0xf2, 0xf2)),
35                Duration::from_secs(2)
36            )
37            .ease(easings::quad_in_out),
38        circle
39            .radius
40            .to(150.0, Duration::from_secs(2))
41            .ease(easings::elastic_out),
42        // Text font size
43        text.font_size
44            .to(50.0, Duration::from_secs(2))
45            .ease(easings::cubic_out),
46    ]);
47
48    // 4. Export (Renders frames and combines them into out.mkv)
49    println!("Starting export to {}...", project.output_path.display());
50    project.export().expect("Failed to export");
51}
examples/math_code.rs (line 6)
4fn main() {
5    let mut project = Project::default()
6        .with_fps(120)
7        .with_title("Math Code")
8        .close_on_finish();
9
10    let triangle = Polygon::default()
11        .with_position(Vec2::ZERO)
12        .with_anchor(Vec2::new(-1.0, -1.0))
13        .with_points(vec![
14            Vec2::new(0.0, 0.0),
15            Vec2::new(0.0, 200.0),
16            Vec2::new(200.0, 200.0),
17        ])
18        .with_fill(Color::rgb8(0x68, 0xab, 0xdf));
19
20    let lines = vec![
21        Line::default()
22            .with_start(Vec2::new(0.0, 0.0))
23            .with_end(Vec2::new(0.0, 200.0))
24            .with_stroke(Color::WHITE, 10.0),
25        Line::default()
26            .with_start(Vec2::new(0.0, 0.0))
27            .with_end(Vec2::new(200.0, 200.0))
28            .with_stroke(Color::WHITE, 10.0),
29        Line::default()
30            .with_start(Vec2::new(0.0, 200.0))
31            .with_end(Vec2::new(200.0, 200.0))
32            .with_stroke(Color::WHITE, 10.0),
33    ];
34
35    let triangle_line_group = GroupNode::default()
36        .with_nodes(vec![
37            triangle.clone_node(),
38            lines[0].clone_node(),
39            lines[1].clone_node(),
40            lines[2].clone_node(),
41        ])
42        .with_position(Vec2::new(400.0, 200.0))
43        .with_scale(0.0);
44
45    let pytagorean_theorem = MathNode::default()
46        .with_position(Vec2::new(150.0, 150.0))
47        .with_equation("a^2 + b^2 = c^2")
48        .with_font_size(10.0)
49        .with_fill(Color::GRAY)
50        .with_opacity(0.0);
51
52    let text_a = TextNode::default()
53        .with_text("a")
54        .with_fill(Color::GRAY)
55        .with_opacity(0.0)
56        .with_scale(0.7)
57        .with_position(Vec2::new(370.0, 300.0));
58
59    let text_b = TextNode::default()
60        .with_text("b")
61        .with_fill(Color::GRAY)
62        .with_opacity(0.0)
63        .with_scale(0.7)
64        .with_position(Vec2::new(500.0, 430.0));
65
66    let text_c = TextNode::default()
67        .with_text("c")
68        .with_fill(Color::GRAY)
69        .with_opacity(0.0)
70        .with_scale(0.7)
71        .with_position(Vec2::new(510.0, 260.0));
72
73    project.scene.add(&triangle_line_group);
74    project.scene.add(&pytagorean_theorem);
75    project.scene.add(&text_a);
76    project.scene.add(&text_b);
77    project.scene.add(&text_c);
78
79    project.scene.video_timeline.add(all![
80        all![triangle_line_group
81            .scale
82            .to(Vec2::ONE, Duration::from_secs(1)),],
83        all![
84            pytagorean_theorem.opacity.to(1.0, Duration::from_secs(1)),
85            pytagorean_theorem
86                .font_size
87                .to(32.0, Duration::from_secs(1)),
88        ],
89        sequence![
90            Duration::from_millis(300),
91            text_a.scale.to(Vec2::new(1.0, 1.0), Duration::from_secs(1)),
92            text_b.scale.to(Vec2::new(1.0, 1.0), Duration::from_secs(1)),
93            text_c.scale.to(Vec2::new(1.0, 1.0), Duration::from_secs(1)),
94            text_a.opacity.to(1.0, Duration::from_secs(1)),
95            text_b.opacity.to(1.0, Duration::from_secs(1)),
96            text_c.opacity.to(1.0, Duration::from_secs(1))
97        ]
98    ]);
99
100    project.show().expect("Failed to render");
101}
examples/advanced_flow.rs (line 7)
4fn main() {
5    // 1. Initialize Project with full API coverage
6    let mut project = Project::default()
7        .with_fps(120)
8        .with_gpu(true)
9        .with_cache(true)
10        .with_ffmpeg(true)
11        .with_output_path("output")
12        .with_title("Advanced Flow")
13        .close_on_finish();
14
15    // 2. Setup Nodes
16    let mut path = BezPath::new();
17    path.move_to((100.0, 300.0));
18    path.curve_to((250.0, 100.0), (550.0, 500.0), (700.0, 300.0));
19    let path_node = PathNode::default()
20        .with_path(path)
21        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 2.0);
22    let follower = Circle::default()
23        .with_position(Vec2::new(100.0, 300.0))
24        .with_radius(20.0)
25        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
26
27    // Showcase: Rect and Line
28    let background_rect = Rect::default()
29        .with_position(Vec2::new(400.0, 300.0))
30        .with_size(Vec2::new(760.0, 560.0))
31        .with_fill(Color::rgba8(0x33, 0x33, 0x33, 150))
32        .with_radius(20.0);
33
34    let divider_line = Line::default()
35        .with_start(Vec2::new(0.0, 300.0))
36        .with_end(Vec2::new(0.0, 300.0))
37        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 1.0);
38    let title_text = TextNode::default()
39        .with_position(Vec2::new(50.0, 50.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_text("Motion Canvas in Rust")
42        .with_font_size(40.0)
43        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
44        .with_font("JetBrains Mono");
45
46    let code_block = CodeNode::default()
47        .with_anchor(Vec2::new(-1.0, -1.0))
48        .with_position(Vec2::new(50.0, 415.0))
49        .with_language("rust")
50        .with_opacity(0.0);
51
52    let math_eq = MathNode::default()
53        .with_position(Vec2::new(150.0, 150.0))
54        .with_equation("f(x) = sin(x)")
55        .with_font_size(30.0)
56        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00)); // Yellow
57
58    let logo = ImageNode::default()
59        .with_position(Vec2::new(650.0, 150.0))
60        .with_path("./examples/images/motion-canvas-logo.png")
61        .with_size(Vec2::new(150.0, 150.0));
62
63    project.scene.video_timeline.add(all![
64        // Show code
65        chain![
66            code_block.opacity.to(1.0, Duration::from_secs(1)),
67            code_block.append("fn main() {\n", Duration::from_secs(1)),
68            code_block.append(
69                "    let mut engine = MotionCanvas::new();\n",
70                Duration::from_secs(1)
71            ),
72            code_block.append("    engine.render();\n", Duration::from_secs(1)),
73            code_block.append("}", Duration::from_secs(1)),
74            // Staggered appearance of nodes
75            sequence![
76                Duration::from_millis(200),
77                divider_line
78                    .end
79                    .to(Vec2::new(800.0, 300.0), Duration::from_secs(1))
80                    .ease(easings::cubic_out),
81                follower
82                    .radius
83                    .to(30.0, Duration::from_millis(500))
84                    .ease(easings::elastic_out),
85            ],
86            // The path follow combined with a "race" logic
87            any![
88                follower
89                    .position
90                    .follow(&path_node, Duration::from_secs(3))
91                    .ease(easings::cubic_in_out),
92                // Race: if this 'wait' finishes first, the follow is done
93                wait(Duration::from_secs(4)),
94            ],
95            // Final flourishes using different easings
96            all![
97                follower
98                    .radius
99                    .to(10.0, Duration::from_secs(1))
100                    .ease(easings::quad_out),
101                divider_line
102                    .start
103                    .to(Vec2::new(400.0, 300.0), Duration::from_secs(1))
104                    .ease(easings::cubic_in),
105            ]
106        ]
107    ]);
108
109    // 4. Build Scene
110    project.scene.add(&background_rect);
111    project.scene.add(&divider_line);
112    project.scene.add(&path_node);
113    project.scene.add(&follower);
114    project.scene.add(&title_text);
115    project.scene.add(&code_block);
116    project.scene.add(&math_eq);
117    project.scene.add(&logo);
118
119    // 5. Run (Choose show() for interactive or export() for PNGs)
120    project.show().expect("Failed to render");
121    // project.export().expect("Failed to export");
122}
Source

pub fn with_dimensions(self, width: u32, height: u32) -> Self

Sets the width and height of the animation.

Examples found in repository?
examples/math_animation.rs (line 8)
4fn main() {
5    // 1. Initialize Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_dimensions(500, 500)
9        .with_title("Math Animation")
10        .close_on_finish();
11
12    // 2. Create MathNode (Typst syntax)
13    let tex = MathNode::default()
14        .with_position(Vec2::new(250.0, 250.0))
15        .with_equation("y = a x^2")
16        .with_font_size(48.0)
17        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2));
18    project.scene.add(&tex);
19
20    // 3. Define Animation Sequence
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                tex.tex("y = a x^2 + b x", Duration::from_secs(1)),
25                tex.tex("e^(i pi) + 1 = 0", Duration::from_secs(1)),
26                tex.tex("y = a x^2", Duration::from_secs(1)),
27            ]
28        },
29        None,
30    ));
31
32    // 4. Run interactive preview
33    project.show().expect("Failed to render");
34}
More examples
Hide additional examples
examples/color_interpolation.rs (line 6)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(300, 300)
7        .with_fps(60)
8        .with_title("Color Interpolation")
9        .with_cache(true)
10        .close_on_finish();
11
12    let circle = Circle::default()
13        .with_position(Vec2::new(150.0, 150.0))
14        .with_radius(50.0)
15        .with_fill(Color::RED); // Red
16
17    project.scene.add(&circle);
18
19    let duration = Duration::from_secs(1);
20
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                circle.fill_paint.to(Paint::Solid(Color::YELLOW), duration),
25                circle.fill_paint.to(Paint::Solid(Color::GREEN), duration),
26                circle.fill_paint.to(Paint::Solid(Color::BLUE), duration),
27                circle.fill_paint.to(Paint::Solid(Color::RED), duration),
28            ]
29        },
30        None,
31    ));
32
33    // Show
34    project.show().expect("Failed to render");
35}
examples/audio_demo.rs (line 6)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(800, 450)
7        .with_title("Audio Demo")
8        .with_background(Color::rgb8(20, 20, 25))
9        .close_on_finish();
10
11    // Setup Video Timeline
12    let rect = Rect::new(Vec2::new(100.0, 100.0), Vec2::new(200.0, 200.0), Color::RED)
13        .with_anchor(Vec2::new(-1.0, -1.0));
14
15    project.scene.add(&rect);
16
17    project.scene.video_timeline.add(chain!(
18        all![
19            rect.position
20                .to(Vec2::new(150.0, 150.0), Duration::from_secs(1)),
21            rect.size.to(Vec2::new(200.0, 50.0), Duration::from_secs(1)),
22            rect.fill_paint
23                .to(Paint::Solid(Color::BLUE), Duration::from_secs(1)),
24        ],
25        all![
26            rect.position
27                .to(Vec2::new(350.0, 150.0), Duration::from_secs(1)),
28            rect.fill_paint
29                .to(Paint::Solid(Color::RED), Duration::from_secs(1)),
30        ]
31    ));
32
33    // Setup Audio Timeline using new macros and builder
34    project.scene.audio_timeline.add(chain!(
35        play!(AudioNode::new("./examples/audios/combo-1.mp3").with_volume(0.5)),
36        audio_wait!(0.05),
37        play!(AudioNode::new("./examples/audios/combo-2.mp3").with_volume(1.0))
38    ));
39
40    println!("Project configured with separate video and audio timelines.");
41    println!(
42        "Video duration: {:?}",
43        project.scene.video_timeline.duration()
44    );
45    println!(
46        "Audio duration: {:?}",
47        project.scene.audio_timeline.duration()
48    );
49
50    project.show().expect("Failed to run audio demo");
51}
examples/images.rs (line 6)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(600, 600)
7        .with_title("Images")
8        .close_on_finish();
9
10    // Using the sample logo path from the project
11    let png = ImageNode::default()
12        .with_position(Vec2::new(450.0, 450.0))
13        .with_path("./examples/images/motion-canvas-logo.png")
14        .with_size(Vec2::new(200.0, 200.0));
15
16    let svg = SvgNode::default()
17        .with_position(Vec2::new(150.0, 150.0))
18        .with_path("./examples/images/motion-canvas-rs.svg")
19        .with_size(Vec2::new(200.0, 200.0));
20
21    project.scene.add(&png);
22    project.scene.add(&svg);
23
24    const MOVE_DUR: Duration = Duration::from_secs(2);
25
26    project.scene.video_timeline.add(loop_anim(
27        move || {
28            with_easing(
29                bounce_out,
30                vec![chain![
31                    all!(
32                        png.position.to(Vec2::new(150.0, 450.0), MOVE_DUR),
33                        svg.position.to(Vec2::new(450.0, 150.0), MOVE_DUR),
34                    ),
35                    all!(
36                        png.position.to(Vec2::new(150.0, 150.0), MOVE_DUR),
37                        svg.position.to(Vec2::new(450.0, 450.0), MOVE_DUR),
38                    ),
39                    all!(
40                        png.position.to(Vec2::new(450.0, 150.0), MOVE_DUR),
41                        svg.position.to(Vec2::new(150.0, 450.0), MOVE_DUR),
42                    ),
43                    all!(
44                        png.position.to(Vec2::new(450.0, 450.0), MOVE_DUR),
45                        svg.position.to(Vec2::new(150.0, 150.0), MOVE_DUR),
46                    ),
47                ]],
48            )
49        },
50        None,
51    ));
52
53    project.show().expect("Failed to render");
54}
examples/grid.rs (line 7)
4fn main() {
5    let mut project = Project::default()
6        .with_title("Grid")
7        .with_dimensions(1280, 720)
8        .close_on_finish();
9
10    // 1. Create Grid Node
11    let grid = GridNode::square(Vec2::new(640.0, 360.0), 4.0, 50.0)
12        .with_stroke(Palette::DARK_GRAY, 2.0)
13        .with_opacity(0.0);
14
15    // 2. Create Text Node
16    let text_node = TextNode::default()
17        .with_position(Vec2::new(640.0, 100.0))
18        .with_font_size(36.0)
19        .with_fill(Color::WHITE)
20        .with_text("Grid: 4x4 | Spacing: 50x50");
21
22    // 3. Bind Text to Grid's rows/columns and spacing
23    let cols_sig = grid.columns.clone();
24    let spacing_sig = grid.spacing.clone();
25    let text_link = text_node.text.bind(grid.rows.clone(), move |rows| {
26        let cols = cols_sig.get();
27        let spacing = spacing_sig.get();
28        format!(
29            "Grid: {:.0}x{:.0} | Spacing: {:.0}x{:.0}",
30            cols, rows, spacing.x, spacing.y
31        )
32    });
33
34    // Add to scene
35    project.scene.add(&grid);
36    project.scene.add(text_node);
37    project.scene.add(text_link);
38
39    project.scene.video_timeline.add(chain![
40        grid.opacity
41            .to(1.0, Duration::from_secs(1))
42            .ease(easings::cubic_out),
43        wait(Duration::from_millis(500)),
44        all![
45            grid.rows.to(16.0, Duration::from_secs(2)),
46            grid.columns.to(16.0, Duration::from_secs(2)),
47            grid.stroke_paint
48                .to(Paint::Solid(Palette::BLUE), Duration::from_secs(2)),
49            grid.spacing
50                .to(Vec2::new(100.0, 100.0), Duration::from_secs(2)),
51        ],
52        wait(Duration::from_millis(500)),
53        all![
54            grid.rows.to(8.0, Duration::from_secs(2)),
55            grid.columns.to(8.0, Duration::from_secs(2)),
56            grid.stroke_paint
57                .to(Paint::Solid(Palette::ORANGE), Duration::from_secs(2)),
58            grid.spacing
59                .to(Vec2::new(20.0, 20.0), Duration::from_secs(2)),
60        ],
61        wait(Duration::from_secs(1)),
62        grid.opacity
63            .to(0.0, Duration::from_secs(1))
64            .ease(easings::cubic_out),
65        wait(Duration::from_millis(500)),
66    ]);
67
68    project.show().expect("Failed to render");
69}
examples/shapes.rs (line 5)
3fn main() {
4    let mut project = Project::default()
5        .with_dimensions(800, 300)
6        .with_title("Shapes");
7
8    // Configuration
9    let y_shapes = 150.0;
10    let y_labels = 50.0;
11    let spacing = 180.0;
12    let x_start = 130.0;
13
14    // Circle
15    let circle_text = TextNode::default()
16        .with_text("Circle")
17        .with_position(Vec2::new(x_start, y_labels))
18        .with_anchor(Vec2::ZERO)
19        .with_font_size(24.0)
20        .with_fill(Color::DIM_GRAY);
21    let circle = Circle::default()
22        .with_position(Vec2::new(x_start, y_shapes))
23        .with_radius(50.0)
24        .with_fill(Color::rgb8(0xe1, 0x32, 0x38));
25
26    // Rectangle
27    let x_rect = x_start + spacing;
28    let rect_text = TextNode::default()
29        .with_text("Rect")
30        .with_position(Vec2::new(x_rect, y_labels))
31        .with_anchor(Vec2::ZERO)
32        .with_font_size(24.0)
33        .with_fill(Color::DIM_GRAY);
34    let rect = Rect::default()
35        .with_position(Vec2::new(x_rect, y_shapes))
36        .with_size(Vec2::new(100.0, 100.0))
37        .with_radius(8.0)
38        .with_fill(Color::rgb8(0x68, 0xab, 0xdf));
39
40    // Line
41    let x_line = x_rect + spacing;
42    let line_text = TextNode::default()
43        .with_text("Line")
44        .with_position(Vec2::new(x_line, y_labels))
45        .with_anchor(Vec2::ZERO)
46        .with_font_size(24.0)
47        .with_fill(Color::DIM_GRAY);
48    let line = Line::default()
49        .with_position(Vec2::new(x_line, y_shapes))
50        .with_start(Vec2::new(-50.0, 50.0))
51        .with_end(Vec2::new(50.0, -50.0))
52        .with_stroke(Color::WHITE, 4.0);
53
54    // Polygon
55    let x_poly = x_line + spacing;
56    let poly_text = TextNode::default()
57        .with_text("Poly")
58        .with_position(Vec2::new(x_poly, y_labels))
59        .with_anchor(Vec2::ZERO)
60        .with_font_size(24.0)
61        .with_fill(Color::DIM_GRAY);
62    let poly = Polygon::regular(5, 50.0)
63        .with_position(Vec2::new(x_poly, y_shapes))
64        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00));
65
66    // Add all nodes to the scene
67    project.scene.add(circle);
68    project.scene.add(circle_text);
69    project.scene.add(rect);
70    project.scene.add(rect_text);
71    project.scene.add(line);
72    project.scene.add(line_text);
73    project.scene.add(poly);
74    project.scene.add(poly_text);
75
76    project.show().expect("Failed to render");
77}
Source

pub fn with_title(self, title: &str) -> Self

Sets the project title.

Examples found in repository?
examples/math_animation.rs (line 9)
4fn main() {
5    // 1. Initialize Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_dimensions(500, 500)
9        .with_title("Math Animation")
10        .close_on_finish();
11
12    // 2. Create MathNode (Typst syntax)
13    let tex = MathNode::default()
14        .with_position(Vec2::new(250.0, 250.0))
15        .with_equation("y = a x^2")
16        .with_font_size(48.0)
17        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2));
18    project.scene.add(&tex);
19
20    // 3. Define Animation Sequence
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                tex.tex("y = a x^2 + b x", Duration::from_secs(1)),
25                tex.tex("e^(i pi) + 1 = 0", Duration::from_secs(1)),
26                tex.tex("y = a x^2", Duration::from_secs(1)),
27            ]
28        },
29        None,
30    ));
31
32    // 4. Run interactive preview
33    project.show().expect("Failed to render");
34}
More examples
Hide additional examples
examples/color_interpolation.rs (line 8)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(300, 300)
7        .with_fps(60)
8        .with_title("Color Interpolation")
9        .with_cache(true)
10        .close_on_finish();
11
12    let circle = Circle::default()
13        .with_position(Vec2::new(150.0, 150.0))
14        .with_radius(50.0)
15        .with_fill(Color::RED); // Red
16
17    project.scene.add(&circle);
18
19    let duration = Duration::from_secs(1);
20
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                circle.fill_paint.to(Paint::Solid(Color::YELLOW), duration),
25                circle.fill_paint.to(Paint::Solid(Color::GREEN), duration),
26                circle.fill_paint.to(Paint::Solid(Color::BLUE), duration),
27                circle.fill_paint.to(Paint::Solid(Color::RED), duration),
28            ]
29        },
30        None,
31    ));
32
33    // Show
34    project.show().expect("Failed to render");
35}
examples/getting_started.rs (line 9)
4fn main() {
5    // 1. Initialize the Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_cache(true)
9        .with_title("Getting Started")
10        .close_on_finish();
11
12    // 2. Define Nodes
13    let circle = Circle::default()
14        .with_position(Vec2::new(400.0, 300.0))
15        .with_radius(50.0)
16        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
17
18    let text = TextNode::default()
19        .with_position(Vec2::new(400.0, 450.0))
20        .with_text("Hello Rust")
21        .with_font_size(40.0)
22        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White-ish
23
24    // 3. Add Nodes to the Scene
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 4. Add Animations to the Timeline
29    project.scene.video_timeline.add(all![
30        circle.radius.to(100.0, Duration::from_secs(1)),
31        text.position
32            .to(Vec2::new(400.0, 500.0), Duration::from_secs(1)),
33    ]);
34
35    // 5. Show
36    project.show().expect("Failed to render");
37}
examples/code_advanced.rs (line 6)
4fn main() {
5    let mut project = Project::default()
6        .with_title("Code Advanced")
7        .close_on_finish();
8
9    let code = CodeNode::default()
10        .with_position(Vec2::new(50.0, 50.0))
11        .with_code(
12            r#"fn main() {
13    println!("Hello");
14}"#,
15        )
16        .with_language("rust")
17        .with_font_size(32.0)
18        .with_dim_opacity(0.1);
19
20    project.scene.add(&code);
21
22    project.scene.video_timeline.add(sequence![
23        Duration::from_secs(1),
24        // 1. Select line 2 (println) - using 1-based index string
25        code.select_string("2", Duration::from_millis(300)),
26        // 2. Select range 1-2
27        code.select_string("1-2", Duration::from_millis(300)),
28        // 3. Append a comment
29        code.append("\n// Done!", Duration::from_millis(300)),
30        // 3. Reset selection
31        code.select_lines(vec![], Duration::from_millis(300)),
32        // 4. Prepend a header (Now natively lazy, no wrapper needed!)
33        code.prepend("// My Script\n", Duration::from_millis(300)),
34    ]);
35
36    project.show().expect("Failed to render");
37}
examples/polygon.rs (line 5)
4fn main() {
5    let mut project = Project::default().with_title("Polygon").close_on_finish();
6
7    // Create a regular pentagon
8    let pentagon = Polygon::regular(5, 100.0)
9        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)) // Red
10        .with_position(Vec2::new(200.0, 300.0))
11        .with_scale(0.0);
12
13    // Create a custom triangle
14    let triangle = Polygon::default()
15        .with_position(Vec2::new(500.0, 300.0))
16        .with_points(vec![
17            Vec2::new(0.0, -100.0),
18            Vec2::new(100.0, 100.0),
19            Vec2::new(-100.0, 100.0),
20        ])
21        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
22        .with_stroke(Color::WHITE, 4.0);
23
24    project.scene.add(&pentagon);
25    project.scene.add(&triangle);
26
27    // Animate rotation and opacity
28    project.scene.video_timeline.add(all![
29        chain![
30            pentagon.scale.to(Vec2::ONE, Duration::from_secs(1)),
31            pentagon
32                .rotation
33                .to(std::f32::consts::PI, Duration::from_secs(2)),
34            pentagon.scale.to(-Vec2::ONE, Duration::from_secs(1)),
35        ],
36        chain![
37            triangle.opacity.to(1.0, Duration::from_secs(1)),
38            triangle
39                .position
40                .to(Vec2::new(500.0, 300.0), Duration::from_secs(1)),
41            triangle
42                .rotation
43                .to(360.0_f32.to_radians(), Duration::from_secs(1))
44        ],
45    ]);
46
47    project.show().expect("Failed to render");
48}
examples/export.rs (line 9)
4fn main() {
5    // 1. Initialize for Export
6    let mut project = Project::default()
7        .with_fps(30)
8        .with_ffmpeg(true)
9        .with_title("Export")
10        .with_output_path("output")
11        .close_on_finish();
12
13    // 2. Setup Nodes
14    let circle = Circle::default()
15        .with_position(Vec2::new(400.0, 300.0))
16        .with_radius(50.0)
17        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)); // Blue
18
19    let text = TextNode::default()
20        .with_position(Vec2::new(400.0, 50.0))
21        .with_text("Export Demo")
22        .with_font_size(40.0)
23        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White
24
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 3. Define Animations (Color and Font Size)
29    project.scene.video_timeline.add(all![
30        // Circle color and size
31        circle
32            .fill_paint
33            .to(
34                Paint::Solid(Color::rgb8(0xf2, 0xf2, 0xf2)),
35                Duration::from_secs(2)
36            )
37            .ease(easings::quad_in_out),
38        circle
39            .radius
40            .to(150.0, Duration::from_secs(2))
41            .ease(easings::elastic_out),
42        // Text font size
43        text.font_size
44            .to(50.0, Duration::from_secs(2))
45            .ease(easings::cubic_out),
46    ]);
47
48    // 4. Export (Renders frames and combines them into out.mkv)
49    println!("Starting export to {}...", project.output_path.display());
50    project.export().expect("Failed to export");
51}
Source

pub fn with_output_path(self, path: &str) -> Self

Sets the output directory for exports.

Examples found in repository?
examples/export.rs (line 10)
4fn main() {
5    // 1. Initialize for Export
6    let mut project = Project::default()
7        .with_fps(30)
8        .with_ffmpeg(true)
9        .with_title("Export")
10        .with_output_path("output")
11        .close_on_finish();
12
13    // 2. Setup Nodes
14    let circle = Circle::default()
15        .with_position(Vec2::new(400.0, 300.0))
16        .with_radius(50.0)
17        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)); // Blue
18
19    let text = TextNode::default()
20        .with_position(Vec2::new(400.0, 50.0))
21        .with_text("Export Demo")
22        .with_font_size(40.0)
23        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White
24
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 3. Define Animations (Color and Font Size)
29    project.scene.video_timeline.add(all![
30        // Circle color and size
31        circle
32            .fill_paint
33            .to(
34                Paint::Solid(Color::rgb8(0xf2, 0xf2, 0xf2)),
35                Duration::from_secs(2)
36            )
37            .ease(easings::quad_in_out),
38        circle
39            .radius
40            .to(150.0, Duration::from_secs(2))
41            .ease(easings::elastic_out),
42        // Text font size
43        text.font_size
44            .to(50.0, Duration::from_secs(2))
45            .ease(easings::cubic_out),
46    ]);
47
48    // 4. Export (Renders frames and combines them into out.mkv)
49    println!("Starting export to {}...", project.output_path.display());
50    project.export().expect("Failed to export");
51}
More examples
Hide additional examples
examples/advanced_flow.rs (line 11)
4fn main() {
5    // 1. Initialize Project with full API coverage
6    let mut project = Project::default()
7        .with_fps(120)
8        .with_gpu(true)
9        .with_cache(true)
10        .with_ffmpeg(true)
11        .with_output_path("output")
12        .with_title("Advanced Flow")
13        .close_on_finish();
14
15    // 2. Setup Nodes
16    let mut path = BezPath::new();
17    path.move_to((100.0, 300.0));
18    path.curve_to((250.0, 100.0), (550.0, 500.0), (700.0, 300.0));
19    let path_node = PathNode::default()
20        .with_path(path)
21        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 2.0);
22    let follower = Circle::default()
23        .with_position(Vec2::new(100.0, 300.0))
24        .with_radius(20.0)
25        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
26
27    // Showcase: Rect and Line
28    let background_rect = Rect::default()
29        .with_position(Vec2::new(400.0, 300.0))
30        .with_size(Vec2::new(760.0, 560.0))
31        .with_fill(Color::rgba8(0x33, 0x33, 0x33, 150))
32        .with_radius(20.0);
33
34    let divider_line = Line::default()
35        .with_start(Vec2::new(0.0, 300.0))
36        .with_end(Vec2::new(0.0, 300.0))
37        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 1.0);
38    let title_text = TextNode::default()
39        .with_position(Vec2::new(50.0, 50.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_text("Motion Canvas in Rust")
42        .with_font_size(40.0)
43        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
44        .with_font("JetBrains Mono");
45
46    let code_block = CodeNode::default()
47        .with_anchor(Vec2::new(-1.0, -1.0))
48        .with_position(Vec2::new(50.0, 415.0))
49        .with_language("rust")
50        .with_opacity(0.0);
51
52    let math_eq = MathNode::default()
53        .with_position(Vec2::new(150.0, 150.0))
54        .with_equation("f(x) = sin(x)")
55        .with_font_size(30.0)
56        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00)); // Yellow
57
58    let logo = ImageNode::default()
59        .with_position(Vec2::new(650.0, 150.0))
60        .with_path("./examples/images/motion-canvas-logo.png")
61        .with_size(Vec2::new(150.0, 150.0));
62
63    project.scene.video_timeline.add(all![
64        // Show code
65        chain![
66            code_block.opacity.to(1.0, Duration::from_secs(1)),
67            code_block.append("fn main() {\n", Duration::from_secs(1)),
68            code_block.append(
69                "    let mut engine = MotionCanvas::new();\n",
70                Duration::from_secs(1)
71            ),
72            code_block.append("    engine.render();\n", Duration::from_secs(1)),
73            code_block.append("}", Duration::from_secs(1)),
74            // Staggered appearance of nodes
75            sequence![
76                Duration::from_millis(200),
77                divider_line
78                    .end
79                    .to(Vec2::new(800.0, 300.0), Duration::from_secs(1))
80                    .ease(easings::cubic_out),
81                follower
82                    .radius
83                    .to(30.0, Duration::from_millis(500))
84                    .ease(easings::elastic_out),
85            ],
86            // The path follow combined with a "race" logic
87            any![
88                follower
89                    .position
90                    .follow(&path_node, Duration::from_secs(3))
91                    .ease(easings::cubic_in_out),
92                // Race: if this 'wait' finishes first, the follow is done
93                wait(Duration::from_secs(4)),
94            ],
95            // Final flourishes using different easings
96            all![
97                follower
98                    .radius
99                    .to(10.0, Duration::from_secs(1))
100                    .ease(easings::quad_out),
101                divider_line
102                    .start
103                    .to(Vec2::new(400.0, 300.0), Duration::from_secs(1))
104                    .ease(easings::cubic_in),
105            ]
106        ]
107    ]);
108
109    // 4. Build Scene
110    project.scene.add(&background_rect);
111    project.scene.add(&divider_line);
112    project.scene.add(&path_node);
113    project.scene.add(&follower);
114    project.scene.add(&title_text);
115    project.scene.add(&code_block);
116    project.scene.add(&math_eq);
117    project.scene.add(&logo);
118
119    // 5. Run (Choose show() for interactive or export() for PNGs)
120    project.show().expect("Failed to render");
121    // project.export().expect("Failed to export");
122}
Source

pub fn with_cache(self, use_cache: bool) -> Self

Enables or disables frame-level caching.

Examples found in repository?
examples/color_interpolation.rs (line 9)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(300, 300)
7        .with_fps(60)
8        .with_title("Color Interpolation")
9        .with_cache(true)
10        .close_on_finish();
11
12    let circle = Circle::default()
13        .with_position(Vec2::new(150.0, 150.0))
14        .with_radius(50.0)
15        .with_fill(Color::RED); // Red
16
17    project.scene.add(&circle);
18
19    let duration = Duration::from_secs(1);
20
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                circle.fill_paint.to(Paint::Solid(Color::YELLOW), duration),
25                circle.fill_paint.to(Paint::Solid(Color::GREEN), duration),
26                circle.fill_paint.to(Paint::Solid(Color::BLUE), duration),
27                circle.fill_paint.to(Paint::Solid(Color::RED), duration),
28            ]
29        },
30        None,
31    ));
32
33    // Show
34    project.show().expect("Failed to render");
35}
More examples
Hide additional examples
examples/getting_started.rs (line 8)
4fn main() {
5    // 1. Initialize the Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_cache(true)
9        .with_title("Getting Started")
10        .close_on_finish();
11
12    // 2. Define Nodes
13    let circle = Circle::default()
14        .with_position(Vec2::new(400.0, 300.0))
15        .with_radius(50.0)
16        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
17
18    let text = TextNode::default()
19        .with_position(Vec2::new(400.0, 450.0))
20        .with_text("Hello Rust")
21        .with_font_size(40.0)
22        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White-ish
23
24    // 3. Add Nodes to the Scene
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 4. Add Animations to the Timeline
29    project.scene.video_timeline.add(all![
30        circle.radius.to(100.0, Duration::from_secs(1)),
31        text.position
32            .to(Vec2::new(400.0, 500.0), Duration::from_secs(1)),
33    ]);
34
35    // 5. Show
36    project.show().expect("Failed to render");
37}
examples/advanced_flow.rs (line 9)
4fn main() {
5    // 1. Initialize Project with full API coverage
6    let mut project = Project::default()
7        .with_fps(120)
8        .with_gpu(true)
9        .with_cache(true)
10        .with_ffmpeg(true)
11        .with_output_path("output")
12        .with_title("Advanced Flow")
13        .close_on_finish();
14
15    // 2. Setup Nodes
16    let mut path = BezPath::new();
17    path.move_to((100.0, 300.0));
18    path.curve_to((250.0, 100.0), (550.0, 500.0), (700.0, 300.0));
19    let path_node = PathNode::default()
20        .with_path(path)
21        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 2.0);
22    let follower = Circle::default()
23        .with_position(Vec2::new(100.0, 300.0))
24        .with_radius(20.0)
25        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
26
27    // Showcase: Rect and Line
28    let background_rect = Rect::default()
29        .with_position(Vec2::new(400.0, 300.0))
30        .with_size(Vec2::new(760.0, 560.0))
31        .with_fill(Color::rgba8(0x33, 0x33, 0x33, 150))
32        .with_radius(20.0);
33
34    let divider_line = Line::default()
35        .with_start(Vec2::new(0.0, 300.0))
36        .with_end(Vec2::new(0.0, 300.0))
37        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 1.0);
38    let title_text = TextNode::default()
39        .with_position(Vec2::new(50.0, 50.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_text("Motion Canvas in Rust")
42        .with_font_size(40.0)
43        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
44        .with_font("JetBrains Mono");
45
46    let code_block = CodeNode::default()
47        .with_anchor(Vec2::new(-1.0, -1.0))
48        .with_position(Vec2::new(50.0, 415.0))
49        .with_language("rust")
50        .with_opacity(0.0);
51
52    let math_eq = MathNode::default()
53        .with_position(Vec2::new(150.0, 150.0))
54        .with_equation("f(x) = sin(x)")
55        .with_font_size(30.0)
56        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00)); // Yellow
57
58    let logo = ImageNode::default()
59        .with_position(Vec2::new(650.0, 150.0))
60        .with_path("./examples/images/motion-canvas-logo.png")
61        .with_size(Vec2::new(150.0, 150.0));
62
63    project.scene.video_timeline.add(all![
64        // Show code
65        chain![
66            code_block.opacity.to(1.0, Duration::from_secs(1)),
67            code_block.append("fn main() {\n", Duration::from_secs(1)),
68            code_block.append(
69                "    let mut engine = MotionCanvas::new();\n",
70                Duration::from_secs(1)
71            ),
72            code_block.append("    engine.render();\n", Duration::from_secs(1)),
73            code_block.append("}", Duration::from_secs(1)),
74            // Staggered appearance of nodes
75            sequence![
76                Duration::from_millis(200),
77                divider_line
78                    .end
79                    .to(Vec2::new(800.0, 300.0), Duration::from_secs(1))
80                    .ease(easings::cubic_out),
81                follower
82                    .radius
83                    .to(30.0, Duration::from_millis(500))
84                    .ease(easings::elastic_out),
85            ],
86            // The path follow combined with a "race" logic
87            any![
88                follower
89                    .position
90                    .follow(&path_node, Duration::from_secs(3))
91                    .ease(easings::cubic_in_out),
92                // Race: if this 'wait' finishes first, the follow is done
93                wait(Duration::from_secs(4)),
94            ],
95            // Final flourishes using different easings
96            all![
97                follower
98                    .radius
99                    .to(10.0, Duration::from_secs(1))
100                    .ease(easings::quad_out),
101                divider_line
102                    .start
103                    .to(Vec2::new(400.0, 300.0), Duration::from_secs(1))
104                    .ease(easings::cubic_in),
105            ]
106        ]
107    ]);
108
109    // 4. Build Scene
110    project.scene.add(&background_rect);
111    project.scene.add(&divider_line);
112    project.scene.add(&path_node);
113    project.scene.add(&follower);
114    project.scene.add(&title_text);
115    project.scene.add(&code_block);
116    project.scene.add(&math_eq);
117    project.scene.add(&logo);
118
119    // 5. Run (Choose show() for interactive or export() for PNGs)
120    project.show().expect("Failed to render");
121    // project.export().expect("Failed to export");
122}
examples/gradient_demo.rs (line 101)
95fn main() {
96    let cx = 960.0_f32;
97    let cy = 480.0_f32;
98
99    let mut project = Project::default()
100        .with_dimensions(1920, 1080)
101        .with_cache(true)
102        .with_title("Gradient Demo")
103        .close_on_finish();
104
105    // Background
106    let bg_rect = Rect::default()
107        .with_position(Vec2::new(cx, 540.0))
108        .with_size(Vec2::new(1920.0, 1080.0))
109        .with_fill(BG);
110
111    let grid = GridNode::default()
112        .with_position(Vec2::new(cx, 540.0))
113        .with_columns(32.0)
114        .with_rows(18.0)
115        .with_spacing_all(60.0)
116        .with_stroke(GRID_COLOR, 1.0)
117        .with_opacity(0.0);
118
119    // Gradients
120    let sunset = tri_grad(CORAL, PINK, VIOLET, 120.0);
121    let ocean = linear_grad(CYAN, INDIGO, 120.0);
122    let glow = radial_grad(FUCHSIA, Color::rgba8(0x4f, 0x46, 0xe5, 0), 130.0);
123    let glow_alt = radial_grad(CYAN, Color::rgba8(0x00, 0xf2, 0xfe, 0), 180.0);
124
125    // Sunset in different directions (same colors, different orientation)
126    let sunset_stops = vec![
127        ColorStop {
128            offset: 0.0,
129            color: CORAL,
130        },
131        ColorStop {
132            offset: 0.5,
133            color: PINK,
134        },
135        ColorStop {
136            offset: 1.0,
137            color: VIOLET,
138        },
139    ];
140    let sunset_horiz = directed_grad(
141        Point::new(-120.0, 0.0),
142        Point::new(120.0, 0.0),
143        sunset_stops.clone(),
144    );
145    let sunset_vert = directed_grad(
146        Point::new(0.0, -120.0),
147        Point::new(0.0, 120.0),
148        sunset_stops.clone(),
149    );
150    let sunset_diag = directed_grad(
151        Point::new(-120.0, -120.0),
152        Point::new(120.0, 120.0),
153        sunset_stops.clone(),
154    );
155
156    // Ocean in different directions
157    let ocean_stops = vec![
158        ColorStop {
159            offset: 0.0,
160            color: CYAN,
161        },
162        ColorStop {
163            offset: 1.0,
164            color: INDIGO,
165        },
166    ];
167    let ocean_vert = directed_grad(
168        Point::new(0.0, -120.0),
169        Point::new(0.0, 120.0),
170        ocean_stops.clone(),
171    );
172
173    // Persistent title
174    let title = TextNode::default()
175        .with_position(Vec2::new(cx, 100.0))
176        .with_text("Gradient Paint Demo")
177        .with_font_size(48.0)
178        .with_fill(Color::rgb8(0xdd, 0xdd, 0xee))
179        .with_opacity(0.0);
180
181    // Per-scene label (reused position, swapped text via separate nodes)
182    let label_y = 200.0;
183
184    // ─── SCENE 1: Circle with linear gradient fill ───
185    let s1_lbl = TextNode::default()
186        .with_position(Vec2::new(cx, label_y))
187        .with_text("Linear Gradient Fill")
188        .with_font_size(28.0)
189        .with_fill(LABEL)
190        .with_opacity(0.0);
191
192    let s1 = Circle::default()
193        .with_position(Vec2::new(cx, cy))
194        .with_radius(130.0)
195        .with_fill(sunset.clone())
196        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 20), 2.0)
197        .with_scale(0.95)
198        .with_opacity(0.0);
199
200    // Guides
201    let s1_g_start = Circle::default()
202        .with_position(Vec2::new(cx - 120.0, cy - 120.0))
203        .with_radius(8.0)
204        .with_fill(Color::WHITE)
205        .with_stroke(CORAL, 2.5)
206        .with_opacity(0.0);
207
208    let s1_g_end = Circle::default()
209        .with_position(Vec2::new(cx + 120.0, cy + 120.0))
210        .with_radius(8.0)
211        .with_fill(Color::WHITE)
212        .with_stroke(VIOLET, 2.5)
213        .with_opacity(0.0);
214
215    let s1_g_line = Line::default()
216        .with_position(Vec2::new(cx, cy))
217        .with_start(Vec2::new(-120.0, -120.0))
218        .with_end(Vec2::new(120.0, 120.0))
219        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
220        .with_opacity(0.0);
221
222    // ─── SCENE 2: Rect with radial gradient fill ───
223    let s2_lbl = TextNode::default()
224        .with_position(Vec2::new(cx, label_y))
225        .with_text("Radial Gradient Fill")
226        .with_font_size(28.0)
227        .with_fill(LABEL)
228        .with_opacity(0.0);
229
230    let s2 = Rect::default()
231        .with_position(Vec2::new(cx, cy))
232        .with_size(Vec2::new(260.0, 260.0))
233        .with_radius(24.0)
234        .with_fill(glow.clone())
235        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 20), 2.0)
236        .with_scale(0.95)
237        .with_opacity(0.0);
238
239    // Guides
240    let s2_g_center = Circle::default()
241        .with_position(Vec2::new(cx, cy))
242        .with_radius(8.0)
243        .with_fill(Color::WHITE)
244        .with_stroke(FUCHSIA, 2.5)
245        .with_opacity(0.0);
246
247    let s2_g_radius = Circle::default()
248        .with_position(Vec2::new(cx, cy))
249        .with_radius(130.0)
250        .with_fill(Color::TRANSPARENT)
251        .with_stroke(Color::rgba8(0xff, 0x00, 0x7f, 80), 1.5)
252        .with_opacity(0.0);
253
254    // ─── SCENE 3: Hexagon with gradient stroke ───
255    let s3_lbl = TextNode::default()
256        .with_position(Vec2::new(cx, label_y))
257        .with_text("Gradient Stroke")
258        .with_font_size(28.0)
259        .with_fill(LABEL)
260        .with_opacity(0.0);
261
262    let s3 = Polygon::regular(6, 130.0)
263        .with_position(Vec2::new(cx, cy))
264        .with_fill(Color::rgba8(0x12, 0x12, 0x20, 255))
265        .with_stroke(ocean.clone(), 5.0)
266        .with_scale(0.95)
267        .with_opacity(0.0);
268
269    // Guides
270    let s3_g_start = Circle::default()
271        .with_position(Vec2::new(cx - 120.0, cy))
272        .with_radius(8.0)
273        .with_fill(Color::WHITE)
274        .with_stroke(CYAN, 2.5)
275        .with_opacity(0.0);
276
277    let s3_g_end = Circle::default()
278        .with_position(Vec2::new(cx + 120.0, cy))
279        .with_radius(8.0)
280        .with_fill(Color::WHITE)
281        .with_stroke(INDIGO, 2.5)
282        .with_opacity(0.0);
283
284    let s3_g_line = Line::default()
285        .with_position(Vec2::new(cx, cy))
286        .with_start(Vec2::new(-120.0, 0.0))
287        .with_end(Vec2::new(120.0, 0.0))
288        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
289        .with_opacity(0.0);
290
291    // ─── SCENE 4: Line with gradient stroke ───
292    let s4_lbl = TextNode::default()
293        .with_position(Vec2::new(cx, label_y))
294        .with_text("Gradient Line")
295        .with_font_size(28.0)
296        .with_fill(LABEL)
297        .with_opacity(0.0);
298
299    let s4 = Line::default()
300        .with_position(Vec2::new(cx, cy))
301        .with_start(Vec2::new(-160.0, 100.0))
302        .with_end(Vec2::new(160.0, -100.0))
303        .with_stroke(sunset.clone(), 8.0)
304        .with_opacity(0.0);
305
306    // Guides
307    let s4_g_start = Circle::default()
308        .with_position(Vec2::new(cx - 120.0, cy - 120.0))
309        .with_radius(8.0)
310        .with_fill(Color::WHITE)
311        .with_stroke(CORAL, 2.5)
312        .with_opacity(0.0);
313
314    let s4_g_end = Circle::default()
315        .with_position(Vec2::new(cx + 120.0, cy + 120.0))
316        .with_radius(8.0)
317        .with_fill(Color::WHITE)
318        .with_stroke(VIOLET, 2.5)
319        .with_opacity(0.0);
320
321    let s4_g_line = Line::default()
322        .with_position(Vec2::new(cx, cy))
323        .with_start(Vec2::new(-120.0, -120.0))
324        .with_end(Vec2::new(120.0, 120.0))
325        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
326        .with_opacity(0.0);
327
328    // ─── SCENE 5: Path with gradient stroke ───
329    let s5_lbl = TextNode::default()
330        .with_position(Vec2::new(cx, label_y))
331        .with_text("Gradient Path")
332        .with_font_size(28.0)
333        .with_fill(LABEL)
334        .with_opacity(0.0);
335
336    let mut tri_path = BezPath::new();
337    tri_path.move_to(kurbo::Point::new(0.0, -120.0));
338    tri_path.line_to(kurbo::Point::new(104.0, 60.0));
339    tri_path.line_to(kurbo::Point::new(-104.0, 60.0));
340    tri_path.close_path();
341
342    let s5 = PathNode::new(Vec2::new(cx, cy), tri_path, CYAN, 6.0)
343        .with_stroke(ocean.clone(), 6.0)
344        .with_opacity(0.0);
345
346    // Guides
347    let s5_g_start = Circle::default()
348        .with_position(Vec2::new(cx - 120.0, cy))
349        .with_radius(8.0)
350        .with_fill(Color::WHITE)
351        .with_stroke(CYAN, 2.5)
352        .with_opacity(0.0);
353
354    let s5_g_end = Circle::default()
355        .with_position(Vec2::new(cx + 120.0, cy))
356        .with_radius(8.0)
357        .with_fill(Color::WHITE)
358        .with_stroke(INDIGO, 2.5)
359        .with_opacity(0.0);
360
361    let s5_g_line = Line::default()
362        .with_position(Vec2::new(cx, cy))
363        .with_start(Vec2::new(-120.0, 0.0))
364        .with_end(Vec2::new(120.0, 0.0))
365        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
366        .with_opacity(0.0);
367
368    // ─── SCENE 6: Grid with gradient stroke ───
369    let s6_lbl = TextNode::default()
370        .with_position(Vec2::new(cx, label_y))
371        .with_text("Gradient Grid")
372        .with_font_size(28.0)
373        .with_fill(LABEL)
374        .with_opacity(0.0);
375
376    let s6_grid = GridNode::default()
377        .with_position(Vec2::new(cx, cy))
378        .with_columns(8.0)
379        .with_rows(6.0)
380        .with_spacing_all(50.0)
381        .with_stroke(ocean.clone(), 2.0)
382        .with_opacity(0.0);
383
384    // Guides
385    let s6_g_start = Circle::default()
386        .with_position(Vec2::new(cx - 120.0, cy))
387        .with_radius(8.0)
388        .with_fill(Color::WHITE)
389        .with_stroke(CYAN, 2.5)
390        .with_opacity(0.0);
391
392    let s6_g_end = Circle::default()
393        .with_position(Vec2::new(cx + 120.0, cy))
394        .with_radius(8.0)
395        .with_fill(Color::WHITE)
396        .with_stroke(INDIGO, 2.5)
397        .with_opacity(0.0);
398
399    let s6_g_line = Line::default()
400        .with_position(Vec2::new(cx, cy))
401        .with_start(Vec2::new(-120.0, 0.0))
402        .with_end(Vec2::new(120.0, 0.0))
403        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
404        .with_opacity(0.0);
405
406    // ─── SCENE 7: Text with gradient fill ───
407    let s7_lbl = TextNode::default()
408        .with_position(Vec2::new(cx, label_y))
409        .with_text("Gradient Text")
410        .with_font_size(28.0)
411        .with_fill(LABEL)
412        .with_opacity(0.0);
413
414    let s7 = TextNode::default()
415        .with_position(Vec2::new(cx, cy))
416        .with_text("HELLO GRADIENTS")
417        .with_font_size(80.0)
418        .with_fill(ocean.clone())
419        .with_scale(0.95)
420        .with_opacity(0.0);
421
422    // Guides
423    let s7_g_start = Circle::default()
424        .with_position(Vec2::new(cx - 120.0, cy))
425        .with_radius(8.0)
426        .with_fill(Color::WHITE)
427        .with_stroke(CYAN, 2.5)
428        .with_opacity(0.0);
429
430    let s7_g_end = Circle::default()
431        .with_position(Vec2::new(cx + 120.0, cy))
432        .with_radius(8.0)
433        .with_fill(Color::WHITE)
434        .with_stroke(INDIGO, 2.5)
435        .with_opacity(0.0);
436
437    let s7_g_line = Line::default()
438        .with_position(Vec2::new(cx, cy))
439        .with_start(Vec2::new(-120.0, 0.0))
440        .with_end(Vec2::new(120.0, 0.0))
441        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
442        .with_opacity(0.0);
443
444    // ─── SCENE 8: Math with gradient fill ───
445    let s8_lbl = TextNode::default()
446        .with_position(Vec2::new(cx, label_y))
447        .with_text("Gradient Math")
448        .with_font_size(28.0)
449        .with_fill(LABEL)
450        .with_opacity(0.0);
451
452    let s8 = MathNode::default()
453        .with_position(Vec2::new(cx, cy))
454        .with_equation("E = m c^2")
455        .with_font_size(72.0)
456        .with_fill(sunset.clone())
457        .with_scale(0.95)
458        .with_opacity(0.0);
459
460    // Guides
461    let s8_g_start = Circle::default()
462        .with_position(Vec2::new(cx - 120.0, cy - 120.0))
463        .with_radius(8.0)
464        .with_fill(Color::WHITE)
465        .with_stroke(CORAL, 2.5)
466        .with_opacity(0.0);
467
468    let s8_g_end = Circle::default()
469        .with_position(Vec2::new(cx + 120.0, cy + 120.0))
470        .with_radius(8.0)
471        .with_fill(Color::WHITE)
472        .with_stroke(VIOLET, 2.5)
473        .with_opacity(0.0);
474
475    let s8_g_line = Line::default()
476        .with_position(Vec2::new(cx, cy))
477        .with_start(Vec2::new(-120.0, -120.0))
478        .with_end(Vec2::new(120.0, 120.0))
479        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
480        .with_opacity(0.0);
481
482    // ─── SCENE 9: Solid ↔ Gradient ───
483    let s9_lbl = TextNode::default()
484        .with_position(Vec2::new(cx, label_y))
485        .with_text("Solid to Gradient")
486        .with_font_size(28.0)
487        .with_fill(LABEL)
488        .with_opacity(0.0);
489
490    let s9 = Circle::default()
491        .with_position(Vec2::new(cx, cy))
492        .with_radius(130.0)
493        .with_fill(Color::WHITE)
494        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 20), 2.0)
495        .with_scale(0.95)
496        .with_opacity(0.0);
497
498    // Guides (start with opacity 0, fade in when Sunset is active, fade out on Solid)
499    let s9_g_start = Circle::default()
500        .with_position(Vec2::new(cx - 120.0, cy - 120.0))
501        .with_radius(8.0)
502        .with_fill(Color::WHITE)
503        .with_stroke(CORAL, 2.5)
504        .with_opacity(0.0);
505
506    let s9_g_end = Circle::default()
507        .with_position(Vec2::new(cx + 120.0, cy + 120.0))
508        .with_radius(8.0)
509        .with_fill(Color::WHITE)
510        .with_stroke(VIOLET, 2.5)
511        .with_opacity(0.0);
512
513    let s9_g_line = Line::default()
514        .with_position(Vec2::new(cx, cy))
515        .with_start(Vec2::new(-120.0, -120.0))
516        .with_end(Vec2::new(120.0, 120.0))
517        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
518        .with_opacity(0.0);
519
520    // ─── SCENE 10: Radial ↔ Linear Interpolation ───
521    let s10_lbl = TextNode::default()
522        .with_position(Vec2::new(cx, label_y))
523        .with_text("Radial ↔ Linear Morph")
524        .with_font_size(28.0)
525        .with_fill(LABEL)
526        .with_opacity(0.0);
527
528    let s10 = Circle::default()
529        .with_position(Vec2::new(cx, cy))
530        .with_radius(130.0)
531        .with_fill(glow.clone()) // Start as Radial Gradient
532        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 20), 2.0)
533        .with_scale(0.95)
534        .with_opacity(0.0);
535
536    // Visual guides for Scene 10 (cross-fading between radial and linear handles)
537    let s10_g_rad_center = Circle::default()
538        .with_position(Vec2::new(cx, cy))
539        .with_radius(8.0)
540        .with_fill(Color::WHITE)
541        .with_stroke(FUCHSIA, 2.5)
542        .with_opacity(0.0);
543
544    let s10_g_rad_radius = Circle::default()
545        .with_position(Vec2::new(cx, cy))
546        .with_radius(130.0)
547        .with_fill(Color::TRANSPARENT)
548        .with_stroke(Color::rgba8(0xff, 0x00, 0x7f, 80), 1.5)
549        .with_opacity(0.0);
550
551    let s10_g_lin_start = Circle::default()
552        .with_position(Vec2::new(cx - 120.0, cy - 120.0))
553        .with_radius(8.0)
554        .with_fill(Color::WHITE)
555        .with_stroke(CORAL, 2.5)
556        .with_opacity(0.0);
557
558    let s10_g_lin_end = Circle::default()
559        .with_position(Vec2::new(cx + 120.0, cy + 120.0))
560        .with_radius(8.0)
561        .with_fill(Color::WHITE)
562        .with_stroke(VIOLET, 2.5)
563        .with_opacity(0.0);
564
565    let s10_g_lin_line = Line::default()
566        .with_position(Vec2::new(cx, cy))
567        .with_start(Vec2::new(-120.0, -120.0))
568        .with_end(Vec2::new(120.0, 120.0))
569        .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 80), 1.5)
570        .with_opacity(0.0);
571
572    // Add all to scene
573    project.scene.add(&bg_rect);
574    project.scene.add(&grid);
575    project.scene.add(&title);
576
577    // Scene 1
578    project.scene.add(&s1_lbl);
579    project.scene.add(&s1);
580    project.scene.add(&s1_g_line);
581    project.scene.add(&s1_g_start);
582    project.scene.add(&s1_g_end);
583
584    // Scene 2
585    project.scene.add(&s2_lbl);
586    project.scene.add(&s2);
587    project.scene.add(&s2_g_radius);
588    project.scene.add(&s2_g_center);
589
590    // Scene 3
591    project.scene.add(&s3_lbl);
592    project.scene.add(&s3);
593    project.scene.add(&s3_g_line);
594    project.scene.add(&s3_g_start);
595    project.scene.add(&s3_g_end);
596
597    // Scene 4
598    project.scene.add(&s4_lbl);
599    project.scene.add(&s4);
600    project.scene.add(&s4_g_line);
601    project.scene.add(&s4_g_start);
602    project.scene.add(&s4_g_end);
603
604    // Scene 5
605    project.scene.add(&s5_lbl);
606    project.scene.add(&s5);
607    project.scene.add(&s5_g_line);
608    project.scene.add(&s5_g_start);
609    project.scene.add(&s5_g_end);
610
611    // Scene 6
612    project.scene.add(&s6_lbl);
613    project.scene.add(&s6_grid);
614    project.scene.add(&s6_g_line);
615    project.scene.add(&s6_g_start);
616    project.scene.add(&s6_g_end);
617
618    // Scene 7
619    project.scene.add(&s7_lbl);
620    project.scene.add(&s7);
621    project.scene.add(&s7_g_line);
622    project.scene.add(&s7_g_start);
623    project.scene.add(&s7_g_end);
624
625    // Scene 8
626    project.scene.add(&s8_lbl);
627    project.scene.add(&s8);
628    project.scene.add(&s8_g_line);
629    project.scene.add(&s8_g_start);
630    project.scene.add(&s8_g_end);
631
632    // Scene 9
633    project.scene.add(&s9_lbl);
634    project.scene.add(&s9);
635    project.scene.add(&s9_g_line);
636    project.scene.add(&s9_g_start);
637    project.scene.add(&s9_g_end);
638
639    // Scene 10
640    project.scene.add(&s10_lbl);
641    project.scene.add(&s10);
642    project.scene.add(&s10_g_rad_radius);
643    project.scene.add(&s10_g_rad_center);
644    project.scene.add(&s10_g_lin_line);
645    project.scene.add(&s10_g_lin_start);
646    project.scene.add(&s10_g_lin_end);
647
648    // Timeline — one scene at a time, each with its own gradient animation
649    project.scene.video_timeline.add(chain![
650        // Intro
651        all![
652            grid.opacity.to(1.0, Duration::from_millis(800)),
653            title.opacity.to(1.0, Duration::from_millis(800)),
654        ],
655        wait!(1),
656        // ─── 1: Circle linear fill — morph colors, then direction ───
657        all![
658            s1_lbl.opacity.to(1.0, FADE_IN),
659            s1.opacity.to(1.0, FADE_IN),
660            s1.scale.to(Vec2::ONE, FADE_IN),
661            s1_g_start.opacity.to(1.0, FADE_IN),
662            s1_g_end.opacity.to(1.0, FADE_IN),
663            s1_g_line.opacity.to(1.0, FADE_IN),
664        ],
665        wait!(1),
666        // Color morph: sunset → ocean
667        all![
668            s1.fill_paint.to(Paint::Gradient(ocean.clone()), MORPH),
669            s1_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
670            s1_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
671            s1_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
672            s1_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
673            s1_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
674            s1_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
675        ],
676        wait!(1),
677        // Color morph: ocean → sunset
678        all![
679            s1.fill_paint.to(Paint::Gradient(sunset.clone()), MORPH),
680            s1_g_start
681                .position
682                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
683            s1_g_end
684                .position
685                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
686            s1_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
687            s1_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
688            s1_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
689            s1_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
690        ],
691        wait!(1),
692        // Direction morph: diagonal → horizontal sunset
693        all![
694            s1.fill_paint
695                .to(Paint::Gradient(sunset_horiz.clone()), MORPH),
696            s1_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
697            s1_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
698            s1_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
699            s1_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
700        ],
701        wait!(1),
702        // Direction morph: horizontal → vertical sunset
703        all![
704            s1.fill_paint
705                .to(Paint::Gradient(sunset_vert.clone()), MORPH),
706            s1_g_start.position.to(Vec2::new(cx, cy - 120.0), MORPH),
707            s1_g_end.position.to(Vec2::new(cx, cy + 120.0), MORPH),
708            s1_g_line.start.to(Vec2::new(0.0, -120.0), MORPH),
709            s1_g_line.end.to(Vec2::new(0.0, 120.0), MORPH),
710        ],
711        wait!(1),
712        // Direction morph: vertical → diagonal sunset
713        all![
714            s1.fill_paint
715                .to(Paint::Gradient(sunset_diag.clone()), MORPH),
716            s1_g_start
717                .position
718                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
719            s1_g_end
720                .position
721                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
722            s1_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
723            s1_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
724        ],
725        wait!(1),
726        all![
727            s1_lbl.opacity.to(0.0, FADE_OUT),
728            s1.opacity.to(0.0, FADE_OUT),
729            s1.scale.to(Vec2::splat(0.95), FADE_OUT),
730            s1_g_start.opacity.to(0.0, FADE_OUT),
731            s1_g_end.opacity.to(0.0, FADE_OUT),
732            s1_g_line.opacity.to(0.0, FADE_OUT),
733        ],
734        // ─── 2: Rect radial fill — morph to another radial gradient and back ───
735        all![
736            s2_lbl.opacity.to(1.0, FADE_IN),
737            s2.opacity.to(1.0, FADE_IN),
738            s2.scale.to(Vec2::ONE, FADE_IN),
739            s2_g_center.opacity.to(1.0, FADE_IN),
740            s2_g_radius.opacity.to(1.0, FADE_IN),
741        ],
742        wait!(1),
743        // Radial color and radius morph: glow (Fuchsia 130px) -> glow_alt (Cyan 180px)
744        all![
745            s2.fill_paint.to(Paint::Gradient(glow_alt.clone()), MORPH),
746            s2_g_radius.radius.to(180.0, MORPH),
747            s2_g_center.stroke_paint.to(Paint::Solid(CYAN), MORPH),
748            s2_g_radius
749                .stroke_paint
750                .to(Paint::Solid(Color::rgba8(0x00, 0xf2, 0xfe, 80)), MORPH),
751        ],
752        wait!(1),
753        // Morph back to glow
754        all![
755            s2.fill_paint.to(Paint::Gradient(glow.clone()), MORPH),
756            s2_g_radius.radius.to(130.0, MORPH),
757            s2_g_center.stroke_paint.to(Paint::Solid(FUCHSIA), MORPH),
758            s2_g_radius
759                .stroke_paint
760                .to(Paint::Solid(Color::rgba8(0xff, 0x00, 0x7f, 80)), MORPH),
761        ],
762        wait!(1),
763        all![
764            s2_lbl.opacity.to(0.0, FADE_OUT),
765            s2.opacity.to(0.0, FADE_OUT),
766            s2.scale.to(Vec2::splat(0.95), FADE_OUT),
767            s2_g_center.opacity.to(0.0, FADE_OUT),
768            s2_g_radius.opacity.to(0.0, FADE_OUT),
769        ],
770        // ─── 3: Hex gradient stroke — morph colors, then direction ───
771        all![
772            s3_lbl.opacity.to(1.0, FADE_IN),
773            s3.opacity.to(1.0, FADE_IN),
774            s3.scale.to(Vec2::ONE, FADE_IN),
775            s3_g_start.opacity.to(1.0, FADE_IN),
776            s3_g_end.opacity.to(1.0, FADE_IN),
777            s3_g_line.opacity.to(1.0, FADE_IN),
778        ],
779        wait!(1),
780        // Morph: ocean (horizontal) -> sunset (diagonal)
781        all![
782            s3.stroke_paint.to(Paint::Gradient(sunset.clone()), MORPH),
783            s3_g_start
784                .position
785                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
786            s3_g_end
787                .position
788                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
789            s3_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
790            s3_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
791            s3_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
792            s3_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
793        ],
794        wait!(1),
795        // Morph: sunset (diagonal) -> ocean (horizontal)
796        all![
797            s3.stroke_paint.to(Paint::Gradient(ocean.clone()), MORPH),
798            s3_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
799            s3_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
800            s3_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
801            s3_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
802            s3_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
803            s3_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
804        ],
805        wait!(1),
806        // Direction morph: horizontal ocean -> vertical ocean
807        all![
808            s3.stroke_paint
809                .to(Paint::Gradient(ocean_vert.clone()), MORPH),
810            s3_g_start.position.to(Vec2::new(cx, cy - 120.0), MORPH),
811            s3_g_end.position.to(Vec2::new(cx, cy + 120.0), MORPH),
812            s3_g_line.start.to(Vec2::new(0.0, -120.0), MORPH),
813            s3_g_line.end.to(Vec2::new(0.0, 120.0), MORPH),
814        ],
815        wait!(1),
816        // Direction morph: vertical ocean -> horizontal ocean
817        all![
818            s3.stroke_paint.to(Paint::Gradient(ocean.clone()), MORPH),
819            s3_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
820            s3_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
821            s3_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
822            s3_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
823        ],
824        wait!(1),
825        all![
826            s3_lbl.opacity.to(0.0, FADE_OUT),
827            s3.opacity.to(0.0, FADE_OUT),
828            s3.scale.to(Vec2::splat(0.95), FADE_OUT),
829            s3_g_start.opacity.to(0.0, FADE_OUT),
830            s3_g_end.opacity.to(0.0, FADE_OUT),
831            s3_g_line.opacity.to(0.0, FADE_OUT),
832        ],
833        // ─── 4: Line gradient stroke ───
834        all![
835            s4_lbl.opacity.to(1.0, FADE_IN),
836            s4.opacity.to(1.0, FADE_IN),
837            s4_g_start.opacity.to(1.0, FADE_IN),
838            s4_g_end.opacity.to(1.0, FADE_IN),
839            s4_g_line.opacity.to(1.0, FADE_IN),
840        ],
841        wait!(1),
842        // Morph: sunset (diagonal) -> ocean (horizontal)
843        all![
844            s4.stroke_paint.to(Paint::Gradient(ocean.clone()), MORPH),
845            s4_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
846            s4_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
847            s4_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
848            s4_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
849            s4_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
850            s4_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
851        ],
852        wait!(1),
853        // Morph: ocean (horizontal) -> sunset (diagonal)
854        all![
855            s4.stroke_paint.to(Paint::Gradient(sunset.clone()), MORPH),
856            s4_g_start
857                .position
858                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
859            s4_g_end
860                .position
861                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
862            s4_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
863            s4_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
864            s4_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
865            s4_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
866        ],
867        wait!(1),
868        all![
869            s4_lbl.opacity.to(0.0, FADE_OUT),
870            s4.opacity.to(0.0, FADE_OUT),
871            s4_g_start.opacity.to(0.0, FADE_OUT),
872            s4_g_end.opacity.to(0.0, FADE_OUT),
873            s4_g_line.opacity.to(0.0, FADE_OUT),
874        ],
875        // ─── 5: Path gradient stroke ───
876        all![
877            s5_lbl.opacity.to(1.0, FADE_IN),
878            s5.opacity.to(1.0, FADE_IN),
879            s5_g_start.opacity.to(1.0, FADE_IN),
880            s5_g_end.opacity.to(1.0, FADE_IN),
881            s5_g_line.opacity.to(1.0, FADE_IN),
882        ],
883        wait!(1),
884        // Morph: ocean (horizontal) -> sunset (diagonal)
885        all![
886            s5.stroke_paint.to(Paint::Gradient(sunset.clone()), MORPH),
887            s5_g_start
888                .position
889                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
890            s5_g_end
891                .position
892                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
893            s5_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
894            s5_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
895            s5_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
896            s5_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
897        ],
898        wait!(1),
899        // Morph: sunset (diagonal) -> ocean (horizontal)
900        all![
901            s5.stroke_paint.to(Paint::Gradient(ocean.clone()), MORPH),
902            s5_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
903            s5_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
904            s5_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
905            s5_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
906            s5_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
907            s5_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
908        ],
909        wait!(1),
910        all![
911            s5_lbl.opacity.to(0.0, FADE_OUT),
912            s5.opacity.to(0.0, FADE_OUT),
913            s5_g_start.opacity.to(0.0, FADE_OUT),
914            s5_g_end.opacity.to(0.0, FADE_OUT),
915            s5_g_line.opacity.to(0.0, FADE_OUT),
916        ],
917        // ─── 6: Grid gradient stroke ───
918        all![
919            s6_lbl.opacity.to(1.0, FADE_IN),
920            s6_grid.opacity.to(1.0, FADE_IN),
921            s6_g_start.opacity.to(1.0, FADE_IN),
922            s6_g_end.opacity.to(1.0, FADE_IN),
923            s6_g_line.opacity.to(1.0, FADE_IN),
924        ],
925        wait!(1),
926        // Morph: ocean (horizontal) -> sunset (diagonal)
927        all![
928            s6_grid
929                .stroke_paint
930                .to(Paint::Gradient(sunset.clone()), MORPH),
931            s6_g_start
932                .position
933                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
934            s6_g_end
935                .position
936                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
937            s6_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
938            s6_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
939            s6_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
940            s6_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
941        ],
942        wait!(1),
943        // Morph: sunset (diagonal) -> ocean (horizontal)
944        all![
945            s6_grid
946                .stroke_paint
947                .to(Paint::Gradient(ocean.clone()), MORPH),
948            s6_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
949            s6_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
950            s6_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
951            s6_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
952            s6_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
953            s6_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
954        ],
955        wait!(1),
956        all![
957            s6_lbl.opacity.to(0.0, FADE_OUT),
958            s6_grid.opacity.to(0.0, FADE_OUT),
959            s6_g_start.opacity.to(0.0, FADE_OUT),
960            s6_g_end.opacity.to(0.0, FADE_OUT),
961            s6_g_line.opacity.to(0.0, FADE_OUT),
962        ],
963        // ─── 7: Text — morph colors, then direction ───
964        all![
965            s7_lbl.opacity.to(1.0, FADE_IN),
966            s7.opacity.to(1.0, FADE_IN),
967            s7.scale.to(Vec2::ONE, FADE_IN),
968            s7_g_start.opacity.to(1.0, FADE_IN),
969            s7_g_end.opacity.to(1.0, FADE_IN),
970            s7_g_line.opacity.to(1.0, FADE_IN),
971        ],
972        wait!(1),
973        // Morph: ocean (horizontal) -> sunset (diagonal)
974        all![
975            s7.fill_paint.to(Paint::Gradient(sunset.clone()), MORPH),
976            s7_g_start
977                .position
978                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
979            s7_g_end
980                .position
981                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
982            s7_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
983            s7_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
984            s7_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
985            s7_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
986        ],
987        wait!(1),
988        // Morph: sunset (diagonal) -> ocean (horizontal)
989        all![
990            s7.fill_paint.to(Paint::Gradient(ocean.clone()), MORPH),
991            s7_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
992            s7_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
993            s7_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
994            s7_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
995            s7_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
996            s7_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
997        ],
998        wait!(1),
999        // Morph: horizontal ocean -> vertical ocean
1000        all![
1001            s7.fill_paint.to(Paint::Gradient(ocean_vert.clone()), MORPH),
1002            s7_g_start.position.to(Vec2::new(cx, cy - 120.0), MORPH),
1003            s7_g_end.position.to(Vec2::new(cx, cy + 120.0), MORPH),
1004            s7_g_line.start.to(Vec2::new(0.0, -120.0), MORPH),
1005            s7_g_line.end.to(Vec2::new(0.0, 120.0), MORPH),
1006        ],
1007        wait!(1),
1008        // Morph: vertical ocean -> horizontal ocean
1009        all![
1010            s7.fill_paint.to(Paint::Gradient(ocean.clone()), MORPH),
1011            s7_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
1012            s7_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
1013            s7_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
1014            s7_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
1015        ],
1016        wait!(1),
1017        all![
1018            s7_lbl.opacity.to(0.0, FADE_OUT),
1019            s7.opacity.to(0.0, FADE_OUT),
1020            s7.scale.to(Vec2::splat(0.95), FADE_OUT),
1021            s7_g_start.opacity.to(0.0, FADE_OUT),
1022            s7_g_end.opacity.to(0.0, FADE_OUT),
1023            s7_g_line.opacity.to(0.0, FADE_OUT),
1024        ],
1025        // ─── 8: Math ───
1026        all![
1027            s8_lbl.opacity.to(1.0, FADE_IN),
1028            s8.opacity.to(1.0, FADE_IN),
1029            s8.scale.to(Vec2::ONE, FADE_IN),
1030            s8_g_start.opacity.to(1.0, FADE_IN),
1031            s8_g_end.opacity.to(1.0, FADE_IN),
1032            s8_g_line.opacity.to(1.0, FADE_IN),
1033        ],
1034        wait!(1),
1035        // Morph: sunset (diagonal) -> ocean (horizontal)
1036        all![
1037            s8.fill_paint.to(Paint::Gradient(ocean.clone()), MORPH),
1038            s8_g_start.position.to(Vec2::new(cx - 120.0, cy), MORPH),
1039            s8_g_end.position.to(Vec2::new(cx + 120.0, cy), MORPH),
1040            s8_g_line.start.to(Vec2::new(-120.0, 0.0), MORPH),
1041            s8_g_line.end.to(Vec2::new(120.0, 0.0), MORPH),
1042            s8_g_start.stroke_paint.to(Paint::Solid(CYAN), MORPH),
1043            s8_g_end.stroke_paint.to(Paint::Solid(INDIGO), MORPH),
1044        ],
1045        wait!(1),
1046        // Morph: ocean (horizontal) -> sunset (diagonal)
1047        all![
1048            s8.fill_paint.to(Paint::Gradient(sunset.clone()), MORPH),
1049            s8_g_start
1050                .position
1051                .to(Vec2::new(cx - 120.0, cy - 120.0), MORPH),
1052            s8_g_end
1053                .position
1054                .to(Vec2::new(cx + 120.0, cy + 120.0), MORPH),
1055            s8_g_line.start.to(Vec2::new(-120.0, -120.0), MORPH),
1056            s8_g_line.end.to(Vec2::new(120.0, 120.0), MORPH),
1057            s8_g_start.stroke_paint.to(Paint::Solid(CORAL), MORPH),
1058            s8_g_end.stroke_paint.to(Paint::Solid(VIOLET), MORPH),
1059        ],
1060        wait!(1),
1061        all![
1062            s8_lbl.opacity.to(0.0, FADE_OUT),
1063            s8.opacity.to(0.0, FADE_OUT),
1064            s8.scale.to(Vec2::splat(0.95), FADE_OUT),
1065            s8_g_start.opacity.to(0.0, FADE_OUT),
1066            s8_g_end.opacity.to(0.0, FADE_OUT),
1067            s8_g_line.opacity.to(0.0, FADE_OUT),
1068        ],
1069        // ─── 9: Solid ↔ Gradient ───
1070        all![
1071            s9_lbl.opacity.to(1.0, FADE_IN),
1072            s9.opacity.to(1.0, FADE_IN),
1073            s9.scale.to(Vec2::ONE, FADE_IN),
1074        ],
1075        wait!(1),
1076        // Morph: solid white -> sunset (diagonal gradient) + fade in guides
1077        all![
1078            s9.fill_paint.to(Paint::Gradient(sunset.clone()), MORPH),
1079            s9_g_start.opacity.to(1.0, MORPH),
1080            s9_g_end.opacity.to(1.0, MORPH),
1081            s9_g_line.opacity.to(1.0, MORPH),
1082        ],
1083        wait!(1),
1084        // Morph: sunset -> solid white + fade out guides
1085        all![
1086            s9.fill_paint.to(Paint::Solid(Color::WHITE), MORPH),
1087            s9_g_start.opacity.to(0.0, MORPH),
1088            s9_g_end.opacity.to(0.0, MORPH),
1089            s9_g_line.opacity.to(0.0, MORPH),
1090        ],
1091        wait!(1),
1092        all![
1093            s9_lbl.opacity.to(0.0, FADE_OUT),
1094            s9.opacity.to(0.0, FADE_OUT),
1095            s9.scale.to(Vec2::splat(0.95), FADE_OUT),
1096        ],
1097        // ─── 10: Radial ↔ Linear Morph ───
1098        all![
1099            s10_lbl.opacity.to(1.0, FADE_IN),
1100            s10.opacity.to(1.0, FADE_IN),
1101            s10.scale.to(Vec2::ONE, FADE_IN),
1102            s10_g_rad_center.opacity.to(1.0, FADE_IN),
1103            s10_g_rad_radius.opacity.to(1.0, FADE_IN),
1104        ],
1105        wait!(1),
1106        // Morph Radial (glow) to Linear (sunset) + cross-fade the visual guides
1107        all![
1108            s10.fill_paint.to(Paint::Gradient(sunset.clone()), MORPH),
1109            s10_g_rad_center.opacity.to(0.0, MORPH),
1110            s10_g_rad_radius.opacity.to(0.0, MORPH),
1111            s10_g_lin_start.opacity.to(1.0, MORPH),
1112            s10_g_lin_end.opacity.to(1.0, MORPH),
1113            s10_g_lin_line.opacity.to(1.0, MORPH),
1114        ],
1115        wait!(1),
1116        // Morph back from Linear (sunset) to Radial (glow)
1117        all![
1118            s10.fill_paint.to(Paint::Gradient(glow.clone()), MORPH),
1119            s10_g_rad_center.opacity.to(1.0, MORPH),
1120            s10_g_rad_radius.opacity.to(1.0, MORPH),
1121            s10_g_lin_start.opacity.to(0.0, MORPH),
1122            s10_g_lin_end.opacity.to(0.0, MORPH),
1123            s10_g_lin_line.opacity.to(0.0, MORPH),
1124        ],
1125        wait!(1),
1126        // Fade everything out
1127        all![
1128            s10_lbl.opacity.to(0.0, FADE_OUT),
1129            s10.opacity.to(0.0, FADE_OUT),
1130            s10_g_rad_center.opacity.to(0.0, FADE_OUT),
1131            s10_g_rad_radius.opacity.to(0.0, FADE_OUT),
1132            title.opacity.to(0.0, FADE_OUT),
1133            grid.opacity.to(0.0, FADE_OUT),
1134        ],
1135    ]);
1136
1137    project.show().expect("Failed to render animation");
1138}
Source

pub fn with_cache_write_interval(self, interval: u32) -> Self

Sets the cache manifest write interval (number of frames).

Source

pub fn with_ffmpeg(self, use_ffmpeg: bool) -> Self

Enables or disables automatic FFmpeg encoding.

Examples found in repository?
examples/export.rs (line 8)
4fn main() {
5    // 1. Initialize for Export
6    let mut project = Project::default()
7        .with_fps(30)
8        .with_ffmpeg(true)
9        .with_title("Export")
10        .with_output_path("output")
11        .close_on_finish();
12
13    // 2. Setup Nodes
14    let circle = Circle::default()
15        .with_position(Vec2::new(400.0, 300.0))
16        .with_radius(50.0)
17        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)); // Blue
18
19    let text = TextNode::default()
20        .with_position(Vec2::new(400.0, 50.0))
21        .with_text("Export Demo")
22        .with_font_size(40.0)
23        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White
24
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 3. Define Animations (Color and Font Size)
29    project.scene.video_timeline.add(all![
30        // Circle color and size
31        circle
32            .fill_paint
33            .to(
34                Paint::Solid(Color::rgb8(0xf2, 0xf2, 0xf2)),
35                Duration::from_secs(2)
36            )
37            .ease(easings::quad_in_out),
38        circle
39            .radius
40            .to(150.0, Duration::from_secs(2))
41            .ease(easings::elastic_out),
42        // Text font size
43        text.font_size
44            .to(50.0, Duration::from_secs(2))
45            .ease(easings::cubic_out),
46    ]);
47
48    // 4. Export (Renders frames and combines them into out.mkv)
49    println!("Starting export to {}...", project.output_path.display());
50    project.export().expect("Failed to export");
51}
More examples
Hide additional examples
examples/advanced_flow.rs (line 10)
4fn main() {
5    // 1. Initialize Project with full API coverage
6    let mut project = Project::default()
7        .with_fps(120)
8        .with_gpu(true)
9        .with_cache(true)
10        .with_ffmpeg(true)
11        .with_output_path("output")
12        .with_title("Advanced Flow")
13        .close_on_finish();
14
15    // 2. Setup Nodes
16    let mut path = BezPath::new();
17    path.move_to((100.0, 300.0));
18    path.curve_to((250.0, 100.0), (550.0, 500.0), (700.0, 300.0));
19    let path_node = PathNode::default()
20        .with_path(path)
21        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 2.0);
22    let follower = Circle::default()
23        .with_position(Vec2::new(100.0, 300.0))
24        .with_radius(20.0)
25        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
26
27    // Showcase: Rect and Line
28    let background_rect = Rect::default()
29        .with_position(Vec2::new(400.0, 300.0))
30        .with_size(Vec2::new(760.0, 560.0))
31        .with_fill(Color::rgba8(0x33, 0x33, 0x33, 150))
32        .with_radius(20.0);
33
34    let divider_line = Line::default()
35        .with_start(Vec2::new(0.0, 300.0))
36        .with_end(Vec2::new(0.0, 300.0))
37        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 1.0);
38    let title_text = TextNode::default()
39        .with_position(Vec2::new(50.0, 50.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_text("Motion Canvas in Rust")
42        .with_font_size(40.0)
43        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
44        .with_font("JetBrains Mono");
45
46    let code_block = CodeNode::default()
47        .with_anchor(Vec2::new(-1.0, -1.0))
48        .with_position(Vec2::new(50.0, 415.0))
49        .with_language("rust")
50        .with_opacity(0.0);
51
52    let math_eq = MathNode::default()
53        .with_position(Vec2::new(150.0, 150.0))
54        .with_equation("f(x) = sin(x)")
55        .with_font_size(30.0)
56        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00)); // Yellow
57
58    let logo = ImageNode::default()
59        .with_position(Vec2::new(650.0, 150.0))
60        .with_path("./examples/images/motion-canvas-logo.png")
61        .with_size(Vec2::new(150.0, 150.0));
62
63    project.scene.video_timeline.add(all![
64        // Show code
65        chain![
66            code_block.opacity.to(1.0, Duration::from_secs(1)),
67            code_block.append("fn main() {\n", Duration::from_secs(1)),
68            code_block.append(
69                "    let mut engine = MotionCanvas::new();\n",
70                Duration::from_secs(1)
71            ),
72            code_block.append("    engine.render();\n", Duration::from_secs(1)),
73            code_block.append("}", Duration::from_secs(1)),
74            // Staggered appearance of nodes
75            sequence![
76                Duration::from_millis(200),
77                divider_line
78                    .end
79                    .to(Vec2::new(800.0, 300.0), Duration::from_secs(1))
80                    .ease(easings::cubic_out),
81                follower
82                    .radius
83                    .to(30.0, Duration::from_millis(500))
84                    .ease(easings::elastic_out),
85            ],
86            // The path follow combined with a "race" logic
87            any![
88                follower
89                    .position
90                    .follow(&path_node, Duration::from_secs(3))
91                    .ease(easings::cubic_in_out),
92                // Race: if this 'wait' finishes first, the follow is done
93                wait(Duration::from_secs(4)),
94            ],
95            // Final flourishes using different easings
96            all![
97                follower
98                    .radius
99                    .to(10.0, Duration::from_secs(1))
100                    .ease(easings::quad_out),
101                divider_line
102                    .start
103                    .to(Vec2::new(400.0, 300.0), Duration::from_secs(1))
104                    .ease(easings::cubic_in),
105            ]
106        ]
107    ]);
108
109    // 4. Build Scene
110    project.scene.add(&background_rect);
111    project.scene.add(&divider_line);
112    project.scene.add(&path_node);
113    project.scene.add(&follower);
114    project.scene.add(&title_text);
115    project.scene.add(&code_block);
116    project.scene.add(&math_eq);
117    project.scene.add(&logo);
118
119    // 5. Run (Choose show() for interactive or export() for PNGs)
120    project.show().expect("Failed to render");
121    // project.export().expect("Failed to export");
122}
Source

pub fn with_gpu(self, use_gpu: bool) -> Self

Enables or disables GPU acceleration.

Examples found in repository?
examples/advanced_flow.rs (line 8)
4fn main() {
5    // 1. Initialize Project with full API coverage
6    let mut project = Project::default()
7        .with_fps(120)
8        .with_gpu(true)
9        .with_cache(true)
10        .with_ffmpeg(true)
11        .with_output_path("output")
12        .with_title("Advanced Flow")
13        .close_on_finish();
14
15    // 2. Setup Nodes
16    let mut path = BezPath::new();
17    path.move_to((100.0, 300.0));
18    path.curve_to((250.0, 100.0), (550.0, 500.0), (700.0, 300.0));
19    let path_node = PathNode::default()
20        .with_path(path)
21        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 2.0);
22    let follower = Circle::default()
23        .with_position(Vec2::new(100.0, 300.0))
24        .with_radius(20.0)
25        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
26
27    // Showcase: Rect and Line
28    let background_rect = Rect::default()
29        .with_position(Vec2::new(400.0, 300.0))
30        .with_size(Vec2::new(760.0, 560.0))
31        .with_fill(Color::rgba8(0x33, 0x33, 0x33, 150))
32        .with_radius(20.0);
33
34    let divider_line = Line::default()
35        .with_start(Vec2::new(0.0, 300.0))
36        .with_end(Vec2::new(0.0, 300.0))
37        .with_stroke(Color::rgb8(0x44, 0x44, 0x44), 1.0);
38    let title_text = TextNode::default()
39        .with_position(Vec2::new(50.0, 50.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_text("Motion Canvas in Rust")
42        .with_font_size(40.0)
43        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
44        .with_font("JetBrains Mono");
45
46    let code_block = CodeNode::default()
47        .with_anchor(Vec2::new(-1.0, -1.0))
48        .with_position(Vec2::new(50.0, 415.0))
49        .with_language("rust")
50        .with_opacity(0.0);
51
52    let math_eq = MathNode::default()
53        .with_position(Vec2::new(150.0, 150.0))
54        .with_equation("f(x) = sin(x)")
55        .with_font_size(30.0)
56        .with_fill(Color::rgb8(0xe6, 0xa7, 0x00)); // Yellow
57
58    let logo = ImageNode::default()
59        .with_position(Vec2::new(650.0, 150.0))
60        .with_path("./examples/images/motion-canvas-logo.png")
61        .with_size(Vec2::new(150.0, 150.0));
62
63    project.scene.video_timeline.add(all![
64        // Show code
65        chain![
66            code_block.opacity.to(1.0, Duration::from_secs(1)),
67            code_block.append("fn main() {\n", Duration::from_secs(1)),
68            code_block.append(
69                "    let mut engine = MotionCanvas::new();\n",
70                Duration::from_secs(1)
71            ),
72            code_block.append("    engine.render();\n", Duration::from_secs(1)),
73            code_block.append("}", Duration::from_secs(1)),
74            // Staggered appearance of nodes
75            sequence![
76                Duration::from_millis(200),
77                divider_line
78                    .end
79                    .to(Vec2::new(800.0, 300.0), Duration::from_secs(1))
80                    .ease(easings::cubic_out),
81                follower
82                    .radius
83                    .to(30.0, Duration::from_millis(500))
84                    .ease(easings::elastic_out),
85            ],
86            // The path follow combined with a "race" logic
87            any![
88                follower
89                    .position
90                    .follow(&path_node, Duration::from_secs(3))
91                    .ease(easings::cubic_in_out),
92                // Race: if this 'wait' finishes first, the follow is done
93                wait(Duration::from_secs(4)),
94            ],
95            // Final flourishes using different easings
96            all![
97                follower
98                    .radius
99                    .to(10.0, Duration::from_secs(1))
100                    .ease(easings::quad_out),
101                divider_line
102                    .start
103                    .to(Vec2::new(400.0, 300.0), Duration::from_secs(1))
104                    .ease(easings::cubic_in),
105            ]
106        ]
107    ]);
108
109    // 4. Build Scene
110    project.scene.add(&background_rect);
111    project.scene.add(&divider_line);
112    project.scene.add(&path_node);
113    project.scene.add(&follower);
114    project.scene.add(&title_text);
115    project.scene.add(&code_block);
116    project.scene.add(&math_eq);
117    project.scene.add(&logo);
118
119    // 5. Run (Choose show() for interactive or export() for PNGs)
120    project.show().expect("Failed to render");
121    // project.export().expect("Failed to export");
122}
Source

pub fn with_background(self, color: Color) -> Self

Sets the background clear color.

Examples found in repository?
examples/audio_demo.rs (line 8)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(800, 450)
7        .with_title("Audio Demo")
8        .with_background(Color::rgb8(20, 20, 25))
9        .close_on_finish();
10
11    // Setup Video Timeline
12    let rect = Rect::new(Vec2::new(100.0, 100.0), Vec2::new(200.0, 200.0), Color::RED)
13        .with_anchor(Vec2::new(-1.0, -1.0));
14
15    project.scene.add(&rect);
16
17    project.scene.video_timeline.add(chain!(
18        all![
19            rect.position
20                .to(Vec2::new(150.0, 150.0), Duration::from_secs(1)),
21            rect.size.to(Vec2::new(200.0, 50.0), Duration::from_secs(1)),
22            rect.fill_paint
23                .to(Paint::Solid(Color::BLUE), Duration::from_secs(1)),
24        ],
25        all![
26            rect.position
27                .to(Vec2::new(350.0, 150.0), Duration::from_secs(1)),
28            rect.fill_paint
29                .to(Paint::Solid(Color::RED), Duration::from_secs(1)),
30        ]
31    ));
32
33    // Setup Audio Timeline using new macros and builder
34    project.scene.audio_timeline.add(chain!(
35        play!(AudioNode::new("./examples/audios/combo-1.mp3").with_volume(0.5)),
36        audio_wait!(0.05),
37        play!(AudioNode::new("./examples/audios/combo-2.mp3").with_volume(1.0))
38    ));
39
40    println!("Project configured with separate video and audio timelines.");
41    println!(
42        "Video duration: {:?}",
43        project.scene.video_timeline.duration()
44    );
45    println!(
46        "Audio duration: {:?}",
47        project.scene.audio_timeline.duration()
48    );
49
50    project.show().expect("Failed to run audio demo");
51}
More examples
Hide additional examples
examples/camera_demo.rs (line 7)
4fn main() {
5    let mut project = Project::default()
6        .with_title("Camera Demo")
7        .with_background(Color::rgb8(0x1a, 0x1a, 0x1a))
8        .close_on_finish();
9
10    // Create a grid of circles to move the camera around
11    let mut nodes: Vec<Box<dyn Node>> = Vec::new();
12
13    for x in -2..=2 {
14        for y in -2..=2 {
15            let color = if (x + y) % 2 == 0 {
16                Color::rgb8(0x34, 0x98, 0xdb) // Blue
17            } else {
18                Color::rgb8(0xe7, 0x4c, 0x3c) // Red
19            };
20
21            nodes.push(Box::new(
22                Circle::default()
23                    .with_position(Vec2::new(x as f32 * 200.0, y as f32 * 200.0))
24                    .with_radius(40.0)
25                    .with_fill(color),
26            ));
27        }
28    }
29
30    // Create the camera and add our nodes to it
31    let camera = CameraNode::new(nodes)
32        .with_position(Vec2::new(0.0, 0.0))
33        .with_zoom(1.0);
34
35    project.scene.add(&camera);
36
37    // Add HUD text (NOT in camera, so it stays fixed)
38    project.scene.add(
39        TextNode::default()
40            .with_position(Vec2::new(400.0, 50.0))
41            .with_text("Camera Control")
42            .with_font_size(60.0)
43            .with_fill(Color::WHITE)
44            .with_anchor(Vec2::ZERO),
45    );
46
47    // Animation: Pan, Zoom, and Rotate the camera
48    project.scene.video_timeline.add(loop_anim!(
49        chain![
50            wait(Duration::from_secs(1)),
51            // 1. Pan to the right
52            camera
53                .position
54                .to(Vec2::new(200.0, 0.0), Duration::from_secs(1)),
55            // 2. Zoom in
56            camera.zoom.to(2.0, Duration::from_secs(1)),
57            // 3. Pan down and rotate
58            all![
59                camera
60                    .position
61                    .to(Vec2::new(200.0, 200.0), Duration::from_secs(1)),
62                camera
63                    .rotation
64                    .to(std::f32::consts::PI / 4.0, Duration::from_secs(1)),
65            ],
66            // 4. Zoom out and reset
67            all![
68                camera.position.to(Vec2::ZERO, Duration::from_secs(1)),
69                camera.zoom.to(0.5, Duration::from_secs(1)),
70                camera.rotation.to(0.0, Duration::from_secs(1)),
71            ],
72            // 5. Back to normal
73            camera.zoom.to(1.0, Duration::from_secs(1)),
74        ],
75        None,
76    ));
77
78    project.show().expect("Failed to render");
79}
examples/nested_cameras.rs (line 7)
4fn main() {
5    let mut project = Project::default()
6        .with_title("Nested Cameras")
7        .with_background(Color::rgb8(0x1a, 0x1a, 0x1a))
8        .close_on_finish();
9
10    // Elements to view
11    let rect = Rect::default()
12        .with_size(Vec2::new(200.0, 200.0))
13        .with_fill(Color::BLUE);
14
15    let circle = Circle::default()
16        .with_position(Vec2::new(100.0, 100.0))
17        .with_radius(30.0)
18        .with_fill(Color::WHITE);
19
20    // Inner Camera: Zooms into the action
21    // We disable 'centered' here so it doesn't double-shift the viewport
22    let inner_camera = CameraNode::new(vec![Box::new(rect.clone()), Box::new(circle.clone())])
23        .with_centered(false);
24
25    // Outer Camera: Handles global movement (centered)
26    let outer_camera = CameraNode::new(vec![Box::new(inner_camera.clone())]).with_centered(true);
27
28    // HUD Indicators (Outside cameras to show state)
29    let outer_status = TextNode::default()
30        .with_text("Outer Camera: Panning")
31        .with_position(Vec2::new(20.0, 20.0))
32        .with_anchor(Vec2::new(-1.0, -1.0))
33        .with_font_size(24.0)
34        .with_fill(Color::rgb8(0x2e, 0xcc, 0x71)) // Green
35        .with_opacity(0.0);
36
37    let inner_status = TextNode::default()
38        .with_text("Inner Camera: Zooming")
39        .with_position(Vec2::new(20.0, 60.0))
40        .with_anchor(Vec2::new(-1.0, -1.0))
41        .with_font_size(24.0)
42        .with_fill(Color::rgb8(0xf1, 0xc4, 0x0f)) // Yellow
43        .with_opacity(0.0);
44
45    project.scene.add(&outer_camera);
46
47    project.scene.add(&outer_status);
48    project.scene.add(&inner_status);
49
50    // Animation Flow
51    project.scene.video_timeline.add(loop_anim!(
52        chain![
53            // 1. Outer camera moves left
54            all![
55                outer_status.opacity.to(1.0, Duration::from_millis(300)),
56                outer_camera
57                    .position
58                    .to(Vec2::new(-200.0, 0.0), Duration::from_secs(1)),
59            ],
60            outer_status.opacity.to(0.0, Duration::from_millis(300)),
61            // 2. Inner camera zooms in
62            all![
63                inner_status.opacity.to(1.0, Duration::from_millis(300)),
64                inner_camera.zoom.to(3.0, Duration::from_secs(1)),
65            ],
66            inner_status.opacity.to(0.0, Duration::from_millis(300)),
67            // 3. Reset both
68            all![
69                outer_status.opacity.to(0.5, Duration::from_millis(300)),
70                inner_status.opacity.to(0.5, Duration::from_millis(300)),
71                outer_camera
72                    .position
73                    .to(Vec2::new(0.0, 0.0), Duration::from_secs(1)),
74                inner_camera.zoom.to(1.0, Duration::from_secs(1)),
75            ],
76            all![
77                outer_status.opacity.to(0.0, Duration::from_millis(300)),
78                inner_status.opacity.to(0.0, Duration::from_millis(300)),
79            ],
80        ],
81        None
82    ));
83
84    project.show().expect("Failed to render");
85}
examples/blur_demo.rs (line 11)
8fn main() {
9    let mut project = Project::default()
10        .with_title("Blur Demo")
11        .with_background(Color::rgb8(0x08, 0x08, 0x10))
12        .close_on_finish();
13
14    // Dot Grid Backdrop
15    for x in 1..8 {
16        for y in 1..6 {
17            project.scene.add(
18                Circle::default()
19                    .with_position(Vec2::new(x as f32 * 100.0, y as f32 * 100.0))
20                    .with_radius(1.5)
21                    .with_fill(Color::rgb8(0x1a, 0x1a, 0x2e)),
22            );
23        }
24    }
25
26    // Title
27    let title = TextNode::default()
28        .with_position(Vec2::new(400.0, 60.0))
29        .with_text("Universal Blur")
30        .with_font_size(42.0)
31        .with_fill(Color::rgb8(0xe2, 0xe8, 0xf0))
32        .with_blur(0.0);
33    project.scene.add(&title);
34
35    // Shape
36    let orb = Circle::default()
37        .with_position(Vec2::new(180.0, 220.0))
38        .with_radius(55.0)
39        .with_fill(Color::rgb8(0xa7, 0x6b, 0xf1))
40        .with_opacity(0.9)
41        .with_blur(20.0);
42    project.scene.add(&orb);
43
44    // SVG
45    let logo = SvgNode::default()
46        .with_position(Vec2::new(620.0, 220.0))
47        .with_path("./examples/images/motion-canvas-rs.svg")
48        .with_size(Vec2::new(100.0, 100.0))
49        .with_blur(0.0);
50    project.scene.add(&logo);
51
52    // Math
53    let eq = MathNode::default()
54        .with_position(Vec2::new(200.0, 400.0))
55        .with_equation(r#"E = m c^2"#)
56        .with_font_size(38.0)
57        .with_fill(Color::rgb8(0xfb, 0xd3, 0x8d))
58        .with_blur(0.0);
59    project.scene.add(&eq);
60
61    // Code
62    let snippet = CodeNode::default()
63        .with_position(Vec2::new(400.0, 400.0))
64        .with_language("rs")
65        .with_code("let blur = 20.0;\ncircle.with_blur(blur);")
66        .with_font_size(18.0)
67        .with_blur(0.0);
68    project.scene.add(&snippet);
69
70    // Group
71    let card = GroupNode::new(vec![
72        Box::new(
73            Rect::default()
74                .with_size(Vec2::new(200.0, 90.0))
75                .with_radius(12.0)
76                .with_fill(Color::rgba8(0x38, 0xa1, 0xdb, 0xbb)),
77        ),
78        Box::new(
79            TextNode::default()
80                .with_text("Group Blur")
81                .with_font_size(22.0)
82                .with_fill(Color::WHITE),
83        ),
84    ])
85    .with_position(Vec2::new(400.0, 300.0))
86    .with_blur(0.0);
87    project.scene.add(&card);
88
89    // Subtitle
90    let sub = TextNode::default()
91        .with_position(Vec2::new(400.0, 550.0))
92        .with_text("Shape · SVG · Math · Code · Group")
93        .with_font_size(16.0)
94        .with_fill(Color::rgb8(0x64, 0x6e, 0x7a))
95        .with_blur(0.0);
96    project.scene.add(&sub);
97
98    // Animation: staggered blur waves
99    // Phase 1 — blur everything except the orb (which sharpens)
100    // Phase 2 — restore, then blur the card and equation
101    // Phase 3 — return to initial state
102    project.scene.video_timeline.add(loop_anim!(
103        chain![
104            wait(BEAT),
105            // Phase 1: orb sharpens, everything else softens
106            all![
107                orb.blur.to(0.0, FAST),
108                logo.blur.to(18.0, FAST),
109                eq.blur.to(14.0, FAST),
110                snippet.blur.to(14.0, FAST),
111                card.blur.to(12.0, FAST),
112                title.blur.to(6.0, FAST),
113                sub.blur.to(4.0, FAST),
114            ],
115            wait(BEAT),
116            // Phase 2: card + equation sharp, rest blurred
117            all![
118                orb.blur.to(24.0, SLOW),
119                logo.blur.to(0.0, SLOW),
120                eq.blur.to(0.0, SLOW),
121                snippet.blur.to(18.0, SLOW),
122                card.blur.to(0.0, SLOW),
123                title.blur.to(0.0, SLOW),
124                sub.blur.to(0.0, SLOW),
125            ],
126            wait(BEAT),
127            // Phase 3: return to opening state
128            all![
129                orb.blur.to(20.0, FAST),
130                logo.blur.to(0.0, FAST),
131                eq.blur.to(0.0, FAST),
132                snippet.blur.to(0.0, FAST),
133                card.blur.to(0.0, FAST),
134                title.blur.to(0.0, FAST),
135                sub.blur.to(0.0, FAST),
136            ],
137        ],
138        None,
139    ));
140
141    project.show().expect("Failed to show window");
142}
examples/mask_demo.rs (line 77)
72fn main() {
73    let mut project = Project::default()
74        .with_dimensions(1280, 720)
75        .with_fps(60)
76        .with_title("Mask Demo")
77        .with_background(Color::rgb8(0x0b, 0x0f, 0x19))
78        .close_on_finish();
79
80    // Dot grid
81    for x in 1..26 {
82        for y in 1..15 {
83            project.scene.add(
84                Circle::default()
85                    .with_position(Vec2::new(x as f32 * 50.0, y as f32 * 50.0))
86                    .with_radius(1.0)
87                    .with_fill(Color::rgb8(0x22, 0x2e, 0x4a)),
88            );
89        }
90    }
91
92    // Title
93    project.scene.add(
94        TextNode::default()
95            .with_position(Vec2::new(640.0, 50.0))
96            .with_text("Masking")
97            .with_font_size(36.0)
98            .with_fill(Color::rgb8(0xf8, 0xfa, 0xfc)),
99    );
100
101    // Mode label (animated)
102    let label = TextNode::default()
103        .with_position(Vec2::new(640.0, 100.0))
104        .with_text("Intersect (Source In)")
105        .with_font_size(20.0)
106        .with_fill(Color::rgb8(0x38, 0xbd, 0xf8));
107    project.scene.add(&label);
108
109    // ── Sequential: single full-size mask ──
110    let spot = Circle::default()
111        .with_position(Vec2::new(-250.0, 0.0))
112        .with_radius(120.0)
113        .with_fill(Color::WHITE);
114
115    let big = MaskNode::new(
116        Box::new(spot.clone()),
117        Box::new(make_source(500.0, 300.0, 44.0)),
118    )
119    .with_position(Vec2::new(640.0, 380.0))
120    .with_mode(MaskMode::Intersect);
121    project.scene.add(&big);
122
123    // ── Side-by-side: 4 small panels (initially hidden) ──
124    let xs: [f32; 4] = [250.0, 510.0, 770.0, 1030.0];
125    let sy = 380.0;
126
127    let (s0, p0, t0) = make_panel(
128        &mut project,
129        xs[0],
130        sy,
131        MaskMode::Intersect,
132        "Intersect",
133        Color::rgb8(0x38, 0xbd, 0xf8),
134    );
135    let (s1, p1, t1) = make_panel(
136        &mut project,
137        xs[1],
138        sy,
139        MaskMode::Subtract,
140        "Subtract",
141        Color::rgb8(0xf4, 0x3f, 0x5e),
142    );
143    let (s2, p2, t2) = make_panel(
144        &mut project,
145        xs[2],
146        sy,
147        MaskMode::Exclude,
148        "Exclude",
149        Color::rgb8(0xa8, 0x55, 0xf7),
150    );
151    let (s3, p3, t3) = make_panel(
152        &mut project,
153        xs[3],
154        sy,
155        MaskMode::Union,
156        "Union",
157        Color::rgb8(0x10, 0xb9, 0x81),
158    );
159
160    // ── Timeline ──
161    project.scene.video_timeline.add(loop_anim!(
162        chain![
163            wait(BEAT),
164            // --- Intersect: slide left→right ---
165            spot.position.to(Vec2::new(250.0, 0.0), SLIDE),
166            wait(BEAT),
167            // reset spot, switch to Subtract
168            all![
169                spot.position.to(Vec2::new(-250.0, 0.0), Duration::ZERO),
170                label
171                    .text
172                    .to("Subtract (Source Out)".into(), Duration::ZERO),
173                label
174                    .fill_paint
175                    .to(Paint::Solid(Color::rgb8(0xf4, 0x3f, 0x5e)), Duration::ZERO),
176                big.mode.to(MaskMode::Subtract, Duration::ZERO),
177            ],
178            wait(BEAT),
179            // --- Subtract: slide left→right ---
180            spot.position.to(Vec2::new(250.0, 0.0), SLIDE),
181            wait(BEAT),
182            // reset spot, switch to Exclude
183            all![
184                spot.position.to(Vec2::new(-250.0, 0.0), Duration::ZERO),
185                label.text.to("Exclude (XOR)".into(), Duration::ZERO),
186                label
187                    .fill_paint
188                    .to(Paint::Solid(Color::rgb8(0xa8, 0x55, 0xf7)), Duration::ZERO),
189                big.mode.to(MaskMode::Exclude, Duration::ZERO),
190            ],
191            wait(BEAT),
192            // --- Exclude: slide left→right ---
193            spot.position.to(Vec2::new(250.0, 0.0), SLIDE),
194            wait(BEAT),
195            // reset spot, switch to Union
196            all![
197                spot.position.to(Vec2::new(-250.0, 0.0), Duration::ZERO),
198                label.text.to("Union (Source Over)".into(), Duration::ZERO),
199                label
200                    .fill_paint
201                    .to(Paint::Solid(Color::rgb8(0x10, 0xb9, 0x81)), Duration::ZERO),
202                big.mode.to(MaskMode::Union, Duration::ZERO),
203            ],
204            wait(BEAT),
205            // --- Union: slide left→right ---
206            spot.position.to(Vec2::new(250.0, 0.0), SLIDE),
207            wait(BEAT),
208            // --- Side-by-side: show all 4, hide big ---
209            all![
210                big.opacity.to(0.0, Duration::ZERO),
211                label.text.to("All Modes".into(), Duration::ZERO),
212                label
213                    .fill_paint
214                    .to(Paint::Solid(Color::rgb8(0xe2, 0xe8, 0xf0)), Duration::ZERO),
215                p0.opacity.to(1.0, Duration::ZERO),
216                p1.opacity.to(1.0, Duration::ZERO),
217                p2.opacity.to(1.0, Duration::ZERO),
218                p3.opacity.to(1.0, Duration::ZERO),
219                t0.opacity.to(1.0, Duration::ZERO),
220                t1.opacity.to(1.0, Duration::ZERO),
221                t2.opacity.to(1.0, Duration::ZERO),
222                t3.opacity.to(1.0, Duration::ZERO),
223            ],
224            wait(BEAT),
225            // All 4 spots slide left→right simultaneously
226            all![
227                s0.position.to(Vec2::new(110.0, 0.0), SLIDE),
228                s1.position.to(Vec2::new(110.0, 0.0), SLIDE),
229                s2.position.to(Vec2::new(110.0, 0.0), SLIDE),
230                s3.position.to(Vec2::new(110.0, 0.0), SLIDE),
231            ],
232            wait(BEAT),
233            // --- Reset everything for loop ---
234            all![
235                // Hide small panels
236                p0.opacity.to(0.0, Duration::ZERO),
237                p1.opacity.to(0.0, Duration::ZERO),
238                p2.opacity.to(0.0, Duration::ZERO),
239                p3.opacity.to(0.0, Duration::ZERO),
240                t0.opacity.to(0.0, Duration::ZERO),
241                t1.opacity.to(0.0, Duration::ZERO),
242                t2.opacity.to(0.0, Duration::ZERO),
243                t3.opacity.to(0.0, Duration::ZERO),
244                // Reset small spots
245                s0.position.to(Vec2::new(-110.0, 0.0), Duration::ZERO),
246                s1.position.to(Vec2::new(-110.0, 0.0), Duration::ZERO),
247                s2.position.to(Vec2::new(-110.0, 0.0), Duration::ZERO),
248                s3.position.to(Vec2::new(-110.0, 0.0), Duration::ZERO),
249                // Restore big mask
250                big.opacity.to(1.0, Duration::ZERO),
251                big.mode.to(MaskMode::Intersect, Duration::ZERO),
252                spot.position.to(Vec2::new(-250.0, 0.0), Duration::ZERO),
253                label
254                    .text
255                    .to("Intersect (Source In)".into(), Duration::ZERO),
256                label
257                    .fill_paint
258                    .to(Paint::Solid(Color::rgb8(0x38, 0xbd, 0xf8)), Duration::ZERO),
259            ],
260        ],
261        None
262    ));
263
264    project.show().expect("Failed to render");
265}
examples/physics_demo.rs (line 724)
720fn main() {
721    let mut project = Project::default()
722        .with_dimensions(W, H)
723        .with_title("Physics Demo")
724        .with_background(BG)
725        .close_on_finish();
726
727    // ── Build all scenes ──
728    let (p1, t1, s1) = build_scene1();
729    let (p2, t2, s2, labels2) = build_scene2();
730    let (p3, t3, s3, labels3) = build_scene3();
731    let (p4, t4, s4) = build_scene4();
732    let (p5, t5, s5, time5_var, links5) = build_scene5();
733    let (p6, t6, s6) = build_scene6();
734    let (p7, t7, s7, _cube_refs7, labels7, bindings7) = build_scene7();
735    let (p8, t8, s8, text8, balls8, status8) = build_scene8();
736    let (p9, t9, s9) = build_scene9();
737    let (p10, t10, s10) = build_scene10();
738
739    // ── Add all to scene (all invisible) ──
740    project.scene.add(&p1);
741    project.scene.add(&t1);
742    project.scene.add(&s1);
743
744    project.scene.add(&p2);
745    project.scene.add(&t2);
746    project.scene.add(&s2);
747    let labels2_c: Vec<_> = labels2
748        .iter()
749        .map(|l| {
750            project.scene.add(l);
751            l.clone()
752        })
753        .collect();
754
755    project.scene.add(&p3);
756    project.scene.add(&t3);
757    project.scene.add(&s3);
758    let labels3_c: Vec<_> = labels3
759        .iter()
760        .map(|l| {
761            project.scene.add(l);
762            l.clone()
763        })
764        .collect();
765
766    project.scene.add(&p4);
767    project.scene.add(&t4);
768    project.scene.add(&s4);
769
770    project.scene.add(&p5);
771    project.scene.add(&t5);
772    project.scene.add(&s5);
773
774    for link in links5 {
775        project.scene.add(link);
776    }
777
778    project.scene.add(&p6);
779    project.scene.add(&t6);
780    project.scene.add(&s6);
781
782    project.scene.add(&p7);
783    project.scene.add(&t7);
784    project.scene.add(&s7);
785    let labels7_c: Vec<_> = labels7
786        .iter()
787        .map(|l| {
788            project.scene.add(l);
789            l.clone()
790        })
791        .collect();
792    for binding in bindings7 {
793        project.scene.add(binding);
794    }
795
796    project.scene.add(&p8);
797    project.scene.add(&t8);
798    project.scene.add(&s8);
799    project.scene.add(&status8);
800
801    project.scene.add(&p9);
802    project.scene.add(&t9);
803    project.scene.add(&s9);
804
805    project.scene.add(&p10);
806    project.scene.add(&t10);
807    project.scene.add(&s10);
808
809    // ── Timeline: one scene at a time ──
810    project.scene.video_timeline.add(chain![
811        // Scene 1: Gravity
812        t1.opacity.to(1.0, FADE),
813        wait!(1),
814        all![p1.opacity.to(1.0, FADE), s1.opacity.to(1.0, FADE),],
815        wait!(4.0),
816        all![
817            p1.opacity.to(0.0, FADE),
818            t1.opacity.to(0.0, FADE),
819            s1.opacity.to(0.0, FADE),
820        ],
821        // Scene 2: Bounciness
822        t2.opacity.to(1.0, FADE),
823        wait!(1),
824        all![
825            p2.opacity.to(1.0, FADE),
826            s2.opacity.to(1.0, FADE),
827            labels2_c[0].opacity.to(1.0, FADE),
828            labels2_c[1].opacity.to(1.0, FADE),
829            labels2_c[2].opacity.to(1.0, FADE),
830        ],
831        wait!(5.0),
832        all![
833            p2.opacity.to(0.0, FADE),
834            s2.opacity.to(0.0, FADE),
835            t2.opacity.to(0.0, FADE),
836            labels2_c[0].opacity.to(0.0, FADE),
837            labels2_c[1].opacity.to(0.0, FADE),
838            labels2_c[2].opacity.to(0.0, FADE),
839        ],
840        // Scene 3: Shapes
841        t3.opacity.to(1.0, FADE),
842        wait!(1),
843        all![
844            p3.opacity.to(1.0, FADE),
845            s3.opacity.to(1.0, FADE),
846            labels3_c[0].opacity.to(1.0, FADE),
847            labels3_c[1].opacity.to(1.0, FADE),
848        ],
849        wait!(5.0),
850        all![
851            p3.opacity.to(0.0, FADE),
852            t3.opacity.to(0.0, FADE),
853            s3.opacity.to(0.0, FADE),
854            labels3_c[0].opacity.to(0.0, FADE),
855            labels3_c[1].opacity.to(0.0, FADE),
856        ],
857        // Scene 4: Stacking
858        t4.opacity.to(1.0, FADE),
859        wait!(1),
860        all![p4.opacity.to(1.0, FADE), s4.opacity.to(1.0, FADE),],
861        wait!(5.0),
862        all![
863            p4.opacity.to(0.0, FADE),
864            t4.opacity.to(0.0, FADE),
865            s4.opacity.to(0.0, FADE),
866        ],
867        // Scene 5: Kinematic Containers
868        t5.opacity.to(1.0, FADE),
869        wait!(1),
870        all![
871            p5.opacity.to(1.0, FADE),
872            s5.opacity.to(1.0, FADE),
873            time5_var.to(4.0, Duration::from_secs(4)),
874        ],
875        wait!(4.0),
876        all![
877            p5.opacity.to(0.0, FADE),
878            t5.opacity.to(0.0, FADE),
879            s5.opacity.to(0.0, FADE),
880        ],
881        // Scene 6: Zero Gravity
882        t6.opacity.to(1.0, FADE),
883        wait!(1),
884        all![p6.opacity.to(1.0, FADE), s6.opacity.to(1.0, FADE),],
885        wait!(5.0),
886        all![
887            p6.opacity.to(0.0, FADE),
888            t6.opacity.to(0.0, FADE),
889            s6.opacity.to(0.0, FADE),
890        ],
891        // Scene 7: Friction
892        t7.opacity.to(1.0, FADE),
893        wait!(1),
894        all![
895            p7.opacity.to(1.0, FADE),
896            s7.opacity.to(1.0, FADE),
897            labels7_c[0].opacity.to(1.0, FADE),
898            labels7_c[1].opacity.to(1.0, FADE),
899            labels7_c[2].opacity.to(1.0, FADE),
900        ],
901        wait!(3.0),
902        all![
903            p7.opacity.to(0.0, FADE),
904            t7.opacity.to(0.0, FADE),
905            s7.opacity.to(0.0, FADE),
906            labels7_c[0].opacity.to(0.0, FADE),
907            labels7_c[1].opacity.to(0.0, FADE),
908            labels7_c[2].opacity.to(0.0, FADE),
909        ],
910        // Scene 8: Mode Switching — full demonstration
911        t8.opacity.to(1.0, FADE),
912        wait!(1),
913        all![
914            p8.opacity.to(1.0, FADE),
915            s8.opacity.to(1.0, FADE),
916            status8.opacity.to(1.0, FADE),
917        ],
918        // ── Phase 1: Disabled — signal-driven tweens ──
919        status8
920            .text
921            .to("mode: Disabled".to_string(), Duration::from_millis(1)),
922        wait!(0.5),
923        // Slide text left
924        text8
925            .position
926            .to(Vec2::new(CX - 200.0, 270.0), Duration::from_millis(800)),
927        wait!(0.3),
928        // Slide text right
929        text8
930            .position
931            .to(Vec2::new(CX + 200.0, 270.0), Duration::from_millis(800)),
932        wait!(0.3),
933        // Slide back to center
934        text8
935            .position
936            .to(Vec2::new(CX, 270.0), Duration::from_millis(600)),
937        wait!(1.0),
938        // Enable balls as Dynamic so they fall onto the disabled text
939        all![
940            balls8[0]
941                .mode
942                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
943            balls8[1]
944                .mode
945                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
946            balls8[2]
947                .mode
948                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
949            balls8[3]
950                .mode
951                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
952        ],
953        wait!(3.0),
954        // ── Phase 2: Dynamic — physics takes over, text falls ──
955        status8
956            .text
957            .to("mode: Dynamic".to_string(), Duration::from_millis(1)),
958        text8
959            .mode
960            .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
961        wait!(1.0),
962        // Enable balls as Dynamic so they fall onto the dynamic text
963        all![
964            balls8[4]
965                .mode
966                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
967            balls8[5]
968                .mode
969                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
970            balls8[6]
971                .mode
972                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
973            balls8[7]
974                .mode
975                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
976        ],
977        wait!(3.0),
978        // ── Phase 3: Kinematic — text becomes a platform, balls drop onto it ──
979        status8
980            .text
981            .to("mode: Kinematic".to_string(), Duration::from_millis(1)),
982        text8
983            .mode
984            .to(PhysicsMode::Kinematic, Duration::from_millis(1)),
985        // Tween text up to act as a shelf
986        text8
987            .position
988            .to(Vec2::new(CX, 300.0), Duration::from_millis(800)),
989        // Enable remaining balls as Dynamic so they fall onto the kinematic text
990        all![
991            balls8[8]
992                .mode
993                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
994            balls8[9]
995                .mode
996                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
997            balls8[10]
998                .mode
999                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
1000            balls8[11]
1001                .mode
1002                .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
1003        ],
1004        wait!(2.0),
1005        // Oscillate the kinematic shelf to show balls react
1006        text8
1007            .position
1008            .to(Vec2::new(CX - 100.0, 280.0), Duration::from_millis(600)),
1009        text8
1010            .position
1011            .to(Vec2::new(CX + 100.0, 320.0), Duration::from_millis(600)),
1012        text8
1013            .position
1014            .to(Vec2::new(CX, 300.0), Duration::from_millis(400)),
1015        wait!(2.0),
1016        all![
1017            p8.opacity.to(0.0, FADE),
1018            t8.opacity.to(0.0, FADE),
1019            s8.opacity.to(0.0, FADE),
1020            status8.opacity.to(0.0, FADE),
1021        ],
1022        // Scene 9: Custom Colliders
1023        t9.opacity.to(1.0, FADE),
1024        wait!(1),
1025        all![p9.opacity.to(1.0, FADE), s9.opacity.to(1.0, FADE),],
1026        wait!(6.0),
1027        all![
1028            p9.opacity.to(0.0, FADE),
1029            t9.opacity.to(0.0, FADE),
1030            s9.opacity.to(0.0, FADE),
1031        ],
1032        // Scene 10: Final Example
1033        t10.opacity.to(1.0, FADE),
1034        wait!(1),
1035        all![p10.opacity.to(1.0, FADE), s10.opacity.to(1.0, FADE),],
1036        wait!(10.0),
1037        all![
1038            p10.opacity.to(0.0, FADE),
1039            t10.opacity.to(0.0, FADE),
1040            s10.opacity.to(0.0, FADE),
1041        ],
1042        wait!(1),
1043    ]);
1044
1045    project.show().expect("Failed to render");
1046}
Source

pub fn with_close_on_finish(self, close: bool) -> Self

Sets whether the window should close automatically on finish.

Source

pub fn close_on_finish(self) -> Self

Convenience method to enable automatic window closing on finish.

Examples found in repository?
examples/math_animation.rs (line 10)
4fn main() {
5    // 1. Initialize Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_dimensions(500, 500)
9        .with_title("Math Animation")
10        .close_on_finish();
11
12    // 2. Create MathNode (Typst syntax)
13    let tex = MathNode::default()
14        .with_position(Vec2::new(250.0, 250.0))
15        .with_equation("y = a x^2")
16        .with_font_size(48.0)
17        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2));
18    project.scene.add(&tex);
19
20    // 3. Define Animation Sequence
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                tex.tex("y = a x^2 + b x", Duration::from_secs(1)),
25                tex.tex("e^(i pi) + 1 = 0", Duration::from_secs(1)),
26                tex.tex("y = a x^2", Duration::from_secs(1)),
27            ]
28        },
29        None,
30    ));
31
32    // 4. Run interactive preview
33    project.show().expect("Failed to render");
34}
More examples
Hide additional examples
examples/color_interpolation.rs (line 10)
4fn main() {
5    let mut project = Project::default()
6        .with_dimensions(300, 300)
7        .with_fps(60)
8        .with_title("Color Interpolation")
9        .with_cache(true)
10        .close_on_finish();
11
12    let circle = Circle::default()
13        .with_position(Vec2::new(150.0, 150.0))
14        .with_radius(50.0)
15        .with_fill(Color::RED); // Red
16
17    project.scene.add(&circle);
18
19    let duration = Duration::from_secs(1);
20
21    project.scene.video_timeline.add(loop_anim(
22        move || {
23            chain![
24                circle.fill_paint.to(Paint::Solid(Color::YELLOW), duration),
25                circle.fill_paint.to(Paint::Solid(Color::GREEN), duration),
26                circle.fill_paint.to(Paint::Solid(Color::BLUE), duration),
27                circle.fill_paint.to(Paint::Solid(Color::RED), duration),
28            ]
29        },
30        None,
31    ));
32
33    // Show
34    project.show().expect("Failed to render");
35}
examples/getting_started.rs (line 10)
4fn main() {
5    // 1. Initialize the Project
6    let mut project = Project::default()
7        .with_fps(60)
8        .with_cache(true)
9        .with_title("Getting Started")
10        .close_on_finish();
11
12    // 2. Define Nodes
13    let circle = Circle::default()
14        .with_position(Vec2::new(400.0, 300.0))
15        .with_radius(50.0)
16        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)); // Red
17
18    let text = TextNode::default()
19        .with_position(Vec2::new(400.0, 450.0))
20        .with_text("Hello Rust")
21        .with_font_size(40.0)
22        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White-ish
23
24    // 3. Add Nodes to the Scene
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 4. Add Animations to the Timeline
29    project.scene.video_timeline.add(all![
30        circle.radius.to(100.0, Duration::from_secs(1)),
31        text.position
32            .to(Vec2::new(400.0, 500.0), Duration::from_secs(1)),
33    ]);
34
35    // 5. Show
36    project.show().expect("Failed to render");
37}
examples/code_advanced.rs (line 7)
4fn main() {
5    let mut project = Project::default()
6        .with_title("Code Advanced")
7        .close_on_finish();
8
9    let code = CodeNode::default()
10        .with_position(Vec2::new(50.0, 50.0))
11        .with_code(
12            r#"fn main() {
13    println!("Hello");
14}"#,
15        )
16        .with_language("rust")
17        .with_font_size(32.0)
18        .with_dim_opacity(0.1);
19
20    project.scene.add(&code);
21
22    project.scene.video_timeline.add(sequence![
23        Duration::from_secs(1),
24        // 1. Select line 2 (println) - using 1-based index string
25        code.select_string("2", Duration::from_millis(300)),
26        // 2. Select range 1-2
27        code.select_string("1-2", Duration::from_millis(300)),
28        // 3. Append a comment
29        code.append("\n// Done!", Duration::from_millis(300)),
30        // 3. Reset selection
31        code.select_lines(vec![], Duration::from_millis(300)),
32        // 4. Prepend a header (Now natively lazy, no wrapper needed!)
33        code.prepend("// My Script\n", Duration::from_millis(300)),
34    ]);
35
36    project.show().expect("Failed to render");
37}
examples/polygon.rs (line 5)
4fn main() {
5    let mut project = Project::default().with_title("Polygon").close_on_finish();
6
7    // Create a regular pentagon
8    let pentagon = Polygon::regular(5, 100.0)
9        .with_fill(Color::rgb8(0xe1, 0x32, 0x38)) // Red
10        .with_position(Vec2::new(200.0, 300.0))
11        .with_scale(0.0);
12
13    // Create a custom triangle
14    let triangle = Polygon::default()
15        .with_position(Vec2::new(500.0, 300.0))
16        .with_points(vec![
17            Vec2::new(0.0, -100.0),
18            Vec2::new(100.0, 100.0),
19            Vec2::new(-100.0, 100.0),
20        ])
21        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)) // Blue
22        .with_stroke(Color::WHITE, 4.0);
23
24    project.scene.add(&pentagon);
25    project.scene.add(&triangle);
26
27    // Animate rotation and opacity
28    project.scene.video_timeline.add(all![
29        chain![
30            pentagon.scale.to(Vec2::ONE, Duration::from_secs(1)),
31            pentagon
32                .rotation
33                .to(std::f32::consts::PI, Duration::from_secs(2)),
34            pentagon.scale.to(-Vec2::ONE, Duration::from_secs(1)),
35        ],
36        chain![
37            triangle.opacity.to(1.0, Duration::from_secs(1)),
38            triangle
39                .position
40                .to(Vec2::new(500.0, 300.0), Duration::from_secs(1)),
41            triangle
42                .rotation
43                .to(360.0_f32.to_radians(), Duration::from_secs(1))
44        ],
45    ]);
46
47    project.show().expect("Failed to render");
48}
examples/export.rs (line 11)
4fn main() {
5    // 1. Initialize for Export
6    let mut project = Project::default()
7        .with_fps(30)
8        .with_ffmpeg(true)
9        .with_title("Export")
10        .with_output_path("output")
11        .close_on_finish();
12
13    // 2. Setup Nodes
14    let circle = Circle::default()
15        .with_position(Vec2::new(400.0, 300.0))
16        .with_radius(50.0)
17        .with_fill(Color::rgb8(0x68, 0xab, 0xdf)); // Blue
18
19    let text = TextNode::default()
20        .with_position(Vec2::new(400.0, 50.0))
21        .with_text("Export Demo")
22        .with_font_size(40.0)
23        .with_fill(Color::rgb8(0xf2, 0xf2, 0xf2)); // White
24
25    project.scene.add(&circle);
26    project.scene.add(&text);
27
28    // 3. Define Animations (Color and Font Size)
29    project.scene.video_timeline.add(all![
30        // Circle color and size
31        circle
32            .fill_paint
33            .to(
34                Paint::Solid(Color::rgb8(0xf2, 0xf2, 0xf2)),
35                Duration::from_secs(2)
36            )
37            .ease(easings::quad_in_out),
38        circle
39            .radius
40            .to(150.0, Duration::from_secs(2))
41            .ease(easings::elastic_out),
42        // Text font size
43        text.font_size
44            .to(50.0, Duration::from_secs(2))
45            .ease(easings::cubic_out),
46    ]);
47
48    // 4. Export (Renders frames and combines them into out.mkv)
49    println!("Starting export to {}...", project.output_path.display());
50    project.export().expect("Failed to export");
51}
Source

pub fn seek_to(&mut self, target_time: Duration)

Resets the scene and advances it to the specified target time.

This is used for seeking in the interactive preview.

Source

pub fn get_frame_name(&self, frame_index: u32) -> String

Returns the sanitized filename for a specific frame index.

Trait Implementations§

Source§

impl Default for Project

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl ProjectRuntimeExt for Project

Source§

fn show(self) -> Result<()>

Opens the playback window for the project. Read more
Source§

fn export(&mut self) -> Result<()>

Starts an export session for the project. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for S
where T: Real + Zero + Arithmetics + Clone, Swp: WhitePoint<T>, Dwp: WhitePoint<T>, D: AdaptFrom<S, Swp, Dwp, T>,

Source§

fn adapt_into_using<M>(self, method: M) -> D
where M: TransformMatrix<T>,

Convert the source color to the destination color using the specified method.
Source§

fn adapt_into(self) -> D

Convert the source color to the destination color using the bradford method by default.
Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T, C> ArraysFrom<C> for T
where C: IntoArrays<T>,

Source§

fn arrays_from(colors: C) -> T

Cast a collection of colors into a collection of arrays.
Source§

impl<T, C> ArraysInto<C> for T
where C: FromArrays<T>,

Source§

fn arrays_into(self) -> C

Cast this collection of arrays into a collection of colors.
Source§

impl<T> Az for T

Source§

fn az<Dst>(self) -> Dst
where T: Cast<Dst>,

Casts the value.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for U
where T: FromCam16Unclamped<WpParam, U>,

Source§

type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar

The number type that’s used in parameters when converting.
Source§

fn cam16_into_unclamped( self, parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>, ) -> T

Converts self into C, using the provided parameters.
Source§

impl<Src, Dst> CastFrom<Src> for Dst
where Src: Cast<Dst>,

Source§

fn cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> CheckedAs for T

Source§

fn checked_as<Dst>(self) -> Option<Dst>
where T: CheckedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> CheckedCastFrom<Src> for Dst
where Src: CheckedCast<Dst>,

Source§

fn checked_cast_from(src: Src) -> Option<Dst>

Casts the value.
Source§

impl<T, C> ComponentsFrom<C> for T
where C: IntoComponents<T>,

Source§

fn components_from(colors: C) -> T

Cast a collection of colors into a collection of color components.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> Filterable for T

Source§

fn filterable( self, filter_name: &'static str, ) -> RequestFilterDataProvider<T, fn(DataRequest<'_>) -> bool>

Creates a filterable data provider with the given name for debugging. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromAngle<T> for T

Source§

fn from_angle(angle: T) -> T

Performs a conversion from angle.
Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T, U> FromStimulus<U> for T
where U: IntoStimulus<T>,

Source§

fn from_stimulus(other: U) -> T

Converts other into Self, while performing the appropriate scaling, rounding and clamping.
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> IntoAngle<U> for T
where U: FromAngle<T>,

Source§

fn into_angle(self) -> U

Performs a conversion into T.
Source§

impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for U
where T: Cam16FromUnclamped<WpParam, U>,

Source§

type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar

The number type that’s used in parameters when converting.
Source§

fn into_cam16_unclamped( self, parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>, ) -> T

Converts self into C, using the provided parameters.
Source§

impl<T, U> IntoColor<U> for T
where U: FromColor<T>,

Source§

fn into_color(self) -> U

Convert into T with values clamped to the color defined bounds Read more
Source§

impl<T, U> IntoColorUnclamped<U> for T
where U: FromColorUnclamped<T>,

Source§

fn into_color_unclamped(self) -> U

Convert into T. The resulting color might be invalid in its color space Read more
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> IntoStimulus<T> for T

Source§

fn into_stimulus(self) -> T

Converts self into T, while performing the appropriate scaling, rounding and clamping.
Source§

impl<T> OverflowingAs for T

Source§

fn overflowing_as<Dst>(self) -> (Dst, bool)
where T: OverflowingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> OverflowingCastFrom<Src> for Dst
where Src: OverflowingCast<Dst>,

Source§

fn overflowing_cast_from(src: Src) -> (Dst, bool)

Casts the value.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> SaturatingAs for T

Source§

fn saturating_as<Dst>(self) -> Dst
where T: SaturatingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> SaturatingCastFrom<Src> for Dst
where Src: SaturatingCast<Dst>,

Source§

fn saturating_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> StrictAs for T

Source§

fn strict_as<Dst>(self) -> Dst
where T: StrictCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> StrictCastFrom<Src> for Dst
where Src: StrictCast<Dst>,

Source§

fn strict_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, C> TryComponentsInto<C> for T
where C: TryFromComponents<T>,

Source§

type Error = <C as TryFromComponents<T>>::Error

The error for when try_into_colors fails to cast.
Source§

fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>

Try to cast this collection of color components into a collection of colors. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T, U> TryIntoColor<U> for T
where U: TryFromColor<T>,

Source§

fn try_into_color(self) -> Result<U, OutOfBounds<U>>

Convert into T, returning ok if the color is inside of its defined range, otherwise an OutOfBounds error is returned which contains the unclamped color. Read more
Source§

impl<C, U> UintsFrom<C> for U
where C: IntoUints<U>,

Source§

fn uints_from(colors: C) -> U

Cast a collection of colors into a collection of unsigned integers.
Source§

impl<C, U> UintsInto<C> for U
where C: FromUints<U>,

Source§

fn uints_into(self) -> C

Cast this collection of unsigned integers into a collection of colors.
Source§

impl<T> UnwrappedAs for T

Source§

fn unwrapped_as<Dst>(self) -> Dst
where T: UnwrappedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> UnwrappedCastFrom<Src> for Dst
where Src: UnwrappedCast<Dst>,

Source§

fn unwrapped_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WrappingAs for T

Source§

fn wrapping_as<Dst>(self) -> Dst
where T: WrappingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> WrappingCastFrom<Src> for Dst
where Src: WrappingCast<Dst>,

Source§

fn wrapping_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> MaybeSendSync for T
where T: Send + Sync,

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,