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: u32Target width of the animation.
height: u32Target height of the animation.
fps: u32Frames per second.
title: StringHuman-readable title of the project.
scene: BaseSceneThe root scene orchestration node.
output_path: PathBufDirectory where exported frames and videos will be saved.
use_cache: boolWhether to use frame-level caching during export.
use_ffmpeg: boolWhether to automatically attempt FFmpeg encoding after export.
use_gpu: boolWhether to use GPU acceleration for rendering.
background_color: ColorBackground clear color for the animation.
close_on_finish: boolIf true, the playback window will close automatically when the animation ends.
current_time: DurationThe current playback/export time.
paused: boolPlayback state (paused or playing).
speed: f32Playback speed multiplier.
timeline: TimelineThe master playback timeline state.
cache_write_interval: u32The number of frames between incremental cache manifest writes to disk.
Implementations§
Source§impl Project
impl Project
Sourcepub fn new(width: u32, height: u32) -> Self
pub fn new(width: u32, height: u32) -> Self
Creates a new project with the given dimensions and default settings.
Examples found in repository?
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
impl Project
Sourcepub fn with_fps(self, fps: u32) -> Self
pub fn with_fps(self, fps: u32) -> Self
Sets the target frames per second.
Examples found in repository?
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
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}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}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}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}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(÷r_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}Sourcepub fn with_dimensions(self, width: u32, height: u32) -> Self
pub fn with_dimensions(self, width: u32, height: u32) -> Self
Sets the width and height of the animation.
Examples found in repository?
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
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}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}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}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}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}Sourcepub fn with_title(self, title: &str) -> Self
pub fn with_title(self, title: &str) -> Self
Sets the project title.
Examples found in repository?
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
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}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}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}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}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/audio_demo.rs
- examples/images.rs
- examples/group_animation.rs
- examples/grid.rs
- examples/shapes.rs
- examples/camera_demo.rs
- examples/code_animation.rs
- examples/nested_cameras.rs
- examples/math_code.rs
- examples/easing_scope.rs
- examples/signals.rs
- examples/blur_demo.rs
- examples/advanced_flow.rs
- examples/anchors.rs
- examples/mask_demo.rs
- examples/physics_demo.rs
- examples/world_map.rs
- examples/news_feed.rs
- examples/gradient_demo.rs
- examples/explainer.rs
Sourcepub fn with_output_path(self, path: &str) -> Self
pub fn with_output_path(self, path: &str) -> Self
Sets the output directory for exports.
Examples found in repository?
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
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(÷r_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}Sourcepub fn with_cache(self, use_cache: bool) -> Self
pub fn with_cache(self, use_cache: bool) -> Self
Enables or disables frame-level caching.
Examples found in repository?
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
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}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(÷r_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}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}Sourcepub fn with_cache_write_interval(self, interval: u32) -> Self
pub fn with_cache_write_interval(self, interval: u32) -> Self
Sets the cache manifest write interval (number of frames).
Sourcepub fn with_ffmpeg(self, use_ffmpeg: bool) -> Self
pub fn with_ffmpeg(self, use_ffmpeg: bool) -> Self
Enables or disables automatic FFmpeg encoding.
Examples found in repository?
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
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(÷r_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}Sourcepub fn with_gpu(self, use_gpu: bool) -> Self
pub fn with_gpu(self, use_gpu: bool) -> Self
Enables or disables GPU acceleration.
Examples found in repository?
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(÷r_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}Sourcepub fn with_background(self, color: Color) -> Self
pub fn with_background(self, color: Color) -> Self
Sets the background clear color.
Examples found in repository?
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
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}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}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}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}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}Sourcepub fn with_close_on_finish(self, close: bool) -> Self
pub fn with_close_on_finish(self, close: bool) -> Self
Sets whether the window should close automatically on finish.
Sourcepub fn close_on_finish(self) -> Self
pub fn close_on_finish(self) -> Self
Convenience method to enable automatic window closing on finish.
Examples found in repository?
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
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}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}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}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}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/audio_demo.rs
- examples/images.rs
- examples/group_animation.rs
- examples/grid.rs
- examples/camera_demo.rs
- examples/code_animation.rs
- examples/nested_cameras.rs
- examples/math_code.rs
- examples/easing_scope.rs
- examples/signals.rs
- examples/blur_demo.rs
- examples/advanced_flow.rs
- examples/anchors.rs
- examples/mask_demo.rs
- examples/physics_demo.rs
- examples/world_map.rs
- examples/news_feed.rs
- examples/gradient_demo.rs
- examples/explainer.rs
Sourcepub fn seek_to(&mut self, target_time: Duration)
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.
Sourcepub fn get_frame_name(&self, frame_index: u32) -> String
pub fn get_frame_name(&self, frame_index: u32) -> String
Returns the sanitized filename for a specific frame index.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Project
impl !RefUnwindSafe for Project
impl Send for Project
impl Sync for Project
impl Unpin for Project
impl UnsafeUnpin for Project
impl !UnwindSafe for Project
Blanket Implementations§
Source§impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
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) -> Dwhere
M: TransformMatrix<T>,
fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
Source§fn adapt_into(self) -> D
fn adapt_into(self) -> D
Source§impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
Source§fn arrays_from(colors: C) -> T
fn arrays_from(colors: C) -> T
Source§impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
Source§fn arrays_into(self) -> C
fn arrays_into(self) -> C
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
Source§type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn cam16_into_unclamped(
self,
parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>,
) -> T
fn cam16_into_unclamped( self, parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T> CheckedAs for T
impl<T> CheckedAs for T
Source§fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
Source§impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
Source§fn checked_cast_from(src: Src) -> Option<Dst>
fn checked_cast_from(src: Src) -> Option<Dst>
Source§impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
Source§fn components_from(colors: C) -> T
fn components_from(colors: C) -> T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&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
impl<T> DowncastSync for T
Source§impl<T> Filterable for T
impl<T> Filterable for T
Source§fn filterable(
self,
filter_name: &'static str,
) -> RequestFilterDataProvider<T, fn(DataRequest<'_>) -> bool>
fn filterable( self, filter_name: &'static str, ) -> RequestFilterDataProvider<T, fn(DataRequest<'_>) -> bool>
Source§impl<T> FromAngle<T> for T
impl<T> FromAngle<T> for T
Source§fn from_angle(angle: T) -> T
fn from_angle(angle: T) -> T
angle.Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
Source§fn from_stimulus(other: U) -> T
fn from_stimulus(other: U) -> T
other into Self, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
Source§fn into_angle(self) -> U
fn into_angle(self) -> U
T.Source§impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
Source§type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn into_cam16_unclamped(
self,
parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>,
) -> T
fn into_cam16_unclamped( self, parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
Source§fn into_color(self) -> U
fn into_color(self) -> U
Source§impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
Source§fn into_color_unclamped(self) -> U
fn into_color_unclamped(self) -> U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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 moreSource§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
Source§impl<T> IntoStimulus<T> for T
impl<T> IntoStimulus<T> for T
Source§fn into_stimulus(self) -> T
fn into_stimulus(self) -> T
self into T, while performing the appropriate scaling,
rounding and clamping.Source§impl<T> OverflowingAs for T
impl<T> OverflowingAs for T
Source§fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
Source§impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
Source§fn overflowing_cast_from(src: Src) -> (Dst, bool)
fn overflowing_cast_from(src: Src) -> (Dst, bool)
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> SaturatingAs for T
impl<T> SaturatingAs for T
Source§fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
Source§impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
Source§fn saturating_cast_from(src: Src) -> Dst
fn saturating_cast_from(src: Src) -> Dst
Source§impl<T> StrictAs for T
impl<T> StrictAs for T
Source§fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
Source§impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
Source§fn strict_cast_from(src: Src) -> Dst
fn strict_cast_from(src: Src) -> Dst
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.Source§impl<T, U> ToSample<U> for Twhere
U: FromSample<T>,
impl<T, U> ToSample<U> for Twhere
U: FromSample<T>,
fn to_sample_(self) -> U
Source§impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
Source§type Error = <C as TryFromComponents<T>>::Error
type Error = <C as TryFromComponents<T>>::Error
try_into_colors fails to cast.Source§fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
Source§impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
Source§fn try_into_color(self) -> Result<U, OutOfBounds<U>>
fn try_into_color(self) -> Result<U, OutOfBounds<U>>
OutOfBounds error is returned which contains
the unclamped color. Read more