Skip to main content

mask_demo/
mask_demo.rs

1use motion_canvas_rs::prelude::*;
2use std::time::Duration;
3
4const SLIDE: Duration = Duration::from_millis(2500);
5const BEAT: Duration = Duration::from_millis(800);
6
7/// Reusable source content: gradient rect, ring pair, label
8fn make_source(w: f32, h: f32, font: f32) -> GroupNode {
9    GroupNode::new(vec![
10        Box::new(
11            Rect::default()
12                .with_size(Vec2::new(w, h))
13                .with_radius(w * 0.04)
14                .with_fill(linear_gradient!(
15                    Color::rgb8(0xf4, 0x3f, 0x5e),
16                    Color::rgb8(0x8b, 0x5c, 0xf6),
17                    Color::rgb8(0x06, 0xb6, 0xd4)
18                )),
19        ),
20        Box::new(
21            Circle::default()
22                .with_position(Vec2::new(-w * 0.3, 0.0))
23                .with_radius(w * 0.12)
24                .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 0x88), 3.0),
25        ),
26        Box::new(
27            Circle::default()
28                .with_position(Vec2::new(w * 0.3, 0.0))
29                .with_radius(w * 0.12)
30                .with_stroke(Color::rgba8(0xff, 0xff, 0xff, 0x88), 3.0),
31        ),
32        Box::new(
33            TextNode::default()
34                .with_text("MASKED")
35                .with_font_size(font)
36                .with_fill(Color::WHITE),
37        ),
38    ])
39}
40
41/// Small mask panel: spot circle + MaskNode + label, initially hidden
42fn make_panel(
43    project: &mut Project,
44    x: f32,
45    y: f32,
46    mode: MaskMode,
47    name: &str,
48    color: Color,
49) -> (Circle, MaskNode, TextNode) {
50    let spot = Circle::default()
51        .with_position(Vec2::new(-110.0, 0.0))
52        .with_radius(50.0)
53        .with_fill(Color::WHITE);
54    let mask = MaskNode::new(
55        Box::new(spot.clone()),
56        Box::new(make_source(220.0, 130.0, 18.0)),
57    )
58    .with_position(Vec2::new(x, y))
59    .with_mode(mode)
60    .with_opacity(0.0);
61    let lbl = TextNode::default()
62        .with_position(Vec2::new(x, y + 90.0))
63        .with_text(name)
64        .with_font_size(15.0)
65        .with_fill(color)
66        .with_opacity(0.0);
67    project.scene.add(&mask);
68    project.scene.add(&lbl);
69    (spot, mask, lbl)
70}
71
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}