1use motion_canvas_rs::prelude::*;
2use std::time::Duration;
3
4const FONT: &str = "JetBrains Mono";
6const BG: Color = Color::rgb8(0x0e, 0x0e, 0x12);
7const WHITE: Color = Color::rgb8(0xf0, 0xf0, 0xf0);
8const DIM: Color = Color::rgb8(0x55, 0x55, 0x66);
9const ACCENT: Color = Color::rgb8(0x68, 0xab, 0xdf);
10const RED: Color = Color::rgb8(0xe1, 0x32, 0x38);
11const YELLOW: Color = Color::rgb8(0xe6, 0xa7, 0x00);
12const GREEN: Color = Color::rgb8(0x25, 0xc2, 0x81);
13const TEAL: Color = Color::rgb8(0x20, 0xb2, 0xaa);
14
15const CANVAS_W: u32 = 1280;
16const CANVAS_H: u32 = 720;
17const LEFT: f32 = 40.0;
18
19fn ms(n: u64) -> Duration {
20 Duration::from_millis(n)
21}
22fn secs(n: u64) -> Duration {
23 Duration::from_secs(n)
24}
25
26fn title(text: &str, y: f32) -> TextNode {
28 TextNode::default()
29 .with_anchor(Vec2::new(-1.0, -1.0))
30 .with_position(Vec2::new(LEFT, y))
31 .with_text(text)
32 .with_font_size(36.0)
33 .with_fill(ACCENT)
34 .with_font(FONT)
35 .with_opacity(0.0)
36}
37fn h2(text: &str, y: f32) -> TextNode {
38 TextNode::default()
39 .with_anchor(Vec2::new(-1.0, -1.0))
40 .with_position(Vec2::new(LEFT, y))
41 .with_text(text)
42 .with_font_size(22.0)
43 .with_fill(WHITE)
44 .with_font(FONT)
45 .with_opacity(0.0)
46}
47fn body(text: &str, y: f32) -> TextNode {
48 TextNode::default()
49 .with_anchor(Vec2::new(-1.0, -1.0))
50 .with_position(Vec2::new(LEFT, y))
51 .with_text(text)
52 .with_font_size(17.0)
53 .with_fill(Color::rgb8(0xcc, 0xcc, 0xdd))
54 .with_font(FONT)
55 .with_opacity(0.0)
56}
57fn dim(text: &str, x: f32, y: f32) -> TextNode {
58 TextNode::default()
59 .with_anchor(Vec2::new(-1.0, -1.0))
60 .with_position(Vec2::new(x, y))
61 .with_text(text)
62 .with_font_size(13.0)
63 .with_fill(DIM)
64 .with_font(FONT)
65 .with_opacity(0.0)
66}
67fn note(text: &str, y: f32) -> TextNode {
68 TextNode::default()
69 .with_anchor(Vec2::new(-1.0, -1.0))
70 .with_position(Vec2::new(LEFT + 20.0, y))
71 .with_text(text)
72 .with_font_size(14.0)
73 .with_fill(YELLOW)
74 .with_font(FONT)
75 .with_opacity(0.0)
76}
77fn code_block(code: &str, y: f32) -> CodeNode {
78 CodeNode::default()
79 .with_position(Vec2::new(LEFT + 20.0, y))
80 .with_code(code)
81 .with_language("rust")
82 .with_font(FONT)
83 .with_font_size(14.0)
84 .with_opacity(0.0)
85}
86fn hline(y: f32) -> Line {
87 Line::default()
88 .with_start(Vec2::new(LEFT, y))
89 .with_end(Vec2::new(LEFT, y))
90 .with_stroke(Color::rgba8(255, 255, 255, 25), 1.0)
91}
92fn show(opacity: &Signal<f32>, d: Duration) -> Box<dyn Animation> {
94 opacity.to(1.0, d).ease(easings::cubic_out).into()
95}
96fn hide(opacity: &Signal<f32>, d: Duration) -> Box<dyn Animation> {
97 opacity.to(0.0, d).ease(easings::cubic_in).into()
98}
99
100fn main() {
101 let mut project = Project::default()
102 .with_dimensions(CANVAS_W, CANVAS_H)
103 .with_fps(60)
104 .with_title("Explainer")
105 .with_background(BG)
106 .close_on_finish();
107
108 let s1_line = hline(100.0);
112 let s1_title = TextNode::default()
113 .with_anchor(Vec2::new(-1.0, -1.0))
114 .with_position(Vec2::new(LEFT, 120.0))
115 .with_text("motion-canvas-rs")
116 .with_font_size(52.0)
117 .with_fill(ACCENT)
118 .with_font(FONT)
119 .with_opacity(0.0);
120 let s1_sub = h2("A GPU-Accelerated Vector Animation Engine", 185.0);
121 let s1_built = body(
122 "Built on Vello + Typst — Inspired by Motion Canvas",
123 220.0,
124 );
125 let s1_desc = body(
126 "This animation will teach you how the library works,",
127 280.0,
128 );
129 let s1_desc2 = body(
130 "from struct definitions to the GPU rendering pipeline.",
131 305.0,
132 );
133
134 let s1_logo = SvgNode::default()
135 .with_position(Vec2::new(1142.0, 543.0))
136 .with_path("examples/images/motion-canvas-rs.svg")
137 .with_scale(0.3)
138 .with_size(Vec2::new(768.0, 768.0))
139 .with_opacity(0.0);
140
141 for n in [&s1_title, &s1_sub, &s1_built, &s1_desc, &s1_desc2] {
142 project.scene.add(n);
143 }
144 project.scene.add(&s1_line);
145 project.scene.add(&s1_logo);
146
147 let s2_h = title("Every program follows 5 steps", 50.0);
151 let steps = [
152 "1. Create a Project — your canvas settings",
153 "2. Create Nodes — shapes, text, images",
154 "3. Add Nodes to Scene — what gets drawn",
155 "4. Animate the Timeline — how things move",
156 "5. Show or Export — live window or video",
157 ];
158 let s2_texts: Vec<TextNode> = steps
159 .iter()
160 .enumerate()
161 .map(|(i, s)| body(s, 120.0 + i as f32 * 35.0))
162 .collect();
163
164 project.scene.add(&s2_h);
165 for t in &s2_texts {
166 project.scene.add(t);
167 }
168
169 let s3_h = title("What is a 'struct'?", 50.0);
173 let s3_explain = h2(
174 "A struct is a container that groups related data together.",
175 95.0,
176 );
177 let s3_analogy = body(
178 "Think of it like a class in Python/JS, but it only holds data.",
179 130.0,
180 );
181
182 let s3_code = code_block(
183 "pub struct Project {
184 pub width: u32, // Canvas width in pixels
185 pub height: u32, // Canvas height in pixels
186 pub fps: u32, // Frames per second
187 pub title: String, // Window title
188 pub scene: BaseScene, // Holds nodes + timelines
189 pub background_color: Color,
190 pub close_on_finish: bool,
191}",
192 175.0,
193 );
194
195 let s3_note = note(
196 "^ This is the actual Project struct from the library.",
197 420.0,
198 );
199 let s3_note2 = body(
200 "'pub' means public — anyone can read/write these fields.",
201 455.0,
202 );
203 let s3_note3 = body(
204 "u32 = unsigned 32-bit integer, String = text, bool = true/false",
205 485.0,
206 );
207
208 for n in [
209 &s3_h,
210 &s3_explain,
211 &s3_analogy,
212 &s3_note,
213 &s3_note2,
214 &s3_note3,
215 ] {
216 project.scene.add(n);
217 }
218 project.scene.add(&s3_code);
219
220 let s4_h = title("What is 'impl'?", 50.0);
224 let s4_explain = h2("impl adds methods (functions) to a struct.", 95.0);
225 let s4_analogy = body(
226 "Like adding methods to a class. Separated from the data.",
227 130.0,
228 );
229
230 let s4_code = code_block(
231 "impl Project {
232 pub fn with_fps(mut self, fps: u32) -> Self {
233 self.fps = fps; // set the value
234 self // return yourself (builder pattern)
235 }
236 pub fn with_title(mut self, title: &str) -> Self {
237 self.title = title.to_string();
238 self
239 }
240}",
241 175.0,
242 );
243
244 let s4_usage = body("Usage — chain calls to configure:", 430.0);
245 let s4_usage_code = code_block(
246 "let project = Project::default()
247 .with_fps(60)
248 .with_title(\"My Animation\")
249 .with_dimensions(800, 600)
250 .close_on_finish();",
251 460.0,
252 );
253
254 let s4_note = note(
255 "Each .with_*() returns 'self', so you can chain them.",
256 590.0,
257 );
258
259 for n in [&s4_h, &s4_explain, &s4_analogy, &s4_usage, &s4_note] {
260 project.scene.add(n);
261 }
262 project.scene.add(&s4_code);
263 project.scene.add(&s4_usage_code);
264
265 let s5_h = title("What is a 'trait'?", 50.0);
269 let s5_explain = h2(
270 "A trait is a contract — like an interface in Java/TypeScript.",
271 95.0,
272 );
273 let s5_analogy = body(
274 "Any type that implements a trait promises to provide those methods.",
275 130.0,
276 );
277
278 let s5_code = code_block(
279 "pub trait Node: Send + Sync + 'static {
280 fn render(&self, scene: &mut Scene,
281 parent_transform: Affine,
282 parent_opacity: f32);
283 fn update(&mut self, dt: Duration);
284 fn state_hash(&self) -> u64;
285 fn clone_node(&self) -> Box<dyn Node>;
286}",
287 175.0,
288 );
289
290 let s5_r = note(
291 "render() — draw yourself using current signal values",
292 410.0,
293 );
294 let s5_u = note(
295 "update(dt) — called every frame (for per-frame logic)",
296 435.0,
297 );
298 let s5_s = note(
299 "state_hash() — returns a number that changes when you change",
300 460.0,
301 );
302 let s5_c = note("clone_node() — make a deep copy of yourself", 485.0);
303 let s5_every = body(
304 "Circle, Rect, Line, TextNode, Polygon all implement Node.",
305 530.0,
306 );
307
308 for n in [
309 &s5_h,
310 &s5_explain,
311 &s5_analogy,
312 &s5_r,
313 &s5_u,
314 &s5_s,
315 &s5_c,
316 &s5_every,
317 ] {
318 project.scene.add(n);
319 }
320 project.scene.add(&s5_code);
321
322 let s6_h = title("The Built-in Nodes", 50.0);
326 let s6_sub = body(
327 "Each node uses the builder pattern and stores properties as Signals.",
328 90.0,
329 );
330
331 let demo_c = Circle::default()
332 .with_position(Vec2::new(120.0, 230.0))
333 .with_radius(40.0)
334 .with_fill(RED)
335 .with_opacity(0.0);
336 let demo_r = Rect::default()
337 .with_position(Vec2::new(293.0, 230.0))
338 .with_size(Vec2::new(80.0, 80.0))
339 .with_fill(ACCENT)
340 .with_radius(8.0)
341 .with_opacity(0.0);
342 let demo_l = Line::default()
343 .with_start(Vec2::new(420.0, 200.0))
344 .with_end(Vec2::new(510.0, 270.0))
345 .with_stroke(WHITE, 3.0)
346 .with_opacity(0.0);
347 let demo_p = Polygon::regular(5, 40.0)
348 .with_position(Vec2::new(606.0, 230.0))
349 .with_fill(YELLOW)
350 .with_opacity(0.0);
351 let demo_t = TextNode::default()
352 .with_position(Vec2::new(765.0, 230.0))
353 .with_text("Abc")
354 .with_font_size(36.0)
355 .with_fill(GREEN)
356 .with_font(FONT)
357 .with_opacity(0.0);
358
359 let lc = dim("Circle", 95.0, 285.0);
360 let lr = dim("Rect", 280.0, 285.0);
361 let ll = dim("Line", 445.0, 285.0);
362 let lp = dim("Polygon", 580.0, 285.0);
363 let lt = dim("TextNode", 735.0, 285.0);
364
365 let s6_box_h = h2("Why Box<dyn Node>?", 340.0);
366 let s6_box1 = body("The scene stores different node types in one list:", 375.0);
367 let s6_box_code = code_block(
368 "pub struct BaseScene {
369 pub nodes: Vec<Box<dyn Node>>, // a list of \"any Node\"
370}
371// 'Box' = heap-allocated, 'dyn Node' = any type implementing Node
372// Like List<INode> in Java or Array<Node> in TypeScript
373project.scene.add(circle); // wrap + add",
374 405.0,
375 );
376
377 for n in [&s6_h, &s6_sub, &lc, &lr, &ll, &lp, <, &s6_box_h, &s6_box1] {
378 project.scene.add(n);
379 }
380 project.scene.add(&demo_c);
381 project.scene.add(&demo_r);
382 project.scene.add(&demo_l);
383 project.scene.add(&demo_p);
384 project.scene.add(&demo_t);
385 project.scene.add(&s6_box_code);
386
387 let s7_h = title("Signals — The Reactive Core", 50.0);
391 let s7_sub = h2("Every animatable property is a Signal<T>.", 95.0);
392
393 let s7_code = code_block(
394 "pub struct Signal<T> {
395 pub data: Arc<Mutex<SignalData<T>>>,
396}
397pub struct SignalData<T> {
398 pub value: T, // the actual value (f32, Vec2, Color...)
399}",
400 140.0,
401 );
402
403 let s7_arc = note("Arc = shared pointer. Multiple owners, same data.", 310.0);
404 let s7_mutex = note(
405 "Mutex = lock. Only one thread reads/writes at a time.",
406 335.0,
407 );
408 let s7_why = body(
409 "Why? A node and its animation both need the same property:",
410 380.0,
411 );
412
413 let s7_diagram_code = code_block(
414 "let circle = Circle::default().with_radius(50.0);
415// circle.radius is a Signal<f32>
416
417circle.radius.to(100.0, Duration::from_secs(1));
418// Creates a SignalTween with a CLONE of circle.radius
419// Both point to the SAME underlying value (via Arc)
420
421// The animation WRITES new values each frame
422// The node READS them when rendering",
423 420.0,
424 );
425
426 let sig_demo = Circle::default()
428 .with_position(Vec2::new(1000.0, 400.0))
429 .with_radius(50.0)
430 .with_fill(RED)
431 .with_stroke(Color::rgba8(255, 255, 255, 50), 2.0)
432 .with_opacity(0.0);
433 let sig_lbl = dim("Live Signal demo", 900.0, 150.0);
434
435 for n in [&s7_h, &s7_sub, &s7_arc, &s7_mutex, &s7_why, &sig_lbl] {
436 project.scene.add(n);
437 }
438 project.scene.add(&s7_code);
439 project.scene.add(&s7_diagram_code);
440 project.scene.add(&sig_demo);
441
442 let s8_h = title("SignalTween — The Animation Engine", 50.0);
446 let s8_sub = body(
447 ".to() creates a SignalTween that interpolates over time:",
448 90.0,
449 );
450
451 let s8_code = code_block(
452 "pub struct SignalTween<T> {
453 data: Arc<Mutex<SignalData<T>>>, // shared ref to signal
454 start_value: Option<T>, // captured on FIRST update (lazy!)
455 target_value: Option<T>, // where we're going
456 duration: Duration, // how long
457 elapsed: Duration, // how much time passed
458 easing: fn(f32) -> f32, // curve function
459}",
460 125.0,
461 );
462
463 let s8_how = h2("Each frame update:", 340.0);
464 let s8_steps = [
465 "1. elapsed += dt",
466 "2. t_linear = elapsed / duration (0.0 to 1.0)",
467 "3. t_eased = easing(t_linear) (curved)",
468 "4. value = lerp(start, target, t) (interpolate)",
469 "5. Write value into Signal (node sees it)",
470 "6. If elapsed >= duration: finished! (return leftover dt)",
471 ];
472 let s8_step_texts: Vec<TextNode> = s8_steps
473 .iter()
474 .enumerate()
475 .map(|(i, s)| body(s, 370.0 + i as f32 * 28.0))
476 .collect();
477
478 let s8_lazy = note(
479 "start_value is captured lazily — so chained tweens read the",
480 570.0,
481 );
482 let s8_lazy2 = note(
483 "correct value at their actual start time, not creation time.",
484 590.0,
485 );
486
487 let prog_bg = Rect::default()
489 .with_position(Vec2::new(895.0, 150.0))
490 .with_size(Vec2::new(400.0, 16.0))
491 .with_fill(Color::rgba8(255, 255, 255, 15))
492 .with_radius(8.0)
493 .with_opacity(0.0);
494 let prog_fill = Rect::default()
495 .with_position(Vec2::new(695.0, 154.0))
496 .with_anchor(Vec2::new(-1.0, 0.5))
497 .with_size(Vec2::new(0.0, 16.0))
498 .with_fill(ACCENT)
499 .with_radius(8.0)
500 .with_opacity(0.0);
501 let plbl0 = dim("t=0", 700.0, 172.0);
502 let plbl1 = dim("t=1", 1070.0, 172.0);
503 let tween_ball = Circle::default()
504 .with_position(Vec2::new(900.0, 430.0))
505 .with_radius(30.0)
506 .with_fill(RED)
507 .with_opacity(0.0);
508 let tween_lbl = dim("radius animating: 30 -> 80", 760.0, 220.0);
509
510 for n in [
511 &s8_h, &s8_sub, &s8_how, &s8_lazy, &s8_lazy2, &plbl0, &plbl1, &tween_lbl,
512 ] {
513 project.scene.add(n);
514 }
515 project.scene.add(&s8_code);
516 for t in &s8_step_texts {
517 project.scene.add(t);
518 }
519 project.scene.add(&prog_bg);
520 project.scene.add(&prog_fill);
521 project.scene.add(&tween_ball);
522
523 let s9_h = title("Tweenable — What Can Be Animated", 50.0);
527 let s9_code = code_block(
528 "pub trait Tweenable: Clone + Send + Sync {
529 fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
530 fn state_hash(&self) -> u64;
531}
532// Implemented for: f32, Vec2, Color, String, Affine, Vec<Vec2>
533// f32: lerp(a, b, t) = a + (b-a)*t
534// Vec2: lerp x and y independently
535// Color: lerp R,G,B,A channels independently
536// String: snap — returns 'a' until t>=1, then 'b'",
537 95.0,
538 );
539
540 let s9_easing_h = h2("Easing functions curve the linear t:", 310.0);
541 let s9_easing_desc = body("Same distance, same duration — different feel.", 340.0);
542
543 let enames = [
544 "linear",
545 "cubic_in_out",
546 "elastic_out",
547 "bounce_out",
548 "back_out",
549 ];
550 let ecolors = [WHITE, ACCENT, RED, YELLOW, GREEN];
551 let mut eballs: Vec<Circle> = Vec::new();
552 let mut elabels: Vec<TextNode> = Vec::new();
553 for (i, name) in enames.iter().enumerate() {
554 let y = 390.0 + i as f32 * 55.0;
555 let b = Circle::default()
556 .with_position(Vec2::new(250.0, y))
557 .with_radius(12.0)
558 .with_fill(ecolors[i])
559 .with_opacity(0.0);
560 let l = dim(name, LEFT, y - 5.0);
561 project.scene.add(&b);
562 project.scene.add(&l);
563 eballs.push(b);
564 elabels.push(l);
565 }
566 project.scene.add(&s9_h);
567 project.scene.add(&s9_code);
568 project.scene.add(&s9_easing_h);
569 project.scene.add(&s9_easing_desc);
570
571 let s10_h = title("Flow Controls — Composing Animations", 50.0);
575 let s10_sub = body(
576 "Individual tweens are simple. Power comes from composing them.",
577 90.0,
578 );
579
580 let s10_chain_h = h2("chain![ ] — one after another", 140.0);
582 let chain_d: Vec<Circle> = (0..3)
583 .map(|i| {
584 Circle::default()
585 .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 200.0))
586 .with_radius(18.0)
587 .with_fill([RED, ACCENT, YELLOW][i])
588 .with_opacity(0.0)
589 })
590 .collect();
591
592 let s10_all_h = h2("all![ ] — all at the same time", 270.0);
594 let all_d: Vec<Circle> = (0..3)
595 .map(|i| {
596 Circle::default()
597 .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 330.0))
598 .with_radius(18.0)
599 .with_fill([RED, ACCENT, YELLOW][i])
600 .with_opacity(0.0)
601 })
602 .collect();
603
604 let s10_seq_h = h2("sequence![ ] — staggered starts", 400.0);
606 let seq_d: Vec<Circle> = (0..3)
607 .map(|i| {
608 Circle::default()
609 .with_position(Vec2::new(LEFT + 30.0 + i as f32 * 60.0, 460.0))
610 .with_radius(18.0)
611 .with_fill([RED, ACCENT, YELLOW][i])
612 .with_opacity(0.0)
613 })
614 .collect();
615
616 let s10_code = code_block(
617 "chain![ a, b, c ] // a then b then c
618all![ a, b, c ] // a + b + c together
619sequence![ 200ms, a, b, c ] // staggered
620delay![ 500ms, a ] // wait then play
621wait(1s) // pause
622any![ a, b ] // race: first wins
623loop_anim![ a, 3 ] // repeat 3 times",
624 510.0,
625 );
626
627 project.scene.add(&s10_h);
628 project.scene.add(&s10_sub);
629 project.scene.add(&s10_chain_h);
630 project.scene.add(&s10_all_h);
631 project.scene.add(&s10_seq_h);
632 project.scene.add(&s10_code);
633 for d in &chain_d {
634 project.scene.add(d);
635 }
636 for d in &all_d {
637 project.scene.add(d);
638 }
639 for d in &seq_d {
640 project.scene.add(d);
641 }
642
643 let s11_h = title("The Timeline — Animation Queue", 50.0);
647 let s11_code = code_block(
648 "pub struct Timeline {
649 pub animations: Vec<Box<dyn Animation>>,
650}
651impl Timeline {
652 fn update(&mut self, mut dt: Duration) {
653 while !self.animations.is_empty() {
654 let (finished, leftover) = self.animations[0].update(dt);
655 if finished {
656 self.animations.remove(0); // pop front
657 dt = leftover; // pass leftover to next!
658 } else { break; }
659 }
660 }
661}",
662 95.0,
663 );
664
665 let s11_leftover = note(
666 "leftover propagation: if A finishes mid-frame, the remaining",
667 390.0,
668 );
669 let s11_leftover2 = note(
670 "dt is immediately given to B. No 'lost frames' at transitions.",
671 415.0,
672 );
673
674 let s11_render_h = h2("Rendering Pipeline (per frame):", 470.0);
675 let s11_steps = [
676 "1. Timeline.update(dt) => SignalTween writes to Signals",
677 "2. Node.render() => reads signals, draws shapes",
678 "3. Vello GPU => compiles scene => wgpu => pixels",
679 "4. state_hash() => seahash for hashing scene state, skip if unchanged",
680 ];
681 let s11_render_texts: Vec<TextNode> = s11_steps
682 .iter()
683 .enumerate()
684 .map(|(i, s)| body(s, 505.0 + i as f32 * 28.0))
685 .collect();
686
687 project.scene.add(&s11_h);
688 project.scene.add(&s11_code);
689 for n in [&s11_leftover, &s11_leftover2, &s11_render_h] {
690 project.scene.add(n);
691 }
692 for t in &s11_render_texts {
693 project.scene.add(t);
694 }
695
696 let s12_h = title("Why an Infinite Loop? — The Event Loop", 50.0);
700 let s12_sub = body(
701 "GPU rendering requires a persistent event loop (winit + wgpu).",
702 90.0,
703 );
704 let s12_code = code_block(
705 "event_loop.run(|event, elwt| {
706 match event {
707 Resumed => { // GPU surface ready
708 renderer.resume(&window);
709 }
710 AboutToWait => { // run every frame
711 scene.update(dt); // advance animations
712 let hash = scene.state_hash();
713 if hash != last_hash { // dirty?
714 window.request_redraw();
715 }
716 }
717 RedrawRequested => { // GPU draw call
718 renderer.render(&scene, w, h);
719 }
720 }
721});",
722 130.0,
723 );
724 let s12_why = note(
725 "The window stays open because the GPU surface is tied to",
726 490.0,
727 );
728 let s12_why2 = note(
729 "the OS event loop. Without it, the surface is immediately dropped.",
730 510.0,
731 );
732 let s12_hash = body(
733 "state_hash() skips re-rendering unchanged frames (dirty-checking).",
734 550.0,
735 );
736
737 for n in [&s12_h, &s12_sub, &s12_why, &s12_why2, &s12_hash] {
738 project.scene.add(n);
739 }
740 project.scene.add(&s12_code);
741
742 let s13_h = title("Headless Export: GPU -> PNG -> FFmpeg", 50.0);
746 let s13_sub = body(
747 "Same GPU rendering, but without a window — output to files.",
748 90.0,
749 );
750 let s13_code = code_block(
751 "pub struct Exporter {
752 texture: wgpu::Texture, // GPU-side image
753 output_buffer: wgpu::Buffer, // CPU-readable copy
754 renderer: Renderer, // Vello
755}
756fn export_frame(&mut self, scene) -> Vec<u8> {
757 scene.render(&mut self.scene); // 1. build shapes
758 renderer.render_to_texture(..); // 2. GPU draws
759 encoder.copy_texture_to_buffer(..); // 3. GPU -> CPU
760 output_buffer.map_async(Read, ..); // 4. read pixels
761 return pixels; // 5. raw RGBA
762}",
763 130.0,
764 );
765 let s13_cache = note(
766 "Cache: state_hash per frame. If unchanged, skip GPU entirely.",
767 420.0,
768 );
769 let s13_ffmpeg = note(
770 "FFmpeg: raw pixels piped to stdin -> libx264 -> .mkv video.",
771 445.0,
772 );
773 let s13_parallel = body(
774 "PNG saving runs on a background thread. Export is pipelined.",
775 485.0,
776 );
777
778 for n in [&s13_h, &s13_sub, &s13_cache, &s13_ffmpeg, &s13_parallel] {
779 project.scene.add(n);
780 }
781 project.scene.add(&s13_code);
782
783 let s14_h = title("Under the Hood: Utility Modules", 50.0);
787 let s14_sub = body(
788 "Helper systems that power the engine behind the scenes.",
789 90.0,
790 );
791 let s14_code = code_block(
792 "// src/engine/util/
793font_manager.rs // Lazy-loads system fonts via Typst
794 // Global HashMap cache with lazy_static
795
796image_manager.rs // Loads PNG + SVG (via resvg)
797 // Caches decoded images as Arc<Image>
798
799code_tokenizer.rs // Syntax highlighting via Syntect
800 // Parses code -> colored spans for CodeNode
801
802hash.rs // SeaHash: fast, deterministic fingerprints
803 // Position-aware combination
804 // Powers Rayon parallel state hashing
805
806export.rs // FFmpeg pipe: rawvideo -> libx264
807 // Audio merging with filter_complex
808 // Title sanitization for filenames",
809 130.0,
810 );
811 let s14_lazy = note(
812 "lazy_static + Mutex = global singleton, created once, cached forever.",
813 495.0,
814 );
815 let s14_arc = body(
816 "Arc<Image> lets multiple nodes share one decoded image without copies.",
817 530.0,
818 );
819 let s14_hash = note(
820 "Rayon + SeaHash = Deterministic fingerprints across runs & threads.",
821 565.0,
822 );
823
824 for n in [&s14_h, &s14_sub, &s14_lazy, &s14_arc, &s14_hash] {
825 project.scene.add(n);
826 }
827 project.scene.add(&s14_code);
828
829 let fin = TextNode::default()
833 .with_anchor(Vec2::new(-1.0, -1.0))
834 .with_position(Vec2::new(LEFT, 200.0))
835 .with_text("That's how it works!")
836 .with_font_size(48.0)
837 .with_fill(ACCENT)
838 .with_font(FONT)
839 .with_opacity(0.0);
840 let fin_steps = [
841 "1. struct — data container",
842 "2. impl — methods / builder pattern",
843 "3. trait Node — interface contract",
844 "4. Box<dyn Node> — type-erased heap allocation",
845 "5. Signal<T> — Arc<Mutex> shared reactive state",
846 "6. SignalTween — per-frame lerp interpolation",
847 "7. Timeline — sequential queue + leftover dt",
848 "8. Event Loop — winit + wgpu infinite loop",
849 "9. Exporter — headless GPU -> PNG/FFmpeg",
850 "10. Utilities — font/image cache, syntax highlight",
851 ];
852 let fin_texts: Vec<TextNode> = fin_steps
853 .iter()
854 .enumerate()
855 .map(|(i, s)| body(s, 270.0 + i as f32 * 28.0))
856 .collect();
857 let fin_hint = dim("cargo run --example getting_started", LEFT, 570.0);
858
859 project.scene.add(&fin);
860 for t in &fin_texts {
861 project.scene.add(t);
862 }
863 project.scene.add(&fin_hint);
864
865 let hide_dur = ms(200);
870
871 project.scene.video_timeline.add(chain![
872 s1_line
874 .end
875 .to(Vec2::new(500.0, 100.0), ms(500))
876 .ease(easings::cubic_out),
877 sequence![
878 ms(120),
879 show(&s1_title.opacity, ms(500)),
880 show(&s1_sub.opacity, ms(500)),
881 show(&s1_built.opacity, ms(500)),
882 show(&s1_logo.opacity, ms(600)),
883 show(&s1_desc.opacity, ms(500)),
884 show(&s1_desc2.opacity, ms(500)),
885 ],
886 wait(secs(5)),
887 all![
888 hide(&s1_title.opacity, hide_dur),
889 hide(&s1_sub.opacity, hide_dur),
890 hide(&s1_built.opacity, hide_dur),
891 hide(&s1_desc.opacity, hide_dur),
892 hide(&s1_desc2.opacity, hide_dur),
893 hide(&s1_logo.opacity, hide_dur),
894 s1_line.end.to(Vec2::new(LEFT, 100.0), hide_dur)
895 ],
896 wait(ms(150)),
897 show(&s2_h.opacity, ms(500)),
899 wait(ms(400)),
900 sequence![
901 ms(250),
902 show(&s2_texts[0].opacity, ms(400)),
903 show(&s2_texts[1].opacity, ms(400)),
904 show(&s2_texts[2].opacity, ms(400)),
905 show(&s2_texts[3].opacity, ms(400)),
906 show(&s2_texts[4].opacity, ms(400)),
907 ],
908 wait(secs(8)),
909 all![
910 hide(&s2_h.opacity, hide_dur),
911 hide(&s2_texts[0].opacity, hide_dur),
912 hide(&s2_texts[1].opacity, hide_dur),
913 hide(&s2_texts[2].opacity, hide_dur),
914 hide(&s2_texts[3].opacity, hide_dur),
915 hide(&s2_texts[4].opacity, hide_dur)
916 ],
917 wait(ms(150)),
918 sequence![
920 ms(120),
921 show(&s3_h.opacity, ms(500)),
922 show(&s3_explain.opacity, ms(400)),
923 show(&s3_analogy.opacity, ms(400))
924 ],
925 wait(ms(500)),
926 show(&s3_code.opacity, ms(500)),
927 wait(secs(6)),
928 sequence![
929 ms(300),
930 show(&s3_note.opacity, ms(400)),
931 show(&s3_note2.opacity, ms(400)),
932 show(&s3_note3.opacity, ms(400))
933 ],
934 wait(secs(6)),
935 all![
936 hide(&s3_h.opacity, hide_dur),
937 hide(&s3_explain.opacity, hide_dur),
938 hide(&s3_analogy.opacity, hide_dur),
939 hide(&s3_code.opacity, hide_dur),
940 hide(&s3_note.opacity, hide_dur),
941 hide(&s3_note2.opacity, hide_dur),
942 hide(&s3_note3.opacity, hide_dur)
943 ],
944 wait(ms(150)),
945 sequence![
947 ms(120),
948 show(&s4_h.opacity, ms(500)),
949 show(&s4_explain.opacity, ms(400)),
950 show(&s4_analogy.opacity, ms(400))
951 ],
952 wait(ms(500)),
953 show(&s4_code.opacity, ms(500)),
954 wait(secs(7)),
955 show(&s4_usage.opacity, ms(400)),
956 show(&s4_usage_code.opacity, ms(500)),
957 wait(secs(2)),
958 show(&s4_note.opacity, ms(400)),
959 wait(secs(5)),
960 all![
961 hide(&s4_h.opacity, hide_dur),
962 hide(&s4_explain.opacity, hide_dur),
963 hide(&s4_analogy.opacity, hide_dur),
964 hide(&s4_code.opacity, hide_dur),
965 hide(&s4_usage.opacity, hide_dur),
966 hide(&s4_usage_code.opacity, hide_dur),
967 hide(&s4_note.opacity, hide_dur)
968 ],
969 wait(ms(150)),
970 sequence![
972 ms(120),
973 show(&s5_h.opacity, ms(500)),
974 show(&s5_explain.opacity, ms(400)),
975 show(&s5_analogy.opacity, ms(400))
976 ],
977 wait(ms(500)),
978 show(&s5_code.opacity, ms(500)),
979 wait(secs(6)),
980 sequence![
981 ms(300),
982 show(&s5_r.opacity, ms(400)),
983 show(&s5_u.opacity, ms(400)),
984 show(&s5_s.opacity, ms(400)),
985 show(&s5_c.opacity, ms(400))
986 ],
987 wait(secs(2)),
988 show(&s5_every.opacity, ms(400)),
989 wait(secs(4)),
990 all![
991 hide(&s5_h.opacity, hide_dur),
992 hide(&s5_explain.opacity, hide_dur),
993 hide(&s5_analogy.opacity, hide_dur),
994 hide(&s5_code.opacity, hide_dur),
995 hide(&s5_r.opacity, hide_dur),
996 hide(&s5_u.opacity, hide_dur),
997 hide(&s5_s.opacity, hide_dur),
998 hide(&s5_c.opacity, hide_dur),
999 hide(&s5_every.opacity, hide_dur)
1000 ],
1001 wait(ms(150)),
1002 sequence![
1004 ms(120),
1005 show(&s6_h.opacity, ms(500)),
1006 show(&s6_sub.opacity, ms(400))
1007 ],
1008 wait(ms(400)),
1009 sequence![
1010 ms(200),
1011 all![show(&demo_c.opacity, ms(400)), show(&lc.opacity, ms(400))],
1012 all![show(&demo_r.opacity, ms(400)), show(&lr.opacity, ms(400))],
1013 all![show(&demo_l.opacity, ms(400)), show(&ll.opacity, ms(400))],
1014 all![show(&demo_p.opacity, ms(400)), show(&lp.opacity, ms(400))],
1015 all![show(&demo_t.opacity, ms(400)), show(<.opacity, ms(400))],
1016 ],
1017 wait(secs(2)),
1018 sequence![
1019 ms(200),
1020 show(&s6_box_h.opacity, ms(400)),
1021 show(&s6_box1.opacity, ms(400)),
1022 show(&s6_box_code.opacity, ms(500))
1023 ],
1024 wait(secs(7)),
1025 all![
1026 hide(&s6_h.opacity, hide_dur),
1027 hide(&s6_sub.opacity, hide_dur),
1028 hide(&demo_c.opacity, hide_dur),
1029 hide(&demo_r.opacity, hide_dur),
1030 hide(&demo_l.opacity, hide_dur),
1031 hide(&demo_p.opacity, hide_dur),
1032 hide(&demo_t.opacity, hide_dur),
1033 hide(&lc.opacity, hide_dur),
1034 hide(&lr.opacity, hide_dur),
1035 hide(&ll.opacity, hide_dur),
1036 hide(&lp.opacity, hide_dur),
1037 hide(<.opacity, hide_dur),
1038 hide(&s6_box_h.opacity, hide_dur),
1039 hide(&s6_box1.opacity, hide_dur),
1040 hide(&s6_box_code.opacity, hide_dur)
1041 ],
1042 wait(ms(150)),
1043 sequence![
1045 ms(120),
1046 show(&s7_h.opacity, ms(500)),
1047 show(&s7_sub.opacity, ms(400))
1048 ],
1049 wait(ms(400)),
1050 show(&s7_code.opacity, ms(500)),
1051 wait(secs(5)),
1052 sequence![
1053 ms(300),
1054 show(&s7_arc.opacity, ms(400)),
1055 show(&s7_mutex.opacity, ms(400))
1056 ],
1057 wait(secs(4)),
1058 show(&s7_why.opacity, ms(400)),
1059 show(&s7_diagram_code.opacity, ms(500)),
1060 wait(secs(6)),
1061 all![
1063 show(&sig_demo.opacity, ms(300)),
1064 show(&sig_lbl.opacity, ms(300))
1065 ],
1066 chain![
1067 sig_demo.radius.to(80.0, ms(700)).ease(easings::elastic_out),
1068 sig_demo.fill_paint.to(Paint::Solid(TEAL), ms(500)),
1069 sig_demo
1070 .position
1071 .to(Vec2::new(950.0, 350.0), ms(500))
1072 .ease(easings::cubic_out),
1073 wait(ms(300)),
1074 all![
1075 sig_demo.radius.to(50.0, ms(400)),
1076 sig_demo.fill_paint.to(Paint::Solid(RED), ms(400)),
1077 sig_demo.position.to(Vec2::new(900.0, 300.0), ms(400))
1078 ],
1079 ],
1080 wait(secs(3)),
1081 all![
1082 hide(&s7_h.opacity, hide_dur),
1083 hide(&s7_sub.opacity, hide_dur),
1084 hide(&s7_code.opacity, hide_dur),
1085 hide(&s7_arc.opacity, hide_dur),
1086 hide(&s7_mutex.opacity, hide_dur),
1087 hide(&s7_why.opacity, hide_dur),
1088 hide(&s7_diagram_code.opacity, hide_dur),
1089 hide(&sig_demo.opacity, hide_dur),
1090 hide(&sig_lbl.opacity, hide_dur)
1091 ],
1092 wait(ms(150)),
1093 sequence![
1095 ms(120),
1096 show(&s8_h.opacity, ms(500)),
1097 show(&s8_sub.opacity, ms(400))
1098 ],
1099 wait(ms(400)),
1100 show(&s8_code.opacity, ms(500)),
1101 wait(secs(6)),
1102 show(&s8_how.opacity, ms(300)),
1103 sequence![
1104 ms(100),
1105 show(&s8_step_texts[0].opacity, ms(250)),
1106 show(&s8_step_texts[1].opacity, ms(250)),
1107 show(&s8_step_texts[2].opacity, ms(250)),
1108 show(&s8_step_texts[3].opacity, ms(250)),
1109 show(&s8_step_texts[4].opacity, ms(250)),
1110 show(&s8_step_texts[5].opacity, ms(250)),
1111 ],
1112 wait(ms(500)),
1113 sequence![
1114 ms(100),
1115 show(&s8_lazy.opacity, ms(300)),
1116 show(&s8_lazy2.opacity, ms(300))
1117 ],
1118 wait(ms(500)),
1119 all![
1121 show(&prog_bg.opacity, ms(200)),
1122 show(&prog_fill.opacity, ms(200)),
1123 show(&plbl0.opacity, ms(200)),
1124 show(&plbl1.opacity, ms(200)),
1125 show(&tween_ball.opacity, ms(200)),
1126 show(&tween_lbl.opacity, ms(200))
1127 ],
1128 all![
1129 prog_fill
1130 .size
1131 .to(Vec2::new(400.0, 16.0), secs(2))
1132 .ease(easings::cubic_in_out),
1133 tween_ball
1134 .radius
1135 .to(80.0, secs(2))
1136 .ease(easings::cubic_in_out),
1137 ],
1138 wait(secs(3)),
1139 all![
1140 hide(&s8_h.opacity, hide_dur),
1141 hide(&s8_sub.opacity, hide_dur),
1142 hide(&s8_code.opacity, hide_dur),
1143 hide(&s8_how.opacity, hide_dur),
1144 hide(&s8_lazy.opacity, hide_dur),
1145 hide(&s8_lazy2.opacity, hide_dur),
1146 hide(&prog_bg.opacity, hide_dur),
1147 hide(&prog_fill.opacity, hide_dur),
1148 hide(&plbl0.opacity, hide_dur),
1149 hide(&plbl1.opacity, hide_dur),
1150 hide(&tween_ball.opacity, hide_dur),
1151 hide(&tween_lbl.opacity, hide_dur),
1152 hide(&s8_step_texts[0].opacity, hide_dur),
1153 hide(&s8_step_texts[1].opacity, hide_dur),
1154 hide(&s8_step_texts[2].opacity, hide_dur),
1155 hide(&s8_step_texts[3].opacity, hide_dur),
1156 hide(&s8_step_texts[4].opacity, hide_dur),
1157 hide(&s8_step_texts[5].opacity, hide_dur)
1158 ],
1159 wait(ms(150)),
1160 show(&s9_h.opacity, ms(500)),
1162 show(&s9_code.opacity, ms(500)),
1163 wait(secs(4)),
1164 sequence![
1165 ms(60),
1166 show(&s9_easing_h.opacity, ms(300)),
1167 show(&s9_easing_desc.opacity, ms(300))
1168 ],
1169 sequence![
1170 ms(50),
1171 all![
1172 show(&eballs[0].opacity, ms(200)),
1173 show(&elabels[0].opacity, ms(200))
1174 ],
1175 all![
1176 show(&eballs[1].opacity, ms(200)),
1177 show(&elabels[1].opacity, ms(200))
1178 ],
1179 all![
1180 show(&eballs[2].opacity, ms(200)),
1181 show(&elabels[2].opacity, ms(200))
1182 ],
1183 all![
1184 show(&eballs[3].opacity, ms(200)),
1185 show(&elabels[3].opacity, ms(200))
1186 ],
1187 all![
1188 show(&eballs[4].opacity, ms(200)),
1189 show(&elabels[4].opacity, ms(200))
1190 ],
1191 ],
1192 wait(ms(300)),
1193 all![
1195 eballs[0]
1196 .position
1197 .to(Vec2::new(1050.0, 390.0), secs(2))
1198 .ease(easings::linear),
1199 eballs[1]
1200 .position
1201 .to(Vec2::new(1050.0, 445.0), secs(2))
1202 .ease(easings::cubic_in_out),
1203 eballs[2]
1204 .position
1205 .to(Vec2::new(1050.0, 500.0), secs(2))
1206 .ease(easings::elastic_out),
1207 eballs[3]
1208 .position
1209 .to(Vec2::new(1050.0, 555.0), secs(2))
1210 .ease(easings::bounce_out),
1211 eballs[4]
1212 .position
1213 .to(Vec2::new(1050.0, 610.0), secs(2))
1214 .ease(easings::back_out),
1215 ],
1216 wait(ms(500)),
1217 all![
1218 eballs[0]
1219 .position
1220 .to(Vec2::new(250.0, 390.0), secs(2))
1221 .ease(easings::linear),
1222 eballs[1]
1223 .position
1224 .to(Vec2::new(250.0, 445.0), secs(2))
1225 .ease(easings::cubic_in_out),
1226 eballs[2]
1227 .position
1228 .to(Vec2::new(250.0, 500.0), secs(2))
1229 .ease(easings::elastic_out),
1230 eballs[3]
1231 .position
1232 .to(Vec2::new(250.0, 555.0), secs(2))
1233 .ease(easings::bounce_out),
1234 eballs[4]
1235 .position
1236 .to(Vec2::new(250.0, 610.0), secs(2))
1237 .ease(easings::back_out),
1238 ],
1239 wait(ms(500)),
1240 all![
1241 hide(&s9_h.opacity, hide_dur),
1242 hide(&s9_code.opacity, hide_dur),
1243 hide(&s9_easing_h.opacity, hide_dur),
1244 hide(&s9_easing_desc.opacity, hide_dur),
1245 hide(&eballs[0].opacity, hide_dur),
1246 hide(&eballs[1].opacity, hide_dur),
1247 hide(&eballs[2].opacity, hide_dur),
1248 hide(&eballs[3].opacity, hide_dur),
1249 hide(&eballs[4].opacity, hide_dur),
1250 hide(&elabels[0].opacity, hide_dur),
1251 hide(&elabels[1].opacity, hide_dur),
1252 hide(&elabels[2].opacity, hide_dur),
1253 hide(&elabels[3].opacity, hide_dur),
1254 hide(&elabels[4].opacity, hide_dur)
1255 ],
1256 wait(ms(150)),
1257 sequence![
1259 ms(120),
1260 show(&s10_h.opacity, ms(500)),
1261 show(&s10_sub.opacity, ms(400))
1262 ],
1263 wait(ms(400)),
1264 show(&s10_chain_h.opacity, ms(400)),
1266 all![
1267 show(&chain_d[0].opacity, ms(300)),
1268 show(&chain_d[1].opacity, ms(300)),
1269 show(&chain_d[2].opacity, ms(300))
1270 ],
1271 wait(ms(300)),
1272 chain![
1273 chain_d[0]
1274 .position
1275 .to(Vec2::new(700.0, 200.0), ms(500))
1276 .ease(easings::cubic_out),
1277 chain_d[1]
1278 .position
1279 .to(Vec2::new(800.0, 200.0), ms(500))
1280 .ease(easings::cubic_out),
1281 chain_d[2]
1282 .position
1283 .to(Vec2::new(900.0, 200.0), ms(500))
1284 .ease(easings::cubic_out),
1285 ],
1286 wait(secs(1)),
1287 show(&s10_all_h.opacity, ms(400)),
1289 all![
1290 show(&all_d[0].opacity, ms(300)),
1291 show(&all_d[1].opacity, ms(300)),
1292 show(&all_d[2].opacity, ms(300))
1293 ],
1294 wait(ms(300)),
1295 all![
1296 all_d[0]
1297 .position
1298 .to(Vec2::new(700.0, 330.0), ms(500))
1299 .ease(easings::cubic_out),
1300 all_d[1]
1301 .position
1302 .to(Vec2::new(800.0, 330.0), ms(500))
1303 .ease(easings::cubic_out),
1304 all_d[2]
1305 .position
1306 .to(Vec2::new(900.0, 330.0), ms(500))
1307 .ease(easings::cubic_out),
1308 ],
1309 wait(secs(1)),
1310 show(&s10_seq_h.opacity, ms(400)),
1312 all![
1313 show(&seq_d[0].opacity, ms(300)),
1314 show(&seq_d[1].opacity, ms(300)),
1315 show(&seq_d[2].opacity, ms(300))
1316 ],
1317 wait(ms(300)),
1318 sequence![
1319 ms(250),
1320 seq_d[0]
1321 .position
1322 .to(Vec2::new(700.0, 460.0), ms(500))
1323 .ease(easings::cubic_out),
1324 seq_d[1]
1325 .position
1326 .to(Vec2::new(800.0, 460.0), ms(500))
1327 .ease(easings::cubic_out),
1328 seq_d[2]
1329 .position
1330 .to(Vec2::new(900.0, 460.0), ms(500))
1331 .ease(easings::cubic_out),
1332 ],
1333 wait(secs(1)),
1334 show(&s10_code.opacity, ms(500)),
1335 wait(secs(8)),
1336 all![
1337 hide(&s10_h.opacity, hide_dur),
1338 hide(&s10_sub.opacity, hide_dur),
1339 hide(&s10_chain_h.opacity, hide_dur),
1340 hide(&s10_all_h.opacity, hide_dur),
1341 hide(&s10_seq_h.opacity, hide_dur),
1342 hide(&s10_code.opacity, hide_dur),
1343 hide(&chain_d[0].opacity, hide_dur),
1344 hide(&chain_d[1].opacity, hide_dur),
1345 hide(&chain_d[2].opacity, hide_dur),
1346 hide(&all_d[0].opacity, hide_dur),
1347 hide(&all_d[1].opacity, hide_dur),
1348 hide(&all_d[2].opacity, hide_dur),
1349 hide(&seq_d[0].opacity, hide_dur),
1350 hide(&seq_d[1].opacity, hide_dur),
1351 hide(&seq_d[2].opacity, hide_dur)
1352 ],
1353 wait(ms(150)),
1354 show(&s11_h.opacity, ms(500)),
1356 wait(ms(400)),
1357 show(&s11_code.opacity, ms(500)),
1358 wait(secs(7)),
1359 sequence![
1360 ms(200),
1361 show(&s11_leftover.opacity, ms(400)),
1362 show(&s11_leftover2.opacity, ms(400))
1363 ],
1364 wait(secs(4)),
1365 show(&s11_render_h.opacity, ms(400)),
1366 sequence![
1367 ms(200),
1368 show(&s11_render_texts[0].opacity, ms(350)),
1369 show(&s11_render_texts[1].opacity, ms(350)),
1370 show(&s11_render_texts[2].opacity, ms(350)),
1371 show(&s11_render_texts[3].opacity, ms(350)),
1372 ],
1373 wait(secs(7)),
1374 all![
1375 hide(&s11_h.opacity, hide_dur),
1376 hide(&s11_code.opacity, hide_dur),
1377 hide(&s11_leftover.opacity, hide_dur),
1378 hide(&s11_leftover2.opacity, hide_dur),
1379 hide(&s11_render_h.opacity, hide_dur),
1380 hide(&s11_render_texts[0].opacity, hide_dur),
1381 hide(&s11_render_texts[1].opacity, hide_dur),
1382 hide(&s11_render_texts[2].opacity, hide_dur),
1383 hide(&s11_render_texts[3].opacity, hide_dur)
1384 ],
1385 wait(ms(300)),
1386 sequence![
1388 ms(120),
1389 show(&s12_h.opacity, ms(500)),
1390 show(&s12_sub.opacity, ms(400))
1391 ],
1392 wait(ms(500)),
1393 show(&s12_code.opacity, ms(500)),
1394 wait(secs(8)),
1395 sequence![
1396 ms(200),
1397 show(&s12_why.opacity, ms(400)),
1398 show(&s12_why2.opacity, ms(400)),
1399 show(&s12_hash.opacity, ms(400))
1400 ],
1401 wait(secs(5)),
1402 all![
1403 hide(&s12_h.opacity, hide_dur),
1404 hide(&s12_sub.opacity, hide_dur),
1405 hide(&s12_code.opacity, hide_dur),
1406 hide(&s12_why.opacity, hide_dur),
1407 hide(&s12_why2.opacity, hide_dur),
1408 hide(&s12_hash.opacity, hide_dur)
1409 ],
1410 wait(ms(150)),
1411 sequence![
1413 ms(120),
1414 show(&s13_h.opacity, ms(500)),
1415 show(&s13_sub.opacity, ms(400))
1416 ],
1417 wait(ms(500)),
1418 show(&s13_code.opacity, ms(500)),
1419 wait(secs(8)),
1420 sequence![
1421 ms(200),
1422 show(&s13_cache.opacity, ms(400)),
1423 show(&s13_ffmpeg.opacity, ms(400)),
1424 show(&s13_parallel.opacity, ms(400))
1425 ],
1426 wait(secs(5)),
1427 all![
1428 hide(&s13_h.opacity, hide_dur),
1429 hide(&s13_sub.opacity, hide_dur),
1430 hide(&s13_code.opacity, hide_dur),
1431 hide(&s13_cache.opacity, hide_dur),
1432 hide(&s13_ffmpeg.opacity, hide_dur),
1433 hide(&s13_parallel.opacity, hide_dur)
1434 ],
1435 wait(ms(150)),
1436 sequence![
1438 ms(120),
1439 show(&s14_h.opacity, ms(500)),
1440 show(&s14_sub.opacity, ms(400))
1441 ],
1442 wait(ms(500)),
1443 show(&s14_code.opacity, ms(500)),
1444 wait(secs(8)),
1445 sequence![
1446 ms(200),
1447 show(&s14_lazy.opacity, ms(400)),
1448 show(&s14_arc.opacity, ms(400))
1449 ],
1450 wait(secs(5)),
1451 all![
1452 hide(&s14_h.opacity, hide_dur),
1453 hide(&s14_sub.opacity, hide_dur),
1454 hide(&s14_code.opacity, hide_dur),
1455 hide(&s14_lazy.opacity, hide_dur),
1456 hide(&s14_arc.opacity, hide_dur)
1457 ],
1458 wait(ms(300)),
1459 show(&fin.opacity, ms(700)),
1461 wait(ms(500)),
1462 sequence![
1463 ms(150),
1464 show(&fin_texts[0].opacity, ms(350)),
1465 show(&fin_texts[1].opacity, ms(350)),
1466 show(&fin_texts[2].opacity, ms(350)),
1467 show(&fin_texts[3].opacity, ms(350)),
1468 show(&fin_texts[4].opacity, ms(350)),
1469 show(&fin_texts[5].opacity, ms(350)),
1470 show(&fin_texts[6].opacity, ms(350)),
1471 show(&fin_texts[7].opacity, ms(350)),
1472 show(&fin_texts[8].opacity, ms(350)),
1473 show(&fin_texts[9].opacity, ms(350)),
1474 ],
1475 wait(secs(2)),
1476 show(&fin_hint.opacity, ms(400)),
1477 wait(secs(6)),
1478 ]);
1479
1480 #[cfg(feature = "audio")]
1481 project
1482 .scene
1483 .audio_timeline
1484 .add(play!(AudioNode::new("background.mp3").with_volume(0.3)));
1485
1486 project.show().expect("Failed to render");
1487}