From 67b8a5036060de39462911bc6b2e4104cf1627b0 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 21 Aug 2025 11:09:08 +0800 Subject: [PATCH 1/6] chore: update flake.nix --- flake.lock | 12 ++++++------ flake.nix | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index b7313e3f..f519d8d7 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751984180, - "narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=", + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1752201818, - "narHash": "sha256-d8KczaVT8WFEZdWg//tMAbv8EDyn2YTWcJvSY8gqKBU=", + "lastModified": 1755657401, + "narHash": "sha256-rPHuWPAcwW63wH1SUtDCqAnf2+60pi/pGMCIhVobzXc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "bd8f8329780b348fedcd37b53dbbee48c08c496d", + "rev": "292ca754b0f679b842fbfc4734f017c351f0e9eb", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index acf34410..48e1bd75 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ let overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system overlays; }; - rust-tools = pkgs.rust-bin.stable.latest.default.override { + rust-tools = pkgs.rust-bin.nightly.latest.default.override { extensions = [ "rust-src" ]; targets = [ "wasm32-unknown-unknown" ]; }; From 6bf05032b5051ffff18e552cf69d095129e991ee Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Sat, 23 Aug 2025 21:18:35 +0800 Subject: [PATCH 2/6] WIP: move examples to ranim-examples crate --- .gitignore | 2 +- Cargo.lock | 10 + examples/bubble_sort/main.rs | 2 +- examples/getting_started0/main.rs | 49 --- justfile | 10 + packages/ranim-examples/Cargo.toml | 30 ++ packages/ranim-examples/docs-rs/header.html | 10 + packages/ranim-examples/src/lib.rs | 69 +++++ packages/ranim-examples/src/sort.rs | 280 ++++++++++++++++++ .../src/tutorial/getting_started.rs | 147 +++++++++ packages/ranim-macros/src/lib.rs | 8 + src/app/mod.rs | 76 +++-- 12 files changed, 625 insertions(+), 68 deletions(-) delete mode 100644 examples/getting_started0/main.rs create mode 100644 packages/ranim-examples/Cargo.toml create mode 100644 packages/ranim-examples/docs-rs/header.html create mode 100644 packages/ranim-examples/src/lib.rs create mode 100644 packages/ranim-examples/src/sort.rs create mode 100644 packages/ranim-examples/src/tutorial/getting_started.rs diff --git a/.gitignore b/.gitignore index 8a6e6ac5..a39e4ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +target # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Cargo.lock b/Cargo.lock index d1ddccfd..421581de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4061,6 +4061,16 @@ dependencies = [ "toml 0.9.5", ] +[[package]] +name = "ranim-examples" +version = "0.1.3" +dependencies = [ + "rand 0.9.2", + "rand_chacha 0.9.0", + "ranim", + "wasm-bindgen", +] + [[package]] name = "ranim-macros" version = "0.1.3" diff --git a/examples/bubble_sort/main.rs b/examples/bubble_sort/main.rs index 2e8350d7..052a2a6e 100644 --- a/examples/bubble_sort/main.rs +++ b/examples/bubble_sort/main.rs @@ -41,7 +41,7 @@ fn bubble_sort(r: &mut RanimScene, num: usize) { .scale(DVec3::splat(0.8)) .put_anchor_on(Anchor::edge(0, -1, 0), target_bc_coord); }); - r.insert(rect) + r.insert_and_show(rect) }) .collect::>(); diff --git a/examples/getting_started0/main.rs b/examples/getting_started0/main.rs deleted file mode 100644 index f3d7e029..00000000 --- a/examples/getting_started0/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -use log::LevelFilter; -use ranim::{ - animation::fading::FadingAnim, color::palettes::manim, items::vitem::geometry::Square, - prelude::*, -}; - -#[scene] -#[preview] -#[output(dir = "getting_started0")] -fn getting_started0(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - // A Square with size 2.0 and color blue - let square = Square::new(2.0).with(|square| { - square.set_color(manim::BLUE_C); - }); - - let r_square = r.insert(square); - { - let timeline = r.timeline_mut(&r_square); - timeline - .play_with(|square| square.fade_in()) - .forward(1.0) - .hide() - .forward(1.0) - .show() - .forward(1.0) - .play_with(|square| square.fade_out()); - } -} -// ANCHOR_END: construct - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(getting_started0_scene); - #[cfg(not(feature = "app"))] - render_scene(getting_started0_scene); -} diff --git a/justfile b/justfile index 8cbab4c6..b1c1fd90 100644 --- a/justfile +++ b/justfile @@ -30,6 +30,16 @@ doc-nightly: -rm -r website/static/doc/ cp -r target/doc/ website/static/doc/ +doc-examples: + RUSTDOCFLAGS="--cfg docsrs --cfg docrs_dep --html-in-header ./packages/ranim-examples/docs-rs/header.html" \ + RUSTFLAGS="--cfg docsrs_dep" \ + cargo doc --no-deps -p ranim-examples --document-private-items --all-features \ + --target-dir ./packages/ranim-examples/target/ + + cargo build -p ranim-examples --release --target wasm32-unknown-unknown + wasm-bindgen --target web ./target/wasm32-unknown-unknown/release/ranim_examples.wasm \ + --out-dir ./packages/ranim-examples/target/doc/ranim_examples/pkg + doc: cargo doc --no-deps -p ranim --document-private-items --all-features -rm -r website/static/doc/ diff --git a/packages/ranim-examples/Cargo.toml b/packages/ranim-examples/Cargo.toml new file mode 100644 index 00000000..01b62272 --- /dev/null +++ b/packages/ranim-examples/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ranim-examples" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +app = ["ranim/app"] + +[target.'cfg(target_family = "wasm")'.dependencies] +ranim = { workspace = true, features = ["app"] } + +[lib] +crate-type = ["rlib", "cdylib"] # use cdylib to produce .wasm file + +[dependencies] +ranim.workspace = true +rand = "0.9.2" +rand_chacha = "0.9.0" +wasm-bindgen = "0.2.100" + +[package.metadata.release] +tag = false + +[package.metadata.docs.rs] +all-features = true +rustc-args = ["--cfg", "docsrs_dep"] +rustdoc-args = ["--cfg", "docsrs_dep", "--html-in-header", "docs-rs/header.html"] + diff --git a/packages/ranim-examples/docs-rs/header.html b/packages/ranim-examples/docs-rs/header.html new file mode 100644 index 00000000..243aee13 --- /dev/null +++ b/packages/ranim-examples/docs-rs/header.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/packages/ranim-examples/src/lib.rs b/packages/ranim-examples/src/lib.rs new file mode 100644 index 00000000..3b0cd2b1 --- /dev/null +++ b/packages/ranim-examples/src/lib.rs @@ -0,0 +1,69 @@ +//! This crate contains all ranim examples + +use std::f64::consts::PI; + +use ranim::{ + animation::{creation::WritingAnim, fading::FadingAnim, transform::TransformAnim}, + color::palettes::manim, + glam::DVec3, + items::vitem::{ + VItem, + geometry::{Circle, Square}, + }, + prelude::*, +}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +pub mod tutorial { + pub mod getting_started; +} +pub mod sort; + +#[scene] +#[preview] +#[output] +/// Hello Ranim! +/// +/// +/// +pub fn hello_ranim(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + let square = Square::new(2.0).with(|square| { + square.set_color(manim::BLUE_C); + }); + + let r_square = r.insert(square); + { + let timeline = r.timeline_mut(&r_square); + timeline.play_with(|square| square.fade_in()); + }; + + let circle = Circle::new(2.0).with(|circle| { + circle + .set_color(manim::RED_C) + .rotate(-PI / 4.0 + PI, DVec3::Z); + }); + + let r_vitem = r.map(r_square, VItem::from); + { + let timeline = r.timeline_mut(&r_vitem); + timeline.play_with(|state| state.transform_to(circle.into())); + timeline.forward(1.0); + let circle = timeline.state().clone(); + timeline.play_with(|circle| circle.unwrite()); + timeline.play(circle.write()); + timeline.play_with(|circle| circle.fade_out()); + }; +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_hello_ranim() { + run_scene_app( + hello_ranim_scene.constructor, + hello_ranim_scene.name.to_string(), + ); +} diff --git a/packages/ranim-examples/src/sort.rs b/packages/ranim-examples/src/sort.rs new file mode 100644 index 00000000..629ea83c --- /dev/null +++ b/packages/ranim-examples/src/sort.rs @@ -0,0 +1,280 @@ +use rand::{SeedableRng, seq::SliceRandom}; +use ranim::{ + animation::transform::TransformAnim, + color::palettes::manim, + components::Anchor, + glam::{DVec3, dvec2}, + items::vitem::geometry::Rectangle, + prelude::*, + utils::rate_functions::linear, +}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +// MARK: bubble_sort + +pub fn bubble_sort(r: &mut RanimScene, num: usize) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + + let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); + let padded_frame_size = frame_size * 0.9; + + let anim_step_duration = 15.0 / num.pow(2) as f64; + + let width_unit = padded_frame_size.x / num as f64; + let height_unit = padded_frame_size.y / num as f64; + + let mut rng = rand_chacha::ChaChaRng::seed_from_u64(114514); + let mut heights = (1..=num) + .map(|x| x as f64 * height_unit) + .collect::>(); + heights.shuffle(&mut rng); + + let padded_frame_bl = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); + let mut r_rects = heights + .iter() + .enumerate() + .map(|(i, &height)| { + let target_bc_coord = + padded_frame_bl.extend(0.0) + DVec3::X * (width_unit * i as f64 + width_unit / 2.0); + let rect = Rectangle::new(width_unit, height).with(|rect| { + rect.stroke_width = 0.0; + rect.set_fill_color(manim::WHITE.with_alpha(0.5)) + .scale(DVec3::splat(0.8)) + .put_anchor_on(Anchor::edge(0, -1, 0), target_bc_coord); + }); + r.insert_and_show(rect) + }) + .collect::>(); + + let anim_highlight = |rect: Rectangle| { + rect.transform(|data| { + data.set_fill_color(manim::BLUE_C.with_alpha(0.5)); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }; + let anim_unhighlight = |rect: Rectangle| { + rect.transform(|data| { + data.set_fill_color(manim::WHITE.with_alpha(0.5)); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }; + let shift_right = DVec3::X * width_unit; + let swap_shift = [shift_right, -shift_right]; + let anim_swap = |timeline: &mut RanimScene, r_rectab: &[&ItemId; 2]| { + let timelines = timeline.timeline_mut(r_rectab); + timelines + .into_iter() + .zip(swap_shift.iter()) + .for_each(|(timeline, shift)| { + timeline.play_with(|rect| { + rect.transform(|data| { + data.shift(*shift); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }); + }); + }; + + for i in (1..num).rev() { + for j in 0..i { + r.timeline_mut(&[&r_rects[j], &r_rects[j + 1]]) + .into_iter() + .for_each(|timeline| { + timeline.play_with(anim_highlight); + }); + if heights[j] > heights[j + 1] { + anim_swap(r, &[&r_rects[j], &r_rects[j + 1]]); + r.timelines_mut().sync(); + heights.swap(j, j + 1); + r_rects.swap(j, j + 1); + } + r.timeline_mut(&[&r_rects[j], &r_rects[j + 1]]) + .into_iter() + .for_each(|timeline| { + timeline.play_with(anim_unhighlight); + }); + r.timelines_mut().sync(); + } + } +} + +#[scene] +#[preview] +#[output] +/// A bubble sort ranim example with input of 10. +/// +/// +/// +pub fn bubble_sort_10(r: &mut RanimScene) { + bubble_sort(r, 10); +} + +#[scene] +#[preview] +#[output] +/// A bubble sort ranim example with input of 100. +/// +/// +/// +pub fn bubble_sort_100(r: &mut RanimScene) { + bubble_sort(r, 100); +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_bubble_sort_10() { + run_scene_app( + bubble_sort_10_scene.constructor, + bubble_sort_10_scene.name.to_string(), + ); +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_bubble_sort_100() { + run_scene_app( + bubble_sort_100_scene.constructor, + bubble_sort_100_scene.name.to_string(), + ); +} + +// MARK: selective_sort + +pub fn selective_sort(r: &mut RanimScene, num: usize) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + + let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); + let padded_frame_size = frame_size * 0.9; + + let anim_step_duration = 15.0 / num.pow(2) as f64; + + let width_unit = padded_frame_size.x / num as f64; + let height_unit = padded_frame_size.y / num as f64; + + let mut rng = rand_chacha::ChaChaRng::seed_from_u64(114514); + let mut heights = (1..=num) + .map(|x| x as f64 * height_unit) + .collect::>(); + heights.shuffle(&mut rng); + + let padded_frame_bl = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); + let mut r_rects = heights + .iter() + .enumerate() + .map(|(i, &height)| { + let target_bc_coord = + padded_frame_bl.extend(0.0) + DVec3::X * (width_unit * i as f64 + width_unit / 2.0); + let rect = Rectangle::new(width_unit, height).with(|rect| { + rect.fill_rgba = manim::WHITE.with_alpha(0.5); + rect.scale(DVec3::splat(0.8)) + .put_anchor_on(Anchor::edge(0, -1, 0), target_bc_coord); + }); + r.insert_and_show(rect) + }) + .collect::>(); + + let highlight = |rect: Rectangle| { + rect.transform(|data| { + data.set_color(manim::RED_C).set_fill_opacity(0.5); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }; + let unhighlight = |rect: Rectangle| { + rect.transform(|data| { + data.set_color(manim::WHITE).set_fill_opacity(0.5); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }; + + let shift_right = DVec3::X * width_unit; + for i in 0..num - 1 { + r.timeline_mut(&r_rects[i]).play_with(highlight); + for j in i + 1..num { + r.timeline_mut(&r_rects[j]).play_with(highlight); + r.timelines_mut().sync(); + + if heights[i] > heights[j] { + let dir = [shift_right, -shift_right]; + let color = [manim::BLUE_C, manim::RED_C]; + r.timeline_mut(&[&r_rects[i], &r_rects[j]]) + .iter_mut() + .zip(dir) + .zip(color) + .for_each(|((timeline, dir), color)| { + timeline.play_with(|rect| { + rect.transform(|rect| { + rect.shift(dir * (j - i) as f64) + .set_color(color) + .set_fill_opacity(0.5); + }) + .with_duration(anim_step_duration) + .with_rate_func(linear) + }); + }); + heights.swap(i, j); + r_rects.swap(i, j); + } + r.timeline_mut(&r_rects[j]).play_with(unhighlight); + r.timelines_mut().sync(); + } + r.timeline_mut(&r_rects[i]).play_with(unhighlight); + } +} + +#[scene] +#[preview] +#[output] +/// A selective sort ranim example with input of 10. +/// +/// +/// +pub fn selective_sort_10(r: &mut RanimScene) { + selective_sort(r, 10); +} + +#[scene] +#[preview] +#[output] +/// A selective sort ranim example with input of 100. +/// +/// +/// +pub fn selective_sort_100(r: &mut RanimScene) { + selective_sort(r, 100); +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_selective_sort_10() { + run_scene_app( + selective_sort_10_scene.constructor, + selective_sort_10_scene.name.to_string(), + ); +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_selective_sort_100() { + run_scene_app( + selective_sort_100_scene.constructor, + selective_sort_100_scene.name.to_string(), + ); +} diff --git a/packages/ranim-examples/src/tutorial/getting_started.rs b/packages/ranim-examples/src/tutorial/getting_started.rs new file mode 100644 index 00000000..bbfd5f26 --- /dev/null +++ b/packages/ranim-examples/src/tutorial/getting_started.rs @@ -0,0 +1,147 @@ +//! Examples to get started with ranim +use ranim::{ + animation::{creation::{CreationAnim, WritingAnim}, fading::FadingAnim, transform::TransformAnim}, color::palettes::manim, items::vitem::{geometry::{Circle, Rectangle, Square}, VItem}, + prelude::*, utils::rate_functions::linear, +}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[scene] +#[preview] +#[output] +/// This example shows the basic api of [`RanimScene`]. +/// +/// +/// +pub fn getting_started0(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + // A Square with size 2.0 and color blue + let square = Square::new(2.0).with(|square| { + square.set_color(manim::BLUE_C); + }); + + let r_square = r.insert(square); + { + let timeline = r.timeline_mut(&r_square); + timeline + .play_with(|square| square.fade_in()) + .forward(1.0) + .hide() + .forward(1.0) + .show() + .forward(1.0) + .play_with(|square| square.fade_out()); + } +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_getting_started0() { + run_scene_app( + getting_started0_scene.constructor, + getting_started0_scene.name.to_string(), + ); +} + + +#[scene] +#[preview] +#[output] +/// This example shows the basic api of [`RanimScene`]. +/// +/// +/// +pub fn getting_started1(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + // A Square with size 2.0 and color blue + let square = Square::new(2.0).with(|square| { + square.set_color(manim::BLUE_C); + }); + + let circle = Circle::new(2.0).with(|circle| { + circle.set_color(manim::RED_C); + }); + + // In order to do more low-level opeerations, + // sometimes we need to convert the item to a low-level item. + let r_vitem = r.insert(VItem::from(square)); + { + let timeline = r.timeline_mut(&r_vitem); + timeline.play_with(|vitem| vitem.transform_to(VItem::from(circle.clone()))); + timeline.play_with(|vitem| vitem.unwrite()); + } +} + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_getting_started1() { + run_scene_app( + getting_started1_scene.constructor, + getting_started1_scene.name.to_string(), + ); +} + +#[scene] +#[preview] +#[output] +/// This example shows the basic api of [`RanimScene`]. +/// +/// +/// +fn getting_started2(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + let rect = Rectangle::new(4.0, 9.0 / 4.0).with(|rect| { + rect.set_stroke_color(manim::GREEN_C); + }); + + // The new initialized timeline is hidden by default, use show to start encoding a static anim and make it show + let r_rect: ItemId = r.insert_and(rect, |timeline| { + timeline.show(); + }); + // or use `insert_and_show` + // let r_rect: ItemId = r.insert_and_show(rect) + + r.timelines_mut().forward(1.0); + + let square = Square::new(2.0).with(|square| { + square.set_color(manim::BLUE_C); + }); + let circle = Circle::new(2.0).with(|circle| { + circle.set_color(manim::RED_C); + }); + let r_vitem = r.insert(VItem::from(square)); + { + let timeline = r.timeline_mut(&r_vitem); + timeline + .forward(1.0) + .play_with(|vitem| vitem.create()) + .play_with(|vitem| { + vitem + .transform_to(VItem::from(circle.clone())) + .with_rate_func(linear) + }) + .play_with(|vitem| vitem.unwrite()); + } + + let r_rect: ItemId = r.map(r_rect, VItem::from); + r.timeline_mut(&r_rect).play_with(|rect| rect.uncreate()); +} + + +#[cfg(any(feature = "app", target_arch = "wasm32"))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn run_getting_started2() { + run_scene_app( + getting_started2_scene.constructor, + getting_started2_scene.name.to_string(), + ); +} \ No newline at end of file diff --git a/packages/ranim-macros/src/lib.rs b/packages/ranim-macros/src/lib.rs index 6d27abcb..517296c6 100644 --- a/packages/ranim-macros/src/lib.rs +++ b/packages/ranim-macros/src/lib.rs @@ -54,6 +54,11 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { let fn_name = &input_fn.sig.ident; let vis = &input_fn.vis; let fn_body = &input_fn.block; + let doc_attrs: Vec<_> = input_fn + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + .collect(); // 场景名称 let scene_name = attrs.name.unwrap_or_else(|| fn_name.to_string()); @@ -118,11 +123,14 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { // 构造 Scene 并塞进分布式切片 let expanded = quote! { + #(#doc_attrs)* #vis fn #fn_name(r: &mut #ranim::timeline::RanimScene) #fn_body + #[doc(hidden)] static #static_output_name: [#ranim::Output; #output_cnt] = [#(#outputs),*]; #[cfg_attr(not(target_family = "wasm"), #ranim::linkme::distributed_slice(#ranim::SCENES))] #[cfg_attr(not(target_family = "wasm"), linkme(crate = #ranim::linkme))] + #[doc(hidden)] static #static_name: #ranim::Scene = #scene; #[allow(non_upper_case_globals)] diff --git a/src/app/mod.rs b/src/app/mod.rs index 169d758e..4ffc8c4b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -238,9 +238,12 @@ struct WinitApp { window: Option>, app_renderer: Option, wgpu_ctx: Option, + #[cfg(target_arch = "wasm32")] + container_id: String, } impl WinitApp { + #[cfg(not(target_arch = "wasm32"))] fn new(app_state: AppState, event_loop: &EventLoop) -> Self { Self { event_loop_proxy: Some(event_loop.create_proxy()), @@ -252,6 +255,19 @@ impl WinitApp { wgpu_ctx: None, } } + #[cfg(target_arch = "wasm32")] + fn new(app_state: AppState, event_loop: &EventLoop, container_id: String) -> Self { + Self { + event_loop_proxy: Some(event_loop.create_proxy()), + app_state, + + size: (0, 0), + window: None, + app_renderer: None, + wgpu_ctx: None, + container_id, + } + } } // MARK: Redraw @@ -400,18 +416,41 @@ impl ApplicationHandler for WinitApp { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); + log::info!("searching for {}", self.container_id); let canvas = document - .get_element_by_id(&format!("app-{}", self.app_state.title)) - .unwrap(); - log::info!("searching for app-{}", self.app_state.title); - let canvas = canvas.dyn_into::().ok(); - - log::info!("found canvas: {}", canvas.is_some()); - if let Some(canvas) = canvas.as_ref() { - self.size = (canvas.width(), canvas.height()); - log::info!("canvas size: {:?}", self.size); - } - window_attrs = window_attrs.with_canvas(canvas); + .get_element_by_id(&self.container_id) + .and_then(|canvas| canvas.dyn_into::().ok()); + + let canvas = match canvas { + Some(canvas) => { + log::info!("found canvas"); + self.size = (canvas.width(), canvas.height()); + log::info!("canvas size: {:?}", self.size); + canvas + } + None => { + log::info!("canvas not found, creating a new one"); + let canvas = document + .create_element("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + + // 设置 canvas 的 id 和尺寸 + canvas.set_id(&self.container_id); + canvas.set_width(800); // 默认宽度 + canvas.set_height(600); // 默认高度 + + // 将 canvas 添加到文档中 + document.body().unwrap().append_child(&canvas).unwrap(); + + self.size = (canvas.width(), canvas.height()); + log::info!("created canvas with size: {:?}", self.size); + canvas + } + }; + + window_attrs = window_attrs.with_canvas(Some(canvas)); // window_attrs = // window_attrs.with_prevent_default(window.prevent_default_event_handling); @@ -504,7 +543,7 @@ impl ApplicationHandler for WinitApp { use crate::PUFFIN_GPU_PROFILER; /// Runs an app with the given app state -pub fn run_app(app: AppState) { +pub fn run_app(app: AppState, #[cfg(target_arch = "wasm32")] container_id: String) { #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); @@ -532,24 +571,27 @@ pub fn run_app(app: AppState) { .unwrap(); event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); - #[allow(unused_mut)] - let mut app = WinitApp::new(app, &event_loop); - #[cfg(target_arch = "wasm32")] { + let mut app = WinitApp::new(app, &event_loop, container_id); use winit::platform::web::EventLoopExtWebSys; event_loop.spawn_app(app); } #[cfg(not(target_arch = "wasm32"))] { + let mut app = WinitApp::new(app, &event_loop); event_loop.run_app(&mut app).unwrap(); } } /// Runs a scene preview app on a scene constructor pub fn run_scene_app(constructor: impl SceneConstructor, name: String) { - let app_state = AppState::new_with_title(constructor, name); - run_app(app_state); + let app_state = AppState::new_with_title(constructor, name.clone()); + run_app( + app_state, + #[cfg(target_arch = "wasm32")] + format!("ranim-app-{name}"), + ); } /// Preview a scene From f464ea22d8edc61074a822ac3029dafd8da7b044 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Mon, 25 Aug 2025 17:15:42 +0800 Subject: [PATCH 3/6] refactor: use inventory instead of linkme for find_scene api in wasm --- Cargo.lock | 31 ++-- Cargo.toml | 41 +---- examples/bubble_sort/main.rs | 142 ------------------ examples/getting_started1/main.rs | 55 ------- examples/getting_started2/main.rs | 75 --------- examples/selective_sort/main.rs | 132 ---------------- packages/ranim-cli/src/cli/render.rs | 2 +- packages/ranim-cli/src/lib.rs | 41 +++-- packages/ranim-examples/src/lib.rs | 12 +- .../src/tutorial/getting_started.rs | 55 +++---- packages/ranim-macros/src/lib.rs | 5 +- src/app/mod.rs | 29 ++-- src/lib.rs | 65 +++++--- 13 files changed, 131 insertions(+), 554 deletions(-) delete mode 100644 examples/bubble_sort/main.rs delete mode 100644 examples/getting_started1/main.rs delete mode 100644 examples/getting_started2/main.rs delete mode 100644 examples/selective_sort/main.rs diff --git a/Cargo.lock b/Cargo.lock index ca85b07c..32811350 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2426,6 +2426,15 @@ dependencies = [ "syn", ] +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -2686,26 +2695,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linkme" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b1703c00b2a6a70738920544aa51652532cacddfec2e162d2e29eae01e665c" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d55ca5d5a14363da83bf3c33874b8feaa34653e760d5216d7ef9829c88001a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -4013,8 +4002,8 @@ dependencies = [ "glam", "image", "indicatif", + "inventory", "itertools 0.14.0", - "linkme", "log", "lru", "pollster", diff --git a/Cargo.toml b/Cargo.toml index 25e3bb2e..ccd99d75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,13 +91,14 @@ typst-svg = "0.13.1" chrono = "0.4.41" reqwest = "0.12.23" slotmap = "1.0.7" +inventory = "0.3.21" [target.'cfg(not(target_family = "wasm"))'.dependencies] egui-winit = { version = "0.32.1", optional = true } which = "8.0.0" flate2 = "1.1.2" reqwest = { version = "0.12.22", features = ["blocking"] } -linkme = "0.3.33" +# linkme = "0.3.33" # web: [target.'cfg(target_family = "wasm")'.dependencies] @@ -160,37 +161,6 @@ path = "examples/basic/main.rs" [package.metadata.example.basic] wasm = true -[[example]] -name = "bubble_sort" -path = "examples/bubble_sort/main.rs" - -[package.metadata.example.bubble_sort] -wasm = true - -[[example]] -name = "getting_started0" -path = "examples/getting_started0/main.rs" - -[package.metadata.example.getting_started0] -wasm = true -hide = true - -[[example]] -name = "getting_started1" -path = "examples/getting_started1/main.rs" - -[package.metadata.example.getting_started1] -wasm = true -hide = true - -[[example]] -name = "getting_started2" -path = "examples/getting_started2/main.rs" - -[package.metadata.example.getting_started2] -wasm = true -hide = true - [[example]] name = "hanoi" path = "examples/hanoi/main.rs" @@ -223,13 +193,6 @@ path = "examples/ranim_logo/main.rs" [package.metadata.example.ranim_logo] wasm = true -[[example]] -name = "selective_sort" -path = "examples/selective_sort/main.rs" - -[package.metadata.example.selective_sort] -wasm = true - [[example]] name = "extract_vitem_visualize" path = "examples/extract_vitem_visualize/main.rs" diff --git a/examples/bubble_sort/main.rs b/examples/bubble_sort/main.rs deleted file mode 100644 index d51a1b08..00000000 --- a/examples/bubble_sort/main.rs +++ /dev/null @@ -1,142 +0,0 @@ -use log::LevelFilter; -use rand::{SeedableRng, seq::SliceRandom}; -use ranim::{ - animation::transform::TransformAnim, - color::palettes::manim, - components::Anchor, - glam::{DVec3, dvec2}, - items::vitem::geometry::Rectangle, - prelude::*, - timeline::TimeMark, - utils::rate_functions::linear, -}; - -fn bubble_sort(r: &mut RanimScene, num: usize) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - - let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); - let padded_frame_size = frame_size * 0.9; - - let anim_step_duration = 15.0 / num.pow(2) as f64; - - let width_unit = padded_frame_size.x / num as f64; - let height_unit = padded_frame_size.y / num as f64; - - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(114514); - let mut heights = (1..=num) - .map(|x| x as f64 * height_unit) - .collect::>(); - heights.shuffle(&mut rng); - - let padded_frame_bl = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); - let mut r_rects = heights - .iter() - .enumerate() - .map(|(i, &height)| { - let target_bc_coord = - padded_frame_bl.extend(0.0) + DVec3::X * (width_unit * i as f64 + width_unit / 2.0); - let rect = Rectangle::new(width_unit, height).with(|rect| { - rect.stroke_width = 0.0; - rect.set_fill_color(manim::WHITE.with_alpha(0.5)) - .scale(DVec3::splat(0.8)) - .put_anchor_on(Anchor::edge(0, -1, 0), target_bc_coord); - }); - r.insert_and_show(rect) - }) - .collect::>(); - - let anim_highlight = |rect: Rectangle| { - rect.transform(|data| { - data.set_fill_color(manim::BLUE_C.with_alpha(0.5)); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }; - let anim_unhighlight = |rect: Rectangle| { - rect.transform(|data| { - data.set_fill_color(manim::WHITE.with_alpha(0.5)); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }; - let shift_right = DVec3::X * width_unit; - let swap_shift = [shift_right, -shift_right]; - let anim_swap = |timeline: &mut RanimScene, r_rectab: &[&ItemId; 2]| { - let timelines = timeline.timeline_mut(r_rectab); - timelines - .into_iter() - .zip(swap_shift.iter()) - .for_each(|(timeline, shift)| { - timeline.play_with(|rect| { - rect.transform(|data| { - data.shift(*shift); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }); - }); - }; - - for i in (1..num).rev() { - for j in 0..i { - r.timeline_mut(&[&r_rects[j], &r_rects[j + 1]]) - .into_iter() - .for_each(|timeline| { - timeline.play_with(anim_highlight); - }); - if heights[j] > heights[j + 1] { - anim_swap(r, &[&r_rects[j], &r_rects[j + 1]]); - r.timelines_mut().sync(); - heights.swap(j, j + 1); - r_rects.swap(j, j + 1); - } - r.timeline_mut(&[&r_rects[j], &r_rects[j + 1]]) - .into_iter() - .for_each(|timeline| { - timeline.play_with(anim_unhighlight); - }); - r.timelines_mut().sync(); - } - } - - r.insert_time_mark( - r.timelines().max_total_secs(), - TimeMark::Capture(format!("preview-{num}.png")), - ); -} - -#[scene] -#[preview] -#[output(dir = "bubble_sort")] -fn bubble_sort_10(r: &mut RanimScene) { - bubble_sort(r, 10); -} - -#[scene(name = "bubble_sort")] -#[preview] -#[output(dir = "bubble_sort")] -fn bubble_sort_100(r: &mut RanimScene) { - bubble_sort(r, 100); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(bubble_sort_100_scene); - #[cfg(not(feature = "app"))] - { - render_scene(bubble_sort_10_scene); - render_scene(bubble_sort_100_scene); - } -} diff --git a/examples/getting_started1/main.rs b/examples/getting_started1/main.rs deleted file mode 100644 index 621682ac..00000000 --- a/examples/getting_started1/main.rs +++ /dev/null @@ -1,55 +0,0 @@ -use log::LevelFilter; -use ranim::{ - animation::{creation::WritingAnim, transform::TransformAnim}, - color::palettes::manim, - items::vitem::{ - VItem, - geometry::{Circle, Square}, - }, - prelude::*, -}; - -// ANCHOR: construct -#[scene] -#[preview] -#[output(dir = "getting_started1")] -fn getting_started1(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - // A Square with size 2.0 and color blue - let square = Square::new(2.0).with(|square| { - square.set_color(manim::BLUE_C); - }); - - let circle = Circle::new(2.0).with(|circle| { - circle.set_color(manim::RED_C); - }); - - // In order to do more low-level opeerations, - // sometimes we need to convert the item to a low-level item. - let r_vitem = r.insert(VItem::from(square)); - { - let timeline = r.timeline_mut(&r_vitem); - timeline.play_with(|vitem| vitem.transform_to(VItem::from(circle.clone()))); - timeline.play_with(|vitem| vitem.unwrite()); - } -} -// ANCHOR_END: construct - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(getting_started1_scene); - #[cfg(not(feature = "app"))] - render_scene(getting_started1_scene); -} diff --git a/examples/getting_started2/main.rs b/examples/getting_started2/main.rs deleted file mode 100644 index 0c524d4a..00000000 --- a/examples/getting_started2/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use log::LevelFilter; -use ranim::{ - animation::{ - creation::{CreationAnim, WritingAnim}, - transform::TransformAnim, - }, - color::palettes::manim, - items::vitem::{ - VItem, - geometry::{Circle, Rectangle, Square}, - }, - prelude::*, - utils::rate_functions::linear, -}; - -#[scene] -#[preview] -#[output(dir = "getting_started2")] -fn getting_started2(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - let rect = Rectangle::new(4.0, 9.0 / 4.0).with(|rect| { - rect.set_stroke_color(manim::GREEN_C); - }); - - // The new initialized timeline is hidden by default, use show to start encoding a static anim and make it show - let r_rect: ItemId = r.insert_and(rect, |timeline| { - timeline.show(); - }); - // or use `insert_and_show` - // let r_rect: ItemId = r.insert_and_show(rect) - - r.timelines_mut().forward(1.0); - - let square = Square::new(2.0).with(|square| { - square.set_color(manim::BLUE_C); - }); - let circle = Circle::new(2.0).with(|circle| { - circle.set_color(manim::RED_C); - }); - let r_vitem = r.insert(VItem::from(square)); - { - let timeline = r.timeline_mut(&r_vitem); - timeline - .forward(1.0) - .play_with(|vitem| vitem.create()) - .play_with(|vitem| { - vitem - .transform_to(VItem::from(circle.clone())) - .with_rate_func(linear) - }) - .play_with(|vitem| vitem.unwrite()); - } - - let r_rect: ItemId = r.map(r_rect, VItem::from); - r.timeline_mut(&r_rect).play_with(|rect| rect.uncreate()); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(getting_started2_scene); - #[cfg(not(feature = "app"))] - render_scene(getting_started2_scene); -} diff --git a/examples/selective_sort/main.rs b/examples/selective_sort/main.rs deleted file mode 100644 index b0afd495..00000000 --- a/examples/selective_sort/main.rs +++ /dev/null @@ -1,132 +0,0 @@ -use glam::{DVec3, dvec2}; -use log::LevelFilter; -use rand::{SeedableRng, seq::SliceRandom}; -use ranim::{ - animation::transform::TransformAnim, color::palettes::manim, components::Anchor, - items::vitem::geometry::Rectangle, prelude::*, timeline::TimeMark, - utils::rate_functions::linear, -}; - -fn selective_sort(r: &mut RanimScene, num: usize) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - - let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); - let padded_frame_size = frame_size * 0.9; - - let anim_step_duration = 15.0 / num.pow(2) as f64; - - let width_unit = padded_frame_size.x / num as f64; - let height_unit = padded_frame_size.y / num as f64; - - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(114514); - let mut heights = (1..=num) - .map(|x| x as f64 * height_unit) - .collect::>(); - heights.shuffle(&mut rng); - - let padded_frame_bl = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); - let mut r_rects = heights - .iter() - .enumerate() - .map(|(i, &height)| { - let target_bc_coord = - padded_frame_bl.extend(0.0) + DVec3::X * (width_unit * i as f64 + width_unit / 2.0); - let rect = Rectangle::new(width_unit, height).with(|rect| { - rect.fill_rgba = manim::WHITE.with_alpha(0.5); - rect.scale(DVec3::splat(0.8)) - .put_anchor_on(Anchor::edge(0, -1, 0), target_bc_coord); - }); - r.insert_and_show(rect) - }) - .collect::>(); - - let highlight = |rect: Rectangle| { - rect.transform(|data| { - data.set_color(manim::RED_C).set_fill_opacity(0.5); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }; - let unhighlight = |rect: Rectangle| { - rect.transform(|data| { - data.set_color(manim::WHITE).set_fill_opacity(0.5); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }; - - let shift_right = DVec3::X * width_unit; - for i in 0..num - 1 { - r.timeline_mut(&r_rects[i]).play_with(highlight); - for j in i + 1..num { - r.timeline_mut(&r_rects[j]).play_with(highlight); - r.timelines_mut().sync(); - - if heights[i] > heights[j] { - let dir = [shift_right, -shift_right]; - let color = [manim::BLUE_C, manim::RED_C]; - r.timeline_mut(&[&r_rects[i], &r_rects[j]]) - .iter_mut() - .zip(dir) - .zip(color) - .for_each(|((timeline, dir), color)| { - timeline.play_with(|rect| { - rect.transform(|rect| { - rect.shift(dir * (j - i) as f64) - .set_color(color) - .set_fill_opacity(0.5); - }) - .with_duration(anim_step_duration) - .with_rate_func(linear) - }); - }); - heights.swap(i, j); - r_rects.swap(i, j); - } - r.timeline_mut(&r_rects[j]).play_with(unhighlight); - r.timelines_mut().sync(); - } - r.timeline_mut(&r_rects[i]).play_with(unhighlight); - } - - r.insert_time_mark( - r.timelines().max_total_secs() / 2.0, - TimeMark::Capture(format!("preview-{num}.png")), - ); -} - -#[scene] -#[preview] -#[output(dir = "selective_sort")] -fn selective_sort_10(r: &mut RanimScene) { - selective_sort(r, 10); -} - -#[scene(name = "selective_sort")] -#[preview] -#[output(dir = "selective_sort")] -fn selective_sort_100(r: &mut RanimScene) { - selective_sort(r, 100); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(selective_sort_100_scene); - #[cfg(not(feature = "app"))] - { - render_scene(selective_sort_10_scene); - render_scene(selective_sort_100_scene); - } -} diff --git a/packages/ranim-cli/src/cli/render.rs b/packages/ranim-cli/src/cli/render.rs index 9443fac9..bff6b964 100644 --- a/packages/ranim-cli/src/cli/render.rs +++ b/packages/ranim-cli/src/cli/render.rs @@ -31,7 +31,7 @@ pub fn render_command(args: &Args, scenes: &[String]) { .unwrap() .expect("Failed on initial build"); - let all_scenes: Vec<&Scene> = lib.scenes().iter().collect::>(); + let all_scenes: Vec<&Scene> = lib.scenes().collect::>(); let scenes_to_render: Vec<&Scene> = if scenes.is_empty() { all_scenes.clone() } else { diff --git a/packages/ranim-cli/src/lib.rs b/packages/ranim-cli/src/lib.rs index b9f09c40..782ba090 100644 --- a/packages/ranim-cli/src/lib.rs +++ b/packages/ranim-cli/src/lib.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use async_channel::{Receiver, Sender, bounded}; use libloading::{Library, Symbol}; -use log::{debug, error, info}; +use log::{error, info}; use ranim::Scene; use std::{ path::{Path, PathBuf}, @@ -118,6 +118,21 @@ pub struct RanimUserLibrary { temp_path: PathBuf, } +pub struct RanimUserLibrarySceneIter<'a> { + lib: &'a RanimUserLibrary, + idx: usize, +} + +impl<'a> Iterator for RanimUserLibrarySceneIter<'a> { + type Item = &'a Scene; + + fn next(&mut self) -> Option { + let res = self.lib.get_scene(self.idx); + self.idx += 1; + res + } +} + impl RanimUserLibrary { pub fn load(dylib_path: impl AsRef) -> Self { let dylib_path = dylib_path.as_ref(); @@ -146,24 +161,28 @@ impl RanimUserLibrary { } } - /// Safety: dylib has a `scenes` and a `scene_cnt` fn with the correct signature and safe implementation - pub fn scenes(&self) -> &[Scene] { + pub fn scene_cnt(&self) -> usize { let scene_cnt: Symbol usize> = unsafe { self.inner.as_ref().unwrap().get(b"scene_cnt").unwrap() }; + scene_cnt() + } - let scenes: Symbol *const Scene> = - unsafe { self.inner.as_ref().unwrap().get(b"scenes").unwrap() }; - - let scene_cnt = scene_cnt(); - debug!("scene_cnt: {scene_cnt}"); - let scenes = scenes(); + pub fn get_scene(&self, idx: usize) -> Option<&Scene> { + let get_scene: Symbol *const Scene> = + unsafe { self.inner.as_ref().unwrap().get(b"get_scene").unwrap() }; + if self.scene_cnt() <= idx { + None + } else { + Some(unsafe { &*get_scene(idx) }) + } + } - unsafe { std::slice::from_raw_parts(scenes, scene_cnt) } + pub fn scenes(&self) -> impl Iterator { + RanimUserLibrarySceneIter { lib: self, idx: 0 } } pub fn get_preview_func(&self) -> Result<&Scene> { self.scenes() - .iter() .find(|s| s.preview) .context("no scene marked with `#[preview]` found") } diff --git a/packages/ranim-examples/src/lib.rs b/packages/ranim-examples/src/lib.rs index 3b0cd2b1..75b6b509 100644 --- a/packages/ranim-examples/src/lib.rs +++ b/packages/ranim-examples/src/lib.rs @@ -27,8 +27,8 @@ pub mod sort; /// /// /// pub fn hello_ranim(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -59,11 +59,3 @@ pub fn hello_ranim(r: &mut RanimScene) { timeline.play_with(|circle| circle.fade_out()); }; } - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_hello_ranim() { - run_scene_app( - hello_ranim_scene.constructor, - hello_ranim_scene.name.to_string(), - ); -} diff --git a/packages/ranim-examples/src/tutorial/getting_started.rs b/packages/ranim-examples/src/tutorial/getting_started.rs index bbfd5f26..caf75d84 100644 --- a/packages/ranim-examples/src/tutorial/getting_started.rs +++ b/packages/ranim-examples/src/tutorial/getting_started.rs @@ -1,7 +1,17 @@ //! Examples to get started with ranim use ranim::{ - animation::{creation::{CreationAnim, WritingAnim}, fading::FadingAnim, transform::TransformAnim}, color::palettes::manim, items::vitem::{geometry::{Circle, Rectangle, Square}, VItem}, - prelude::*, utils::rate_functions::linear, + animation::{ + creation::{CreationAnim, WritingAnim}, + fading::FadingAnim, + transform::TransformAnim, + }, + color::palettes::manim, + items::vitem::{ + VItem, + geometry::{Circle, Rectangle, Square}, + }, + prelude::*, + utils::rate_functions::linear, }; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -13,8 +23,8 @@ use wasm_bindgen::prelude::*; /// /// /// pub fn getting_started0(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -37,16 +47,6 @@ pub fn getting_started0(r: &mut RanimScene) { } } -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_getting_started0() { - run_scene_app( - getting_started0_scene.constructor, - getting_started0_scene.name.to_string(), - ); -} - - #[scene] #[preview] #[output] @@ -54,8 +54,8 @@ pub fn run_getting_started0() { /// /// /// pub fn getting_started1(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -78,15 +78,6 @@ pub fn getting_started1(r: &mut RanimScene) { } } -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_getting_started1() { - run_scene_app( - getting_started1_scene.constructor, - getting_started1_scene.name.to_string(), - ); -} - #[scene] #[preview] #[output] @@ -94,8 +85,8 @@ pub fn run_getting_started1() { /// /// /// fn getting_started2(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -135,13 +126,3 @@ fn getting_started2(r: &mut RanimScene) { let r_rect: ItemId = r.map(r_rect, VItem::from); r.timeline_mut(&r_rect).play_with(|rect| rect.uncreate()); } - - -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_getting_started2() { - run_scene_app( - getting_started2_scene.constructor, - getting_started2_scene.name.to_string(), - ); -} \ No newline at end of file diff --git a/packages/ranim-macros/src/lib.rs b/packages/ranim-macros/src/lib.rs index 517296c6..e024dae2 100644 --- a/packages/ranim-macros/src/lib.rs +++ b/packages/ranim-macros/src/lib.rs @@ -128,10 +128,11 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { #[doc(hidden)] static #static_output_name: [#ranim::Output; #output_cnt] = [#(#outputs),*]; - #[cfg_attr(not(target_family = "wasm"), #ranim::linkme::distributed_slice(#ranim::SCENES))] - #[cfg_attr(not(target_family = "wasm"), linkme(crate = #ranim::linkme))] #[doc(hidden)] static #static_name: #ranim::Scene = #scene; + #ranim::inventory::submit!{ + #scene + } #[allow(non_upper_case_globals)] #vis static #static_scene_name: &'static #ranim::Scene = &#static_name; diff --git a/src/app/mod.rs b/src/app/mod.rs index 4246e436..cd16b6ba 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -32,6 +32,9 @@ use crate::{ utils::wgpu::WgpuContext, }; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + #[derive(Default, Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct OccupiedScreenSpace { @@ -610,7 +613,7 @@ impl ApplicationHandler for WinitApp { use crate::PUFFIN_GPU_PROFILER; /// Runs an app with the given app state -pub fn run_app(app: AppState) { +pub fn run_app(app: AppState, #[cfg(target_arch = "wasm32")] container_id: String) { #[cfg(feature = "profiling")] let (_cpu_server, _gpu_server) = { puffin::set_scopes_on(true); @@ -645,16 +648,24 @@ pub fn run_app(app: AppState) { } } -/// Runs a scene preview app on a scene constructor -pub fn run_scene_app(constructor: impl SceneConstructor, name: String) { - #[cfg(target_arch = "wasm32")] - { - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("Failed to initialize console_log"); +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +impl Scene { + pub fn run_app(&self) { + run_scene_app(self.constructor, self.name.to_string()); } + pub fn run_app_with_name(&self, name: String) { + run_scene_app(self.constructor, name); + } +} - let app_state = AppState::new_with_title(constructor, name); - run_app(app_state); +/// Runs a scene preview app on a scene constructor +pub fn run_scene_app(constructor: impl SceneConstructor, name: String) { + let app_state = AppState::new_with_title(constructor, name.clone()); + run_app( + app_state, + #[cfg(target_arch = "wasm32")] + format!("ranim-app-{name}"), + ); } /// Preview a scene diff --git a/src/lib.rs b/src/lib.rs index 5262d7a4..deb5aeb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,9 @@ use animation::EvalResult; use log::{info, trace}; use timeline::{RanimScene, SealedRanimScene}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + #[cfg(not(target_arch = "wasm32"))] use file_writer::{FileWriter, FileWriterBuilder}; #[cfg(not(target_arch = "wasm32"))] @@ -89,23 +92,57 @@ impl SceneConstructor for F { } // MARK: Dylib part -#[cfg(not(target_family = "wasm"))] -pub use linkme; - #[doc(hidden)] #[derive(Clone)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct Scene { + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub name: &'static str, + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub constructor: fn(&mut RanimScene), + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub config: SceneConfig, + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub outputs: &'static [Output], pub preview: bool, } -#[cfg(not(target_family = "wasm"))] +pub use inventory; + +inventory::collect!(Scene); + +#[doc(hidden)] +#[unsafe(no_mangle)] +pub extern "C" fn get_scene(idx: usize) -> *const Scene { + inventory::iter::().skip(idx).take(1).next().unwrap() +} + #[doc(hidden)] -#[linkme::distributed_slice] -pub static SCENES: [Scene]; +#[unsafe(no_mangle)] +pub extern "C" fn scene_cnt() -> usize { + inventory::iter::().count() +} + +#[cfg(target_arch = "wasm32")] +unsafe extern "C" { + fn __wasm_call_ctors(); +} + +/// Return a scene with matched name +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub fn find_scene(name: &str) -> Option { + inventory::iter::().find(|s| s.name == name).cloned() +} + +#[cfg(target_arch = "wasm32")] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] +fn wasm_start() { + unsafe { + __wasm_call_ctors(); + } + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("Failed to initialize console_log"); +} /// Scene config #[derive(Debug, Clone)] @@ -124,6 +161,7 @@ impl Default for SceneConfig { /// The output of a scene #[derive(Debug, Clone)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct Output { /// The width of the output texture in pixels. pub width: u32, @@ -136,6 +174,7 @@ pub struct Output { /// The directory to save the output /// /// Related to the `output` folder, Or absolute. + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub dir: &'static str, } @@ -156,20 +195,6 @@ impl Output { }; } -#[cfg(not(target_family = "wasm"))] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub extern "C" fn scenes() -> *const Scene { - SCENES.as_ptr() -} - -#[cfg(not(target_family = "wasm"))] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub extern "C" fn scene_cnt() -> usize { - SCENES.len() -} - // MARK: Prelude /// The preludes pub mod prelude { From 8a480ed80b9f687cc1a129feb0237bb670facb2f Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 28 Aug 2025 09:56:01 +0800 Subject: [PATCH 4/6] added wasm_demo_doc attr macro --- Cargo.lock | 1 + packages/ranim-examples/Cargo.toml | 1 + packages/ranim-examples/src/lib.rs | 9 +--- packages/ranim-examples/src/sort.rs | 48 ++----------------- .../src/tutorial/getting_started.rs | 21 ++------ packages/ranim-macros/src/lib.rs | 20 +++++++- packages/ranim-macros/src/scene.rs | 5 ++ src/lib.rs | 2 +- 8 files changed, 37 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32811350..ec0bdd47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4060,6 +4060,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "ranim", + "ranim-macros", "wasm-bindgen", ] diff --git a/packages/ranim-examples/Cargo.toml b/packages/ranim-examples/Cargo.toml index 01b62272..4de5c29c 100644 --- a/packages/ranim-examples/Cargo.toml +++ b/packages/ranim-examples/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["rlib", "cdylib"] # use cdylib to produce .wasm file [dependencies] ranim.workspace = true +ranim-macros.workspace = true rand = "0.9.2" rand_chacha = "0.9.0" wasm-bindgen = "0.2.100" diff --git a/packages/ranim-examples/src/lib.rs b/packages/ranim-examples/src/lib.rs index 75b6b509..73fa2b80 100644 --- a/packages/ranim-examples/src/lib.rs +++ b/packages/ranim-examples/src/lib.rs @@ -22,14 +22,9 @@ pub mod sort; #[scene] #[preview] -#[output] +#[wasm_demo_doc] +#[output(dir = "hello_ranim")] /// Hello Ranim! -/// -/// -/// pub fn hello_ranim(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); let square = Square::new(2.0).with(|square| { diff --git a/packages/ranim-examples/src/sort.rs b/packages/ranim-examples/src/sort.rs index 629ea83c..de7183b4 100644 --- a/packages/ranim-examples/src/sort.rs +++ b/packages/ranim-examples/src/sort.rs @@ -104,28 +104,17 @@ pub fn bubble_sort(r: &mut RanimScene, num: usize) { #[scene] #[preview] -#[output] +#[output(dir = "bubble_sort")] /// A bubble sort ranim example with input of 10. -/// -/// -/// pub fn bubble_sort_10(r: &mut RanimScene) { bubble_sort(r, 10); } #[scene] +#[wasm_demo_doc] #[preview] -#[output] +#[output(dir = "bubble_sort")] /// A bubble sort ranim example with input of 100. -/// -/// -/// pub fn bubble_sort_100(r: &mut RanimScene) { bubble_sort(r, 100); } @@ -234,47 +223,20 @@ pub fn selective_sort(r: &mut RanimScene, num: usize) { } #[scene] +#[wasm_demo_doc] #[preview] #[output] /// A selective sort ranim example with input of 10. -/// -/// -/// pub fn selective_sort_10(r: &mut RanimScene) { selective_sort(r, 10); } #[scene] +#[wasm_demo_doc] #[preview] #[output] /// A selective sort ranim example with input of 100. -/// -/// -/// pub fn selective_sort_100(r: &mut RanimScene) { selective_sort(r, 100); } -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_selective_sort_10() { - run_scene_app( - selective_sort_10_scene.constructor, - selective_sort_10_scene.name.to_string(), - ); -} - -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_selective_sort_100() { - run_scene_app( - selective_sort_100_scene.constructor, - selective_sort_100_scene.name.to_string(), - ); -} diff --git a/packages/ranim-examples/src/tutorial/getting_started.rs b/packages/ranim-examples/src/tutorial/getting_started.rs index caf75d84..a53eeadd 100644 --- a/packages/ranim-examples/src/tutorial/getting_started.rs +++ b/packages/ranim-examples/src/tutorial/getting_started.rs @@ -17,15 +17,10 @@ use ranim::{ use wasm_bindgen::prelude::*; #[scene] +#[wasm_demo_doc] #[preview] #[output] /// This example shows the basic api of [`RanimScene`]. -/// -/// -/// pub fn getting_started0(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); // A Square with size 2.0 and color blue @@ -48,15 +43,10 @@ pub fn getting_started0(r: &mut RanimScene) { } #[scene] +#[wasm_demo_doc] #[preview] #[output] /// This example shows the basic api of [`RanimScene`]. -/// -/// -/// pub fn getting_started1(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); // A Square with size 2.0 and color blue @@ -79,15 +69,10 @@ pub fn getting_started1(r: &mut RanimScene) { } #[scene] +#[wasm_demo_doc] #[preview] #[output] /// This example shows the basic api of [`RanimScene`]. -/// -/// -/// fn getting_started2(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); let rect = Rectangle::new(4.0, 9.0 / 4.0).with(|rect| { diff --git a/packages/ranim-macros/src/lib.rs b/packages/ranim-macros/src/lib.rs index e024dae2..77f7affd 100644 --- a/packages/ranim-macros/src/lib.rs +++ b/packages/ranim-macros/src/lib.rs @@ -31,6 +31,7 @@ struct SceneAttrs { name: Option, // #[scene(name = "...")] frame_height: Option, // #[scene(frame_height = 8.0)] preview: bool, // #[preview] + wasm_demo_doc: bool, // #[wasm_demo_doc] outputs: Vec, // #[output(...)] } @@ -44,7 +45,7 @@ struct OutputDef { dir: String, } -// ---------- 入口 ---------- +// MARK: scene #[proc_macro_attribute] pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { let ranim = ranim_path(); @@ -98,6 +99,17 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { } let preview = attrs.preview; + let doc = if attrs.wasm_demo_doc { + quote! { + #[doc = concat!("")] + #[doc = concat!(""] + } + } else { + quote! {} + }; let static_output_name = syn::Ident::new( &format!("__SCENE_{}_OUTPUTS", fn_name.to_string().to_uppercase()), @@ -123,6 +135,7 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { // 构造 Scene 并塞进分布式切片 let expanded = quote! { + #doc #(#doc_attrs)* #vis fn #fn_name(r: &mut #ranim::timeline::RanimScene) #fn_body @@ -151,6 +164,11 @@ pub fn preview(_: TokenStream, _: TokenStream) -> TokenStream { TokenStream::new() } +#[proc_macro_attribute] +pub fn wasm_demo_doc(_attr: TokenStream, _: TokenStream) -> TokenStream { + TokenStream::new() +} + // MARK: derive Traits #[proc_macro_derive(Fill)] diff --git a/packages/ranim-macros/src/scene.rs b/packages/ranim-macros/src/scene.rs index 35634fa5..fcabd413 100644 --- a/packages/ranim-macros/src/scene.rs +++ b/packages/ranim-macros/src/scene.rs @@ -41,6 +41,11 @@ pub fn parse_scene_attrs( continue; } + if attr.path().is_ident("wasm_demo_doc") { + res.wasm_demo_doc = true; + continue; + } + if let Meta::List(list) = &attr.meta && list.path.is_ident("output") { diff --git a/src/lib.rs b/src/lib.rs index deb5aeb2..ebe3a4f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,7 +203,7 @@ pub mod prelude { #[cfg(not(target_arch = "wasm32"))] pub use crate::{render, render_scene, render_scene_output}; - pub use ranim_macros::{output, preview, scene}; + pub use ranim_macros::{output, preview, wasm_demo_doc, scene}; pub use crate::items::{ItemId, camera_frame::CameraFrame}; pub use crate::timeline::{RanimScene, TimelineFunc, TimelinesFunc}; From f02112673efefcbf6c0c9d84512f53edc0e8e2e4 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 28 Aug 2025 18:29:16 +0800 Subject: [PATCH 5/6] refactor: move examples to ranim-examples crate --- Cargo.lock | 1 + Cargo.toml | 61 ----- examples/arc/README.md | 8 - examples/arc/main.rs | 78 ------ examples/arc_between_points/README.md | 7 - examples/arc_between_points/main.rs | 76 ------ examples/basic/README.md | 7 - examples/hello_ranim/main.rs | 68 ------ examples/palettes/README.md | 5 - examples/palettes/main.rs | 77 ------ examples/ranim_logo/main.rs | 173 ------------- packages/ranim-examples/Cargo.toml | 1 + .../ranim-examples/src/algo/hanoi.rs | 41 +--- .../ranim-examples/src/{ => algo}/sort.rs | 20 +- .../ranim-examples/src/camera.rs | 23 +- packages/ranim-examples/src/lib.rs | 228 +++++++++++++++++- .../ranim-examples/src/tutorial/extract.rs | 48 +--- .../src/tutorial/getting_started.rs | 6 +- packages/ranim-examples/src/vitem/geometry.rs | 112 +++++++++ .../ranim-examples/src/vitem/svg.rs | 28 +-- packages/ranim-macros/src/lib.rs | 38 ++- src/color/mod.rs | 5 +- src/lib.rs | 2 +- 23 files changed, 398 insertions(+), 715 deletions(-) delete mode 100644 examples/arc/README.md delete mode 100644 examples/arc/main.rs delete mode 100644 examples/arc_between_points/README.md delete mode 100644 examples/arc_between_points/main.rs delete mode 100644 examples/basic/README.md delete mode 100644 examples/hello_ranim/main.rs delete mode 100644 examples/palettes/README.md delete mode 100644 examples/palettes/main.rs delete mode 100644 examples/ranim_logo/main.rs rename examples/hanoi/main.rs => packages/ranim-examples/src/algo/hanoi.rs (81%) rename packages/ranim-examples/src/{ => algo}/sort.rs (93%) rename examples/perspective_blend/main.rs => packages/ranim-examples/src/camera.rs (83%) rename examples/extract_vitem_visualize/main.rs => packages/ranim-examples/src/tutorial/extract.rs (87%) create mode 100644 packages/ranim-examples/src/vitem/geometry.rs rename examples/basic/main.rs => packages/ranim-examples/src/vitem/svg.rs (70%) diff --git a/Cargo.lock b/Cargo.lock index ec0bdd47..5a0b2e7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4057,6 +4057,7 @@ dependencies = [ name = "ranim-examples" version = "0.1.3" dependencies = [ + "itertools 0.14.0", "rand 0.9.2", "rand_chacha 0.9.0", "ranim", diff --git a/Cargo.toml b/Cargo.toml index ccd99d75..3809b271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,64 +138,3 @@ pre-release-hook = [ pre-release-replacements = [ { file = "README.md", search = "ranim = \"[^\"]+\"", replace = "{{crate_name}} = \"{{version}}\"" }, ] - -# MARK: Examples -[[example]] -name = "arc" -path = "examples/arc/main.rs" - -[package.metadata.example.arc] -wasm = true - -[[example]] -name = "arc_between_points" -path = "examples/arc_between_points/main.rs" - -[package.metadata.example.arc_between_points] -wasm = true - -[[example]] -name = "basic" -path = "examples/basic/main.rs" - -[package.metadata.example.basic] -wasm = true - -[[example]] -name = "hanoi" -path = "examples/hanoi/main.rs" - -[package.metadata.example.hanoi] -wasm = true - -[[example]] -name = "hello_ranim" -path = "examples/hello_ranim/main.rs" - -[package.metadata.example.hello_ranim] -wasm = true - -[[example]] -name = "palettes" -path = "examples/palettes/main.rs" - -[[example]] -name = "perspective_blend" -path = "examples/perspective_blend/main.rs" - -[package.metadata.example.perspective_blend] -wasm = true - -[[example]] -name = "ranim_logo" -path = "examples/ranim_logo/main.rs" - -[package.metadata.example.ranim_logo] -wasm = true - -[[example]] -name = "extract_vitem_visualize" -path = "examples/extract_vitem_visualize/main.rs" - -[package.metadata.example.extract_vitem_visualize] -wasm = true diff --git a/examples/arc/README.md b/examples/arc/README.md deleted file mode 100644 index 5c67ceb1..00000000 --- a/examples/arc/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Arc - -This example demonstrates `Arc`. - -https://github.com/user-attachments/assets/4f8c6f36-e6b7-429b-8d2a-91c21e17dec2 - -> [arc.mp4](../../assets/arc.mp4) - diff --git a/examples/arc/main.rs b/examples/arc/main.rs deleted file mode 100644 index 2532b253..00000000 --- a/examples/arc/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -use itertools::Itertools; -use log::LevelFilter; -use ranim::{ - animation::{fading::FadingAnim, lagged::LaggedAnim}, - color::HueDirection, - glam::dvec2, - items::{Group, vitem::geometry::Arc}, - prelude::*, - timeline::TimeMark, -}; - -#[scene] -#[preview] -#[output(dir = "arc")] -fn arc(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - - // let frame_size = app.camera().size; - let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); - let frame_start = dvec2(frame_size.x / -2.0, frame_size.y / -2.0); - - let start_color = color!("#FF8080FF"); - let end_color = color!("#58C4DDFF"); - - let nrow = 10; - let ncol = 10; - let step_x = frame_size.x / ncol as f64; - let step_y = frame_size.y / nrow as f64; - - let arcs = (0..nrow) - .cartesian_product(0..ncol) - .map(|(i, j)| { - let (i, j) = (i as f64, j as f64); - - let angle = std::f64::consts::PI * (j + 1.0) / ncol as f64 * 360.0 / 180.0; - let radius = step_y / 2.0 * 0.8; - let color = start_color.lerp( - end_color, - i as f32 / (nrow - 1) as f32, - HueDirection::Increasing, - ); - let offset = frame_start + dvec2(j * step_x + step_x / 2.0, i * step_y + step_y / 2.0); - Arc::new(angle, radius).with(|arc| { - arc.stroke_width = 0.12 * (j as f32 + 0.02) / ncol as f32; - arc.set_stroke_color(color) - .put_center_on(offset.extend(0.0)); - }) - }) - .collect::>(); - let r_arcs = r.insert(arcs); - - r.timeline_mut(&r_arcs) - .play_with(|arcs| arcs.lagged(0.2, |arc| arc.fade_in()).with_duration(3.0)); - - r.insert_time_mark( - r.timelines().max_total_secs(), - TimeMark::Capture("preview.png".to_string()), - ); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(arc_scene); - #[cfg(not(feature = "app"))] - render_scene(arc_scene) -} diff --git a/examples/arc_between_points/README.md b/examples/arc_between_points/README.md deleted file mode 100644 index b2dd3d18..00000000 --- a/examples/arc_between_points/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ArcBetweenPoints Example - -This example demonstrates `ArcBetweenPoints`. - -https://github.com/user-attachments/assets/9595ce1d-2390-4d4d-958f-9299755460b7 - -> [arc_between_points.mp4](../../assets/arc_between_points.mp4) diff --git a/examples/arc_between_points/main.rs b/examples/arc_between_points/main.rs deleted file mode 100644 index dfa74061..00000000 --- a/examples/arc_between_points/main.rs +++ /dev/null @@ -1,76 +0,0 @@ -use itertools::Itertools; -use log::LevelFilter; -use ranim::{ - animation::{fading::FadingAnim, lagged::LaggedAnim}, - color::HueDirection, - glam::{DMat2, dvec2}, - items::{Group, vitem::geometry::ArcBetweenPoints}, - prelude::*, - timeline::TimeMark, -}; - -#[scene] -#[preview] -#[output(dir = "arc_between_points")] -fn arc_between_points(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - let center = dvec2(0.0, 0.0); - - let start_color = color!("#FF8080FF"); - let end_color = color!("#58C4DDFF"); - let ntan = 16; - let nrad = 5; - - let arcs = (0..nrad) - .map(|i| { - let radius = 6.0 * (i + 1) as f64 / nrad as f64; - let width = 0.12 * ((nrad - i) as f64 / nrad as f64).powi(2); - let angle = std::f64::consts::PI * 7.0 / 4.0 * (i + 1) as f64 / nrad as f64; - (radius, width, angle) - }) - .cartesian_product(0..ntan) - .map(|((rad, width, angle), j)| { - let color = start_color.lerp( - end_color, - j as f32 / (ntan - 1) as f32, - HueDirection::Increasing, - ); - let vec = DMat2::from_angle(std::f64::consts::PI * 2.0 / ntan as f64 * j as f64) - * dvec2(rad, 0.0); - ArcBetweenPoints::new(center.extend(0.0), (center + vec).extend(0.0), angle).with( - |arc| { - arc.stroke_width = width as f32; - arc.set_stroke_color(color); - }, - ) - }) - .collect::>(); - let r_arcs = r.insert(arcs); - - r.timeline_mut(&r_arcs) - .play_with(|arcs| arcs.lagged(0.2, |arc| arc.fade_in()).with_duration(3.0)); - - r.insert_time_mark( - r.timelines().max_total_secs(), - TimeMark::Capture("preview.png".to_string()), - ); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(arc_between_points_scene); - #[cfg(not(feature = "app"))] - render_scene(arc_between_points_scene) -} diff --git a/examples/basic/README.md b/examples/basic/README.md deleted file mode 100644 index b04745a2..00000000 --- a/examples/basic/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Basic Example - -This example demonstrates the basic usage of `ranim`. - -https://github.com/user-attachments/assets/dadab1ab-65e4-4cd3-bda4-b2ef97c61b0b - -> [basic.mp4](../../assets/basic.mp4) diff --git a/examples/hello_ranim/main.rs b/examples/hello_ranim/main.rs deleted file mode 100644 index 752dc3b1..00000000 --- a/examples/hello_ranim/main.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::f64::consts::PI; - -use log::LevelFilter; -use ranim::{ - animation::{creation::WritingAnim, fading::FadingAnim, transform::TransformAnim}, - color::palettes::manim, - glam::DVec3, - items::vitem::{ - VItem, - geometry::{Circle, Square}, - }, - prelude::*, - timeline::TimeMark, -}; - -#[scene] -#[preview] -#[output(dir = "hello_ranim")] -fn hello_ranim(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - let square = Square::new(2.0).with(|square| { - square.set_color(manim::BLUE_C); - }); - - let r_square = r.insert(square); - { - let timeline = r.timeline_mut(&r_square); - timeline.play_with(|square| square.fade_in()); - }; - - let circle = Circle::new(2.0).with(|circle| { - circle - .set_color(manim::RED_C) - .rotate(-PI / 4.0 + PI, DVec3::Z); - }); - - let r_vitem = r.map(r_square, VItem::from); - { - let timeline = r.timeline_mut(&r_vitem); - timeline.play_with(|state| state.transform_to(circle.into())); - timeline.forward(1.0); - let circle = timeline.state().clone(); - timeline.play_with(|circle| circle.unwrite()); - timeline.play(circle.write()); - timeline.play_with(|circle| circle.fade_out()); - }; - - r.insert_time_mark(3.7, TimeMark::Capture("preview.png".to_string())); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(hello_ranim_scene); - #[cfg(not(feature = "app"))] - render_scene(hello_ranim_scene) -} diff --git a/examples/palettes/README.md b/examples/palettes/README.md deleted file mode 100644 index 01fc6e93..00000000 --- a/examples/palettes/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Palettes - -Palettes from manim - -![palette](../../assets/palettes.png) diff --git a/examples/palettes/main.rs b/examples/palettes/main.rs deleted file mode 100644 index e71208fb..00000000 --- a/examples/palettes/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -use log::LevelFilter; -use ranim::{ - color::palettes::manim::*, - components::Anchor, - glam::{dvec2, dvec3}, - items::{Group, vitem::geometry::Rectangle}, - prelude::*, - timeline::TimeMark, -}; - -#[scene] -#[preview] -#[output(dir = "palettes")] -fn palettes(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); - let padded_frame_size = frame_size * 0.9; - - let colors = vec![ - vec![BLUE_E, BLUE_D, BLUE_C, BLUE_B, BLUE_A], - vec![TEAL_E, TEAL_D, TEAL_C, TEAL_B, TEAL_A], - vec![GREEN_E, GREEN_D, GREEN_C, GREEN_B, GREEN_A], - vec![YELLOW_E, YELLOW_D, YELLOW_C, YELLOW_B, YELLOW_A], - vec![GOLD_E, GOLD_D, GOLD_C, GOLD_B, GOLD_A], - vec![RED_E, RED_D, RED_C, RED_B, RED_A], - vec![MAROON_E, MAROON_D, MAROON_C, MAROON_B, MAROON_A], - vec![PURPLE_E, PURPLE_D, PURPLE_C, PURPLE_B, PURPLE_A], - vec![GREY_E, GREY_D, GREY_C, GREY_B, GREY_A], - vec![WHITE, BLACK, GREEN_SCREEN], - vec![GREY_BROWN, LIGHT_BROWN, PINK, LIGHT_PINK, ORANGE], - ]; - - let padded_frame_start = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); - let h_step = padded_frame_size.y / colors.len() as f64; - - let squares = colors - .iter() - .enumerate() - .flat_map(|(i, row)| { - let y = i as f64 * h_step; - let w_step = padded_frame_size.x / row.len() as f64; - row.iter().enumerate().map(move |(j, color)| { - let x = j as f64 * w_step; - Rectangle::new(w_step as f64, h_step as f64).with(|rect| { - rect.stroke_width = 0.0; - - rect.set_color(*color).put_anchor_on( - Anchor::edge(-1, -1, 0), - padded_frame_start.extend(0.0) + dvec3(x, y, 0.0), - ); - }) - }) - }) - .collect::>(); - r.insert_and_show(squares); - r.insert_time_mark(0.0, TimeMark::Capture("preview.png".to_string())); - r.timelines_mut().forward(0.01); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(palettes_scene); - #[cfg(not(feature = "app"))] - render_scene(palettes_scene); -} diff --git a/examples/ranim_logo/main.rs b/examples/ranim_logo/main.rs deleted file mode 100644 index 5d23ecc9..00000000 --- a/examples/ranim_logo/main.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::f64::consts::PI; - -use glam::{DVec3, dvec2, dvec3}; -use itertools::Itertools; -use log::LevelFilter; -use ranim::{ - animation::{creation::WritingAnim, lagged::LaggedAnim, transform::TransformAnim}, - color::palettes::manim, - components::{Anchor, ScaleHint}, - items::{ - Group, - vitem::{ - VItem, - geometry::{Polygon, Rectangle, Square}, - svg::SvgItem, - typst::typst_svg, - }, - }, - prelude::*, - timeline::TimeMark, - utils::rate_functions::{linear, smooth}, -}; - -fn build_logo(logo_width: f64) -> [VItem; 6] { - let red_bg_rect = Rectangle::new(logo_width / 2.0, logo_width).with(|rect| { - rect.set_color(manim::RED_C.with_alpha(0.5)) - .put_center_on(dvec3(-logo_width / 4.0, 0.0, 0.0)); - }); - let red_rect = Rectangle::new(logo_width / 4.0, logo_width).with(|rect| { - rect.set_color(manim::RED_C) - .put_anchor_on(Anchor::edge(1, 0, 0), dvec3(-logo_width / 4.0, 0.0, 0.0)); - }); - - let green_bg_sq = Square::new(logo_width / 2.0).with(|sq| { - sq.set_color(manim::GREEN_C.with_alpha(0.5)) - .put_center_on(dvec3(logo_width / 4.0, logo_width / 4.0, 0.0)); - }); - let green_triangle = Polygon::new(vec![ - dvec3(0.0, logo_width / 2.0, 0.0), - dvec3(logo_width / 2.0, logo_width / 2.0, 0.0), - dvec3(logo_width / 2.0, 0.0, 0.0), - ]) - .with(|tri| { - tri.set_color(manim::GREEN_C); - }); // ◥ - - let blue_bg_sq = Square::new(logo_width / 2.0).with(|sq| { - sq.set_color(manim::BLUE_C.with_alpha(0.5)) - .put_center_on(dvec3(logo_width / 4.0, -logo_width / 4.0, 0.0)); - }); - let blue_triangle = green_triangle.clone().with(|tri| { - tri.set_color(manim::BLUE_C) - .rotate(PI, DVec3::Z) - .shift(DVec3::NEG_Y * logo_width / 2.0); - }); // ◣ - - [ - VItem::from(red_bg_rect), - VItem::from(red_rect), - VItem::from(green_bg_sq), - VItem::from(green_triangle), - VItem::from(blue_bg_sq), - VItem::from(blue_triangle), - ] -} -#[scene] -#[preview] -#[output(dir = "ranim_logo")] -fn ranim_logo(r: &mut RanimScene) { - let _r_cam = r.insert_and_show(CameraFrame::default()); - let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); - let logo_width = frame_size.y * 0.618; - - let logo = build_logo(logo_width); - let r_logo = logo.map(|item| r.insert(item)); - - let ranim_text = Group::::from( - SvgItem::new(typst_svg( - r#" -#align(center)[ - #text(10pt, font: "LXGW Bright")[Ranim] -]"#, - )) - .with(|text| { - text.set_color(manim::WHITE) - .scale_to(ScaleHint::PorportionalY(1.0)) - .put_center_on(DVec3::NEG_Y * 2.5); - }), - ); - let r_ranim_text = r.insert(ranim_text); - - r_logo.iter().for_each(|item| { - r.timeline_mut(item) - .play_with(|item| item.write().with_duration(3.0).with_rate_func(smooth)); - }); - r.timelines_mut().sync(); - - let gap_ratio = 1.0 / 60.0; - let gap = logo_width * gap_ratio; - let scale = (logo_width - gap * 2.0) / logo_width; - let scale = [ - dvec3(scale, 1.0, 1.0), - dvec3(scale, scale, 1.0), - dvec3(scale, scale, 1.0), - ]; - let anchor = [ - Anchor::edge(-1, 0, 0), - Anchor::edge(1, 1, 0), - Anchor::edge(1, -1, 0), - ]; - r_logo - .iter() - .chunks(2) - .into_iter() - .zip(scale.into_iter().zip(anchor)) - .for_each(|(chunk, (scale, anchor))| { - let chunk = chunk.collect_array::<2>().unwrap(); - r.timeline_mut(&chunk).iter_mut().for_each(|timeline| { - timeline.play_with(|item| { - item.transform(|data| { - data.scale_by_anchor(scale, anchor) - .scale_by_anchor(dvec3(0.9, 0.9, 1.0), Anchor::ORIGIN) - .shift(dvec3(0.0, 1.3, 0.0)); - }) - .with_rate_func(smooth) - }); - }); - }); - r.timeline_mut(&r_ranim_text) - .forward(0.5) - .play_with(|text| { - text.lagged(0.2, |item| { - item.write().with_duration(2.0).with_rate_func(linear) - }) - .with_duration(2.0) - }); - r.timelines_mut().sync(); - - r.insert_time_mark( - r.timelines().max_total_secs(), - TimeMark::Capture("preview.png".to_string()), - ); - r.timelines_mut().forward(1.0); - - r_logo.iter().for_each(|r_logo_part| { - r.timeline_mut(r_logo_part) - .play_with(|item| item.unwrite().with_duration(3.0).with_rate_func(smooth)); - }); - r.timeline_mut(&r_ranim_text).play_with(|text| { - text.lagged(0.0, |item| { - item.unwrite().with_duration(3.0).with_rate_func(linear) - }) - }); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(ranim_logo_scene); - #[cfg(not(feature = "app"))] - render_scene(ranim_logo_scene); -} diff --git a/packages/ranim-examples/Cargo.toml b/packages/ranim-examples/Cargo.toml index 4de5c29c..5bd98121 100644 --- a/packages/ranim-examples/Cargo.toml +++ b/packages/ranim-examples/Cargo.toml @@ -17,6 +17,7 @@ crate-type = ["rlib", "cdylib"] # use cdylib to produce .wasm file [dependencies] ranim.workspace = true ranim-macros.workspace = true +itertools.workspace = true rand = "0.9.2" rand_chacha = "0.9.0" wasm-bindgen = "0.2.100" diff --git a/examples/hanoi/main.rs b/packages/ranim-examples/src/algo/hanoi.rs similarity index 81% rename from examples/hanoi/main.rs rename to packages/ranim-examples/src/algo/hanoi.rs index be2e0b2c..0fcc6ebb 100644 --- a/examples/hanoi/main.rs +++ b/packages/ranim-examples/src/algo/hanoi.rs @@ -1,4 +1,3 @@ -use log::LevelFilter; use ranim::{ animation::transform::TransformAnim, color::{HueDirection, palettes::manim}, @@ -10,7 +9,7 @@ use ranim::{ utils::rate_functions::{ease_in_quad, ease_out_quad, linear}, }; -fn solve_hanoi( +pub fn solve_hanoi( n: usize, idx_src: usize, idx_dst: usize, @@ -26,7 +25,7 @@ fn solve_hanoi( } } -fn hanoi(r: &mut RanimScene, n: usize) { +pub fn hanoi(r: &mut RanimScene, n: usize) { let _r_cam = r.insert_and_show(CameraFrame::default()); let total_sec = 10.0; let rod_width = 0.4; @@ -34,7 +33,6 @@ fn hanoi(r: &mut RanimScene, n: usize) { let rod_section_width = 4.0; let _rods = [-1, 0, 1] - .into_iter() .map(|i| { Rectangle::new(rod_width, rod_height).with(|rect| { rect.set_color(manim::GREY_C).put_anchor_on( @@ -43,8 +41,7 @@ fn hanoi(r: &mut RanimScene, n: usize) { ); }) }) - .map(|rect| r.insert_and_show(rect)) - .collect::>(); + .map(|rect| r.insert_and_show(rect)); let min_disk_width = rod_width * 1.7; let max_disk_width = rod_section_width * 0.8; @@ -111,37 +108,19 @@ fn hanoi(r: &mut RanimScene, n: usize) { } #[scene] +#[wasm_demo_doc] #[preview] #[output(dir = "hanoi")] -fn hanoi_5(r: &mut RanimScene) { +/// An example of solving the Tower of Hanoi problem with 5 disks. +pub fn hanoi_5(r: &mut RanimScene) { hanoi(r, 5); } -#[scene(name = "hanoi")] +#[scene] +#[wasm_demo_doc] #[preview] #[output(dir = "hanoi")] -fn hanoi_10(r: &mut RanimScene) { +/// An example of solving the Tower of Hanoi problem with 10 disks. +pub fn hanoi_10(r: &mut RanimScene) { hanoi(r, 10); } - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(hanoi_10_scene); - #[cfg(not(feature = "app"))] - { - render_scene(hanoi_5_scene); - render_scene(hanoi_10_scene); - } -} diff --git a/packages/ranim-examples/src/sort.rs b/packages/ranim-examples/src/algo/sort.rs similarity index 93% rename from packages/ranim-examples/src/sort.rs rename to packages/ranim-examples/src/algo/sort.rs index de7183b4..493f9972 100644 --- a/packages/ranim-examples/src/sort.rs +++ b/packages/ranim-examples/src/algo/sort.rs @@ -103,6 +103,7 @@ pub fn bubble_sort(r: &mut RanimScene, num: usize) { } #[scene] +#[wasm_demo_doc] #[preview] #[output(dir = "bubble_sort")] /// A bubble sort ranim example with input of 10. @@ -119,24 +120,6 @@ pub fn bubble_sort_100(r: &mut RanimScene) { bubble_sort(r, 100); } -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_bubble_sort_10() { - run_scene_app( - bubble_sort_10_scene.constructor, - bubble_sort_10_scene.name.to_string(), - ); -} - -#[cfg(any(feature = "app", target_arch = "wasm32"))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub fn run_bubble_sort_100() { - run_scene_app( - bubble_sort_100_scene.constructor, - bubble_sort_100_scene.name.to_string(), - ); -} - // MARK: selective_sort pub fn selective_sort(r: &mut RanimScene, num: usize) { @@ -239,4 +222,3 @@ pub fn selective_sort_10(r: &mut RanimScene) { pub fn selective_sort_100(r: &mut RanimScene) { selective_sort(r, 100); } - diff --git a/examples/perspective_blend/main.rs b/packages/ranim-examples/src/camera.rs similarity index 83% rename from examples/perspective_blend/main.rs rename to packages/ranim-examples/src/camera.rs index 4095ec20..7699148d 100644 --- a/examples/perspective_blend/main.rs +++ b/packages/ranim-examples/src/camera.rs @@ -1,4 +1,3 @@ -use log::LevelFilter; use ranim::{ animation::transform::TransformAnim, color::palettes::manim, @@ -13,8 +12,10 @@ use ranim::{ }; #[scene] +#[wasm_demo_doc] #[preview] #[output(dir = "perspective_blend")] +/// An example of perspective blend. fn perspective_blend(r: &mut RanimScene) { let r_cam = r.insert_and_show(CameraFrame::default()); r.timeline_mut(&r_cam).update_with(|cam| { @@ -101,23 +102,3 @@ fn perspective_blend(r: &mut RanimScene) { TimeMark::Capture("preview.png".to_string()), ); } - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(not(feature = "app"))] - render_scene(perspective_blend_scene); - - #[cfg(feature = "app")] - preview(perspective_blend_scene); -} diff --git a/packages/ranim-examples/src/lib.rs b/packages/ranim-examples/src/lib.rs index 73fa2b80..03bbdedb 100644 --- a/packages/ranim-examples/src/lib.rs +++ b/packages/ranim-examples/src/lib.rs @@ -1,25 +1,46 @@ //! This crate contains all ranim examples use std::f64::consts::PI; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +use itertools::Itertools; use ranim::{ - animation::{creation::WritingAnim, fading::FadingAnim, transform::TransformAnim}, + animation::{ + creation::WritingAnim, fading::FadingAnim, lagged::LaggedAnim, transform::TransformAnim, + }, color::palettes::manim, - glam::DVec3, - items::vitem::{ - VItem, - geometry::{Circle, Square}, + components::{Anchor, ScaleHint}, + glam::{DVec3, dvec2, dvec3}, + items::{ + Group, + vitem::{ + VItem, + geometry::{Circle, Polygon, Rectangle, Square}, + svg::SvgItem, + typst::typst_svg, + }, }, prelude::*, + timeline::TimeMark, + utils::rate_functions::{linear, smooth}, }; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; pub mod tutorial { + pub mod extract; pub mod getting_started; } -pub mod sort; +pub mod vitem { + pub mod geometry; + pub mod svg; +} +pub mod algo { + pub mod hanoi; + pub mod sort; +} +pub mod camera; +// MARK: hello_ranim #[scene] #[preview] #[wasm_demo_doc] @@ -53,4 +74,195 @@ pub fn hello_ranim(r: &mut RanimScene) { timeline.play(circle.write()); timeline.play_with(|circle| circle.fade_out()); }; + + r.insert_time_mark(3.7, TimeMark::Capture("preview.png".to_string())); +} + +// MARK: ranim_logo +pub fn build_logo(logo_width: f64) -> [VItem; 6] { + let red_bg_rect = Rectangle::new(logo_width / 2.0, logo_width).with(|rect| { + rect.set_color(manim::RED_C.with_alpha(0.5)) + .put_center_on(dvec3(-logo_width / 4.0, 0.0, 0.0)); + }); + let red_rect = Rectangle::new(logo_width / 4.0, logo_width).with(|rect| { + rect.set_color(manim::RED_C) + .put_anchor_on(Anchor::edge(1, 0, 0), dvec3(-logo_width / 4.0, 0.0, 0.0)); + }); + + let green_bg_sq = Square::new(logo_width / 2.0).with(|sq| { + sq.set_color(manim::GREEN_C.with_alpha(0.5)) + .put_center_on(dvec3(logo_width / 4.0, logo_width / 4.0, 0.0)); + }); + let green_triangle = Polygon::new(vec![ + dvec3(0.0, logo_width / 2.0, 0.0), + dvec3(logo_width / 2.0, logo_width / 2.0, 0.0), + dvec3(logo_width / 2.0, 0.0, 0.0), + ]) + .with(|tri| { + tri.set_color(manim::GREEN_C); + }); // ◥ + + let blue_bg_sq = Square::new(logo_width / 2.0).with(|sq| { + sq.set_color(manim::BLUE_C.with_alpha(0.5)) + .put_center_on(dvec3(logo_width / 4.0, -logo_width / 4.0, 0.0)); + }); + let blue_triangle = green_triangle.clone().with(|tri| { + tri.set_color(manim::BLUE_C) + .rotate(PI, DVec3::Z) + .shift(DVec3::NEG_Y * logo_width / 2.0); + }); // ◣ + + [ + VItem::from(red_bg_rect), + VItem::from(red_rect), + VItem::from(green_bg_sq), + VItem::from(green_triangle), + VItem::from(blue_bg_sq), + VItem::from(blue_triangle), + ] +} + +#[scene] +#[wasm_demo_doc] +#[preview] +#[output(dir = "ranim_logo")] +/// A scene shows the logo of ranim +pub fn ranim_logo(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); + let logo_width = frame_size.y * 0.618; + + let logo = build_logo(logo_width); + let r_logo = logo.map(|item| r.insert(item)); + + let ranim_text = Group::::from( + SvgItem::new(typst_svg( + r#" +#align(center)[ + #text(10pt, font: "LXGW Bright")[Ranim] +]"#, + )) + .with(|text| { + text.set_color(manim::WHITE) + .scale_to(ScaleHint::PorportionalY(1.0)) + .put_center_on(DVec3::NEG_Y * 2.5); + }), + ); + let r_ranim_text = r.insert(ranim_text); + + r_logo.iter().for_each(|item| { + r.timeline_mut(item) + .play_with(|item| item.write().with_duration(3.0).with_rate_func(smooth)); + }); + r.timelines_mut().sync(); + + let gap_ratio = 1.0 / 60.0; + let gap = logo_width * gap_ratio; + let scale = (logo_width - gap * 2.0) / logo_width; + let scale = [ + dvec3(scale, 1.0, 1.0), + dvec3(scale, scale, 1.0), + dvec3(scale, scale, 1.0), + ]; + let anchor = [ + Anchor::edge(-1, 0, 0), + Anchor::edge(1, 1, 0), + Anchor::edge(1, -1, 0), + ]; + r_logo + .iter() + .chunks(2) + .into_iter() + .zip(scale.into_iter().zip(anchor)) + .for_each(|(chunk, (scale, anchor))| { + let chunk = chunk.collect_array::<2>().unwrap(); + r.timeline_mut(&chunk).iter_mut().for_each(|timeline| { + timeline.play_with(|item| { + item.transform(|data| { + data.scale_by_anchor(scale, anchor) + .scale_by_anchor(dvec3(0.9, 0.9, 1.0), Anchor::ORIGIN) + .shift(dvec3(0.0, 1.3, 0.0)); + }) + .with_rate_func(smooth) + }); + }); + }); + r.timeline_mut(&r_ranim_text) + .forward(0.5) + .play_with(|text| { + text.lagged(0.2, |item| { + item.write().with_duration(2.0).with_rate_func(linear) + }) + .with_duration(2.0) + }); + r.timelines_mut().sync(); + + r.insert_time_mark( + r.timelines().max_total_secs(), + TimeMark::Capture("preview.png".to_string()), + ); + r.timelines_mut().forward(1.0); + + r_logo.iter().for_each(|r_logo_part| { + r.timeline_mut(r_logo_part) + .play_with(|item| item.unwrite().with_duration(3.0).with_rate_func(smooth)); + }); + r.timeline_mut(&r_ranim_text).play_with(|text| { + text.lagged(0.0, |item| { + item.unwrite().with_duration(3.0).with_rate_func(linear) + }) + }); +} + +// MARK: palettes +#[scene] +#[wasm_demo_doc] +#[preview] +#[output(dir = "palettes")] +/// An example shows manim's palettes +pub fn palettes(r: &mut RanimScene) { + use ranim::color::palettes::manim::*; + let _r_cam = r.insert_and_show(CameraFrame::default()); + let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); + let padded_frame_size = frame_size * 0.9; + + let colors = vec![ + vec![BLUE_E, BLUE_D, BLUE_C, BLUE_B, BLUE_A], + vec![TEAL_E, TEAL_D, TEAL_C, TEAL_B, TEAL_A], + vec![GREEN_E, GREEN_D, GREEN_C, GREEN_B, GREEN_A], + vec![YELLOW_E, YELLOW_D, YELLOW_C, YELLOW_B, YELLOW_A], + vec![GOLD_E, GOLD_D, GOLD_C, GOLD_B, GOLD_A], + vec![RED_E, RED_D, RED_C, RED_B, RED_A], + vec![MAROON_E, MAROON_D, MAROON_C, MAROON_B, MAROON_A], + vec![PURPLE_E, PURPLE_D, PURPLE_C, PURPLE_B, PURPLE_A], + vec![GREY_E, GREY_D, GREY_C, GREY_B, GREY_A], + vec![WHITE, BLACK, GREEN_SCREEN], + vec![GREY_BROWN, LIGHT_BROWN, PINK, LIGHT_PINK, ORANGE], + ]; + + let padded_frame_start = dvec2(padded_frame_size.x / -2.0, padded_frame_size.y / -2.0); + let h_step = padded_frame_size.y / colors.len() as f64; + + let squares = colors + .iter() + .enumerate() + .flat_map(|(i, row)| { + let y = i as f64 * h_step; + let w_step = padded_frame_size.x / row.len() as f64; + row.iter().enumerate().map(move |(j, color)| { + let x = j as f64 * w_step; + Rectangle::new(w_step as f64, h_step as f64).with(|rect| { + rect.stroke_width = 0.0; + + rect.set_color(*color).put_anchor_on( + Anchor::edge(-1, -1, 0), + padded_frame_start.extend(0.0) + dvec3(x, y, 0.0), + ); + }) + }) + }) + .collect::>(); + r.insert_and_show(squares); + r.insert_time_mark(0.0, TimeMark::Capture("preview.png".to_string())); + r.timelines_mut().forward(0.01); } diff --git a/examples/extract_vitem_visualize/main.rs b/packages/ranim-examples/src/tutorial/extract.rs similarity index 87% rename from examples/extract_vitem_visualize/main.rs rename to packages/ranim-examples/src/tutorial/extract.rs index d75a581d..f562754e 100644 --- a/examples/extract_vitem_visualize/main.rs +++ b/packages/ranim-examples/src/tutorial/extract.rs @@ -1,10 +1,10 @@ -use log::LevelFilter; use std::f64::consts::PI; use ranim::{ animation::{creation::WritingAnim, fading::FadingAnim, transform::TransformAnim}, color::palettes::manim, components::ScaleHint, + glam::DVec3, items::{ Group, vitem::{ @@ -19,9 +19,9 @@ use ranim::{ timeline::TimeMark, }; -use glam::DVec3; - #[derive(Clone)] +/// A custom item that extract to a vec of [`VItemPrimitive`] that visualizes +/// the underlying points of a vitem pub struct VisualVItem(VItem); impl Interpolatable for VisualVItem { @@ -173,9 +173,10 @@ impl Empty for VisualVItem { // MARK: ranim_text #[scene] +#[wasm_demo_doc] #[preview] -#[output(dir = "extract_vitem_visualize")] -fn ranim_text(r: &mut RanimScene) { +#[output(dir = "ranim_text_extract_vitem_visualize")] +fn ranim_text_extract_vitem_visualize(r: &mut RanimScene) { let r_cam = r.insert_and_show(CameraFrame::default()); let text = SvgItem::new(typst_svg("Ranim")).with(|item| { @@ -210,13 +211,15 @@ fn ranim_text(r: &mut RanimScene) { .play_with(|cam| cam.transform_to(default_cam)); // r.timelines_mut().forward(1.0); - r.insert_time_mark(5.0, TimeMark::Capture("preview-ranim_text.png".to_string())); + r.insert_time_mark(5.0, TimeMark::Capture("preview.png".to_string())); } -#[scene(name = "extract_vitem_visualize")] +#[scene] +#[wasm_demo_doc] #[preview] -#[output(dir = "extract_vitem_visualize")] -pub fn hello_ranim(r: &mut RanimScene) { +#[output(dir = "hello_ranim_extract_vitem_visualize")] +/// [`crate::hello_ranim`] with [`VisualVItem`] +pub fn hello_ranim_extract_vitem_visualize(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); let square = VisualVItem(VItem::from(Square::new(2.0).with(|square| { @@ -243,30 +246,5 @@ pub fn hello_ranim(r: &mut RanimScene) { } r.timelines_mut().sync(); - r.insert_time_mark( - 3.2, - TimeMark::Capture("preview-hello_ranim.png".to_string()), - ); -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(hello_ranim_scene); - #[cfg(not(feature = "app"))] - { - render_scene(hello_ranim_scene); - render_scene(ranim_text_scene); - } + r.insert_time_mark(3.2, TimeMark::Capture("preview.png".to_string())); } diff --git a/packages/ranim-examples/src/tutorial/getting_started.rs b/packages/ranim-examples/src/tutorial/getting_started.rs index a53eeadd..6f27826f 100644 --- a/packages/ranim-examples/src/tutorial/getting_started.rs +++ b/packages/ranim-examples/src/tutorial/getting_started.rs @@ -19,7 +19,7 @@ use wasm_bindgen::prelude::*; #[scene] #[wasm_demo_doc] #[preview] -#[output] +#[output(dir = "getting_started0")] /// This example shows the basic api of [`RanimScene`]. pub fn getting_started0(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -45,7 +45,7 @@ pub fn getting_started0(r: &mut RanimScene) { #[scene] #[wasm_demo_doc] #[preview] -#[output] +#[output(dir = "getting_started1")] /// This example shows the basic api of [`RanimScene`]. pub fn getting_started1(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); @@ -71,7 +71,7 @@ pub fn getting_started1(r: &mut RanimScene) { #[scene] #[wasm_demo_doc] #[preview] -#[output] +#[output(dir = "getting_started2")] /// This example shows the basic api of [`RanimScene`]. fn getting_started2(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); diff --git a/packages/ranim-examples/src/vitem/geometry.rs b/packages/ranim-examples/src/vitem/geometry.rs new file mode 100644 index 00000000..265b8eae --- /dev/null +++ b/packages/ranim-examples/src/vitem/geometry.rs @@ -0,0 +1,112 @@ +use itertools::Itertools; +use ranim::{ + animation::{fading::FadingAnim, lagged::LaggedAnim}, + color::HueDirection, + glam::{DMat2, dvec2}, + items::{ + Group, + vitem::geometry::{Arc, ArcBetweenPoints}, + }, + prelude::*, + timeline::TimeMark, +}; + +#[scene] +#[wasm_demo_doc] +#[preview] +#[output(dir = "arc")] +/// A scene that shows how to use [`Arc`] item. +pub fn arc(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + + // let frame_size = app.camera().size; + let frame_size = dvec2(8.0 * 16.0 / 9.0, 8.0); + let frame_start = dvec2(frame_size.x / -2.0, frame_size.y / -2.0); + + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); + + let nrow = 10; + let ncol = 10; + let step_x = frame_size.x / ncol as f64; + let step_y = frame_size.y / nrow as f64; + + let arcs = (0..nrow) + .cartesian_product(0..ncol) + .map(|(i, j)| { + let (i, j) = (i as f64, j as f64); + + let angle = std::f64::consts::PI * (j + 1.0) / ncol as f64 * 360.0 / 180.0; + let radius = step_y / 2.0 * 0.8; + let color = start_color.lerp( + end_color, + i as f32 / (nrow - 1) as f32, + HueDirection::Increasing, + ); + let offset = frame_start + dvec2(j * step_x + step_x / 2.0, i * step_y + step_y / 2.0); + Arc::new(angle, radius).with(|arc| { + arc.stroke_width = 0.12 * (j as f32 + 0.02) / ncol as f32; + arc.set_stroke_color(color) + .put_center_on(offset.extend(0.0)); + }) + }) + .collect::>(); + let r_arcs = r.insert(arcs); + + r.timeline_mut(&r_arcs) + .play_with(|arcs| arcs.lagged(0.2, |arc| arc.fade_in()).with_duration(3.0)); + + r.insert_time_mark( + r.timelines().max_total_secs(), + TimeMark::Capture("preview.png".to_string()), + ); +} + +#[scene] +#[wasm_demo_doc] +#[preview] +#[output(dir = "arc_between_points")] +/// A scene that shows how to use [`ArcBetweenPoints`] item. +pub fn arc_between_points(r: &mut RanimScene) { + let _r_cam = r.insert_and_show(CameraFrame::default()); + let center = dvec2(0.0, 0.0); + + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); + let ntan = 16; + let nrad = 5; + + let arcs = (0..nrad) + .map(|i| { + let radius = 6.0 * (i + 1) as f64 / nrad as f64; + let width = 0.12 * ((nrad - i) as f64 / nrad as f64).powi(2); + let angle = std::f64::consts::PI * 7.0 / 4.0 * (i + 1) as f64 / nrad as f64; + (radius, width, angle) + }) + .cartesian_product(0..ntan) + .map(|((rad, width, angle), j)| { + let color = start_color.lerp( + end_color, + j as f32 / (ntan - 1) as f32, + HueDirection::Increasing, + ); + let vec = DMat2::from_angle(std::f64::consts::PI * 2.0 / ntan as f64 * j as f64) + * dvec2(rad, 0.0); + ArcBetweenPoints::new(center.extend(0.0), (center + vec).extend(0.0), angle).with( + |arc| { + arc.stroke_width = width as f32; + arc.set_stroke_color(color); + }, + ) + }) + .collect::>(); + let r_arcs = r.insert(arcs); + + r.timeline_mut(&r_arcs) + .play_with(|arcs| arcs.lagged(0.2, |arc| arc.fade_in()).with_duration(3.0)); + + r.insert_time_mark( + r.timelines().max_total_secs(), + TimeMark::Capture("preview.png".to_string()), + ); +} diff --git a/examples/basic/main.rs b/packages/ranim-examples/src/vitem/svg.rs similarity index 70% rename from examples/basic/main.rs rename to packages/ranim-examples/src/vitem/svg.rs index 945d8730..1fc7929f 100644 --- a/examples/basic/main.rs +++ b/packages/ranim-examples/src/vitem/svg.rs @@ -1,9 +1,8 @@ -use glam::DVec3; -use log::LevelFilter; use ranim::{ animation::{creation::WritingAnim, fading::FadingAnim, lagged::LaggedAnim}, color::palettes::manim, components::ScaleHint, + glam::DVec3, items::{ Group, vitem::{VItem, svg::SvgItem, typst::typst_svg}, @@ -12,12 +11,14 @@ use ranim::{ timeline::TimeMark, }; -const SVG: &str = include_str!("../../assets/Ghostscript_Tiger.svg"); +const SVG: &str = include_str!("../../../../assets/Ghostscript_Tiger.svg"); #[scene] +#[wasm_demo_doc] #[preview] #[output(dir = "basic")] -fn basic(r: &mut RanimScene) { +/// An example shows [`SvgItem`] +fn svg_item(r: &mut RanimScene) { let _r_cam = r.insert_and_show(CameraFrame::default()); r.timelines_mut().forward(0.2); @@ -56,22 +57,3 @@ fn basic(r: &mut RanimScene) { TimeMark::Capture("preview.png".to_string()), ); } - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - #[cfg(debug_assertions)] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Trace) - .init(); - #[cfg(not(debug_assertions))] - pretty_env_logger::formatted_timed_builder() - .filter(Some("ranim"), LevelFilter::Info) - .init(); - } - - #[cfg(feature = "app")] - preview(basic_scene); - #[cfg(not(feature = "app"))] - render_scene(basic_scene) -} diff --git a/packages/ranim-macros/src/lib.rs b/packages/ranim-macros/src/lib.rs index 77f7affd..ba6a485d 100644 --- a/packages/ranim-macros/src/lib.rs +++ b/packages/ranim-macros/src/lib.rs @@ -61,6 +61,13 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { .filter(|attr| attr.path().is_ident("doc")) .collect(); + let doc_head = doc_attrs.first(); + let doc_attrs = if doc_attrs.len() > 1 { + &doc_attrs[1..] + } else { + &[] + }; + // 场景名称 let scene_name = attrs.name.unwrap_or_else(|| fn_name.to_string()); @@ -99,17 +106,27 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { } let preview = attrs.preview; - let doc = if attrs.wasm_demo_doc { - quote! { - #[doc = concat!("")] - #[doc = concat!(""] - } - } else { - quote! {} + let mut doc = quote! { + #(#doc_attrs)* }; + if attrs.wasm_demo_doc { + doc = quote! { + #[doc = concat!(r#"
"#)] + #[doc = concat!(r#" "#)] + #[doc = concat!(r#" "#)] + #[doc = concat!(r#"
"#)] + #doc + }; + } + if let Some(doc_head) = doc_head { + doc = quote! { + #doc_head + #doc + }; + } let static_output_name = syn::Ident::new( &format!("__SCENE_{}_OUTPUTS", fn_name.to_string().to_uppercase()), @@ -136,7 +153,6 @@ pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream { // 构造 Scene 并塞进分布式切片 let expanded = quote! { #doc - #(#doc_attrs)* #vis fn #fn_name(r: &mut #ranim::timeline::RanimScene) #fn_body #[doc(hidden)] diff --git a/src/color/mod.rs b/src/color/mod.rs index f5277dfb..a3c9ba9a 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -1,8 +1,7 @@ -pub use color::{AlphaColor, OpaqueColor, Srgb}; +pub use ::color::*; /// palettes pub mod palettes; -pub use ::color::HueDirection; /// Color preludes pub mod prelude { @@ -36,7 +35,7 @@ pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> AlphaColor { #[macro_export] macro_rules! color { ($color_str:expr) => {{ - use ::color::{Srgb, parse_color}; + use ::ranim::color::{Srgb, parse_color}; parse_color($color_str) .expect("Invalid color string") .to_alpha_color::() diff --git a/src/lib.rs b/src/lib.rs index ebe3a4f2..346778e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,7 +203,7 @@ pub mod prelude { #[cfg(not(target_arch = "wasm32"))] pub use crate::{render, render_scene, render_scene_output}; - pub use ranim_macros::{output, preview, wasm_demo_doc, scene}; + pub use ranim_macros::{output, preview, scene, wasm_demo_doc}; pub use crate::items::{ItemId, camera_frame::CameraFrame}; pub use crate::timeline::{RanimScene, TimelineFunc, TimelinesFunc}; From 1125541f312f5951c8d791b931b2f4bd3f53597c Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Sun, 14 Sep 2025 09:12:06 +0800 Subject: [PATCH 6/6] add dylib crate-type for ranim lib, added measureme to flake.nix --- Cargo.toml | 4 ++++ flake.lock | 12 ++++++------ flake.nix | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3809b271..6a3655f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ readme = "README.md" keywords = ["animation", "manim", "wgpu"] exclude = ["website", "assets", "book"] +# Use RUSTFLAGS="-C prefer-dynamic" reduces link time from 500ms to 100ms +[lib] +crate-type = ["rlib", "dylib"] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/flake.lock b/flake.lock index f519d8d7..3c7706af 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755615617, - "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", + "lastModified": 1757487488, + "narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "20075955deac2583bb12f07151c2df830ef346b4", + "rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1755657401, - "narHash": "sha256-rPHuWPAcwW63wH1SUtDCqAnf2+60pi/pGMCIhVobzXc=", + "lastModified": 1757730403, + "narHash": "sha256-Jxl4OZRVsXs8JxEHUVQn3oPu6zcqFyGGKaFrlNgbzp0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "292ca754b0f679b842fbfc4734f017c351f0e9eb", + "rev": "3232f7f8bd07849fc6f4ae77fe695e0abb2eba2c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 48e1bd75..f6607649 100644 --- a/flake.nix +++ b/flake.nix @@ -61,6 +61,7 @@ wasm-pack wasm-bindgen-cli mdbook-i18n-helpers + measureme ]); }; });