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