diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 10635b69..9caf1c27 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -16,7 +16,22 @@ iconography, images or other Adobe assets; open licences only; every asset attri | `assets/fonts/Inter-Italic.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | | `assets/fonts/NotoSerif-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/latin-greek-cyrillic | OFL-1.1 (`assets/fonts/OFL-NotoSerif.txt`) | | `assets/fonts/JetBrainsMono-Regular.ttf` | The JetBrains Mono Project Authors | https://github.com/JetBrains/JetBrainsMono | OFL-1.1 (`assets/fonts/OFL-JetBrainsMono.txt`) | -| `apps/effectcraft-web/web/favicon.svg` | EffectCraft contributors | Original work: web app icon, the code-drawn EffectCraft mark (split rounded square, keyframe diamond) as SVG | MIT OR Apache-2.0 | +| `apps/effectcraft-web/web/favicon.svg` | EffectCraft contributors | Original work: web app icon, a copy of `assets/app-icon/effectcraft-small.svg` (the unicorn app icon) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft-1024.png` | EffectCraft contributors | Original work: effectCraft app icon, 1024 px (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft-macos-512.png` | EffectCraft contributors | Original work: effectCraft runtime Dock icon (macOS) (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft-small.svg` | EffectCraft contributors | Original work: effectCraft app icon, light vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft.icns` | EffectCraft contributors | Original work: effectCraft macOS app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft.ico` | EffectCraft contributors | Original work: effectCraft Windows app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, master vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 128x128 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 16x16 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 24x24 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 256x256 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 32x32 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 48x48 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 512x512 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 64x64 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor scalable (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | | `crates/lottie/tests/fixtures/bouncing-ball.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (bouncing ball, eased position keys) | MIT OR Apache-2.0 | | `crates/lottie/tests/fixtures/shapes-gradient.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (star, rectangle, gradient fill, dashed stroke, trim paths) | MIT OR Apache-2.0 | | `crates/lottie/tests/fixtures/precomp-matte-text.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (precomp, track matte, parented text) | MIT OR Apache-2.0 | diff --git a/Cargo.lock b/Cargo.lock index 68e5616f..6f31e68d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,6 +1137,7 @@ dependencies = [ "rfd", "serde_json", "winit", + "winresource", ] [[package]] @@ -4687,10 +4688,12 @@ version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ + "indexmap", "serde_core", "serde_spanned", "toml_datetime", "toml_parser", + "toml_writer", "winnow", ] @@ -4724,6 +4727,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tracing" version = "0.1.44" @@ -5763,6 +5772,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/README.md b/README.md index d4ccd4ac..00ffa3b1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@

+

+ EffectCraft app icon: an engraved unicorn on EffectCraft pink (#e0368f) +

+

EffectCraft

diff --git a/apps/effectcraft-web/web/favicon.svg b/apps/effectcraft-web/web/favicon.svg index 0b4f3c8f..150ac8f9 100644 --- a/apps/effectcraft-web/web/favicon.svg +++ b/apps/effectcraft-web/web/favicon.svg @@ -1,8 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/apps/effectcraft-web/web/favicon.svg.attribution b/apps/effectcraft-web/web/favicon.svg.attribution index e5f8b025..e723699a 100644 --- a/apps/effectcraft-web/web/favicon.svg.attribution +++ b/apps/effectcraft-web/web/favicon.svg.attribution @@ -1,9 +1,8 @@ asset: favicon.svg title: EffectCraft web app icon (favicon) -author: EffectCraft contributors +author: EffectCraft contributors (drawing by the project owner in ArtCraft) source: original work license: MIT OR Apache-2.0 -added: 2026-10-01 by EffectCraft contributors (Claude agent, M15 web app) -notes: Hand-written SVG of the code-drawn EffectCraft mark (`paint_logo` in - crates/ui-egui/src/header.rs): a navy rounded square split into blue and violet halves - with a white keyframe diamond, from basic shapes. Not derived from any other product's icon. +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Copy of assets/app-icon/effectcraft-small.svg (the lighter vector trace of the app icon). Replaces the earlier code-drawn split-square mark. diff --git a/apps/effectcraft/Cargo.toml b/apps/effectcraft/Cargo.toml index 5a2165aa..6b34763f 100644 --- a/apps/effectcraft/Cargo.toml +++ b/apps/effectcraft/Cargo.toml @@ -19,6 +19,10 @@ serde_json = { workspace = true } rfd = { workspace = true } cpal = { workspace = true } +[build-dependencies] +# Icon + VERSIONINFO for effectcraft.exe; build.rs only uses it when targeting Windows. +winresource = "0.1" + [target.'cfg(target_os = "macos")'.dependencies] # Same winit as eframe: macOS event-loop options (no app activation when driven by an agent). winit = { version = "0.30", default-features = false } diff --git a/apps/effectcraft/build.rs b/apps/effectcraft/build.rs new file mode 100644 index 00000000..50eb5f34 --- /dev/null +++ b/apps/effectcraft/build.rs @@ -0,0 +1,27 @@ +//! Windows only: embed the app icon and version info (VERSIONINFO) into `effectcraft.exe`. +//! +//! On every other target this does nothing. A missing resource compiler is a warning, so a +//! cross-compile from macOS or Linux still links, unless `EFFECTCRAFT_REQUIRE_WINRES=1` turns it +//! into an error (for release builds). + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=../../assets/app-icon/effectcraft.ico"); + println!("cargo:rerun-if-env-changed=EFFECTCRAFT_REQUIRE_WINRES"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + let mut res = winresource::WindowsResource::new(); + res.set_icon("../../assets/app-icon/effectcraft.ico") + .set("ProductName", "EffectCraft") + .set("FileDescription", "EffectCraft motion graphics and visual effects") + .set("LegalCopyright", "Copyright (c) 2026 The EffectCraft contributors. MIT OR Apache-2.0.") + .set("OriginalFilename", "effectcraft.exe") + .set("InternalName", "effectcraft"); + if let Err(e) = res.compile() { + if std::env::var_os("EFFECTCRAFT_REQUIRE_WINRES").is_some() { + panic!("embedding Windows resources failed: {e}"); + } + println!("cargo:warning=effectcraft.exe built without icon/version resources: {e}"); + } +} diff --git a/apps/effectcraft/src/main.rs b/apps/effectcraft/src/main.rs index 70cbd933..34b40a84 100644 --- a/apps/effectcraft/src/main.rs +++ b/apps/effectcraft/src/main.rs @@ -11,6 +11,16 @@ mod control_server; use effectcraft_ui_egui::EffectcraftApp; use serde_json::json; +/// The app icon for the window, taskbar and Dock (assets/app-icon, regenerated by +/// `packaging/icons.sh`). macOS gets the padded variant so it sits on Apple's icon grid in the Dock. +fn app_icon() -> egui::IconData { + #[cfg(target_os = "macos")] + let png: &[u8] = include_bytes!("../../../assets/app-icon/effectcraft-macos-512.png"); + #[cfg(not(target_os = "macos"))] + let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png"); + eframe::icon_data::from_png_bytes(png).unwrap_or_default() +} + fn main() -> eframe::Result { let mut control_port: Option = std::env::var("EFFECTCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok()); let mut files = Vec::new(); @@ -37,7 +47,10 @@ fn main() -> eframe::Result { .with_min_inner_size([960.0, 600.0]) .with_drag_and_drop(true) // Started for an agent (`--control`): open without taking the user's keyboard focus. - .with_active(control_port.is_none()), + .with_active(control_port.is_none()) + .with_icon(app_icon()) + // Wayland matches the window to `ai.storyteller.effectcraft.desktop` by this ID. + .with_app_id("ai.storyteller.effectcraft"), event_loop_builder: agent_event_loop(control_port.is_some()), ..Default::default() }; diff --git a/assets/app-icon/LICENSE.txt b/assets/app-icon/LICENSE.txt new file mode 100644 index 00000000..5fdd611e --- /dev/null +++ b/assets/app-icon/LICENSE.txt @@ -0,0 +1,13 @@ +EffectCraft app icon (effectcraft.svg, effectcraft-small.svg and every file derived from them in +this directory: effectcraft-1024.png, effectcraft-macos-512.png, effectcraft.icns, effectcraft.ico, +hicolor/), and the web favicon apps/effectcraft-web/web/favicon.svg. + +Original artwork made for this project by the project owner in ArtCraft, then vectorised (no +third-party material). Licensed like the rest of EffectCraft, under either of + + * Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * MIT license (https://opensource.org/licenses/MIT) + +at your option. + +Regenerate the derived files with packaging/icons.sh. diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md new file mode 100644 index 00000000..814ad601 --- /dev/null +++ b/assets/app-icon/README.md @@ -0,0 +1,42 @@ +# EffectCraft app icon + +**Creature:** a unicorn, as an engraved head-and-shoulders bust looking out of the tile. + +**Style:** an ink engraving portrait in the Crafting Apps' owl-template framing (the FilmCraft owl): +full-bleed colour field, tight head-and-shoulders crop, the body running off the bottom and right edges. + +**Tile:** 512 × 512 viewBox, rounded square `rx=112`, clipped; no border or roundel. + +**Palette** (exactly three colours): + +| Colour | Hex | Used for | +|---|---|---| +| App colour (EffectCraft pink) | `#e0368f` | the full-bleed field | +| Paper | `#efe9dc` | the figure | +| Ink | `#0b0b0c` | line work and contour | + +**Provenance:** the project owner drew it in ArtCraft (a 2880 px engraving-style image, kept in +craftrules at `assets/app-icons/effectcraft/source.png`). It was vectorised with craftrules +`assets/logo-options/_tools/vectorize_tile.py`, which keys out the field and traces the ink with potrace. +Licence: see [LICENSE.txt](LICENSE.txt) (MIT OR Apache-2.0, like the repo). + +## Files + +| File | What | +|---|---| +| `effectcraft.svg` | master vector, traced at 2048 px (canonical) | +| `effectcraft-small.svg` | lighter vector (~350 KB), traced at 1024 px; also the web favicon | +| `effectcraft-1024.png` | 1024 px render | +| `effectcraft-macos-512.png` | runtime Dock icon on macOS, on Apple's 824/1024 grid | +| `effectcraft.icns` | macOS icon (Apple grid) | +| `effectcraft.ico` | Windows icon, 16–256 px; embedded in `effectcraft.exe` by `apps/effectcraft/build.rs` | +| `hicolor/x/apps/ai.storyteller.effectcraft.png` | Linux icon theme, 16–512 px; the 256 px one is the runtime icon on Windows and Linux | +| `hicolor/scalable/apps/ai.storyteller.effectcraft.svg` | Linux scalable icon | + +App ID: `ai.storyteller.effectcraft` (Wayland app ID, `packaging/linux/ai.storyteller.effectcraft.desktop`). + +## Regenerate + +Copy a new `icon-master.svg` / `icon.svg` from craftrules `assets/app-icons/effectcraft/` over +`effectcraft.svg` / `effectcraft-small.svg`, then run `packaging/icons.sh` (needs `resvg`; `iconutil` +on macOS for the `.icns`). Copy `effectcraft-small.svg` to `apps/effectcraft-web/web/favicon.svg` too. diff --git a/assets/app-icon/effectcraft-1024.png b/assets/app-icon/effectcraft-1024.png new file mode 100644 index 00000000..8635334a Binary files /dev/null and b/assets/app-icon/effectcraft-1024.png differ diff --git a/assets/app-icon/effectcraft-1024.png.attribution b/assets/app-icon/effectcraft-1024.png.attribution new file mode 100644 index 00000000..1793aa51 --- /dev/null +++ b/assets/app-icon/effectcraft-1024.png.attribution @@ -0,0 +1,8 @@ +asset: effectcraft-1024.png +title: EffectCraft app icon, 1024 px +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/effectcraft-macos-512.png b/assets/app-icon/effectcraft-macos-512.png new file mode 100644 index 00000000..8f34aeb6 Binary files /dev/null and b/assets/app-icon/effectcraft-macos-512.png differ diff --git a/assets/app-icon/effectcraft-macos-512.png.attribution b/assets/app-icon/effectcraft-macos-512.png.attribution new file mode 100644 index 00000000..7bfafd11 --- /dev/null +++ b/assets/app-icon/effectcraft-macos-512.png.attribution @@ -0,0 +1,8 @@ +asset: effectcraft-macos-512.png +title: EffectCraft runtime Dock icon (macOS) +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg on Apple's 824/1024 icon grid with resvg by packaging/icons.sh; embedded by apps/effectcraft/src/main.rs. diff --git a/assets/app-icon/effectcraft-small.svg b/assets/app-icon/effectcraft-small.svg new file mode 100644 index 00000000..150ac8f9 --- /dev/null +++ b/assets/app-icon/effectcraft-small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/app-icon/effectcraft-small.svg.attribution b/assets/app-icon/effectcraft-small.svg.attribution new file mode 100644 index 00000000..6dbaab16 --- /dev/null +++ b/assets/app-icon/effectcraft-small.svg.attribution @@ -0,0 +1,8 @@ +asset: effectcraft-small.svg +title: EffectCraft app icon, light vector +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: The same drawing vectorised at 1024 px with craftrules vectorize_tile.py, about 350 KB, for places where size matters. diff --git a/assets/app-icon/effectcraft.icns b/assets/app-icon/effectcraft.icns new file mode 100644 index 00000000..919d6876 Binary files /dev/null and b/assets/app-icon/effectcraft.icns differ diff --git a/assets/app-icon/effectcraft.icns.attribution b/assets/app-icon/effectcraft.icns.attribution new file mode 100644 index 00000000..d338c26f --- /dev/null +++ b/assets/app-icon/effectcraft.icns.attribution @@ -0,0 +1,8 @@ +asset: effectcraft.icns +title: EffectCraft macOS app icon +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg on Apple's 824/1024 icon grid with resvg and iconutil by packaging/icons.sh. diff --git a/assets/app-icon/effectcraft.ico b/assets/app-icon/effectcraft.ico new file mode 100644 index 00000000..35cd557c Binary files /dev/null and b/assets/app-icon/effectcraft.ico differ diff --git a/assets/app-icon/effectcraft.ico.attribution b/assets/app-icon/effectcraft.ico.attribution new file mode 100644 index 00000000..25a7faa1 --- /dev/null +++ b/assets/app-icon/effectcraft.ico.attribution @@ -0,0 +1,8 @@ +asset: effectcraft.ico +title: EffectCraft Windows app icon +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg (16-256 px) with resvg and packed by cargo xtask ico (packaging/icons.sh). diff --git a/assets/app-icon/effectcraft.svg b/assets/app-icon/effectcraft.svg new file mode 100644 index 00000000..3cf53a2f --- /dev/null +++ b/assets/app-icon/effectcraft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/app-icon/effectcraft.svg.attribution b/assets/app-icon/effectcraft.svg.attribution new file mode 100644 index 00000000..21960623 --- /dev/null +++ b/assets/app-icon/effectcraft.svg.attribution @@ -0,0 +1,8 @@ +asset: effectcraft.svg +title: EffectCraft app icon, master vector +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: The owner's ArtCraft drawing of an engraved unicorn bust, vectorised at 2048 px with craftrules assets/logo-options/_tools/vectorize_tile.py (potrace; three colours). Canonical artwork; the other icon files derive from it. diff --git a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..e41b16be Binary files /dev/null and b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..36847426 --- /dev/null +++ b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 128x128 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..639ebefe Binary files /dev/null and b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..943f87c1 --- /dev/null +++ b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 16x16 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..74d211bf Binary files /dev/null and b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..10347ea9 --- /dev/null +++ b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 24x24 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..291feff6 Binary files /dev/null and b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..86dd6d76 --- /dev/null +++ b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 256x256 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. Also the runtime window/taskbar icon on Windows and Linux. diff --git a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..e477dc25 Binary files /dev/null and b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..06f910e8 --- /dev/null +++ b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 32x32 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..be56e1aa Binary files /dev/null and b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..46fbd4d4 --- /dev/null +++ b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 48x48 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..6c233775 Binary files /dev/null and b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..a08d1411 --- /dev/null +++ b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 512x512 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png new file mode 100644 index 00000000..2b3b2e97 Binary files /dev/null and b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png differ diff --git a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png.attribution b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png.attribution new file mode 100644 index 00000000..fcfe963c --- /dev/null +++ b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.png +title: EffectCraft app icon, Linux hicolor 64x64 +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Rendered from effectcraft.svg with resvg by packaging/icons.sh. diff --git a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg new file mode 100644 index 00000000..150ac8f9 --- /dev/null +++ b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg.attribution b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg.attribution new file mode 100644 index 00000000..562c3506 --- /dev/null +++ b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg.attribution @@ -0,0 +1,8 @@ +asset: ai.storyteller.effectcraft.svg +title: EffectCraft app icon, Linux hicolor scalable +author: EffectCraft contributors (drawing by the project owner in ArtCraft) +source: original work +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by EffectCraft contributors (Claude agent, app icon) +notes: Copy of effectcraft-small.svg, written by packaging/icons.sh. diff --git a/packaging/icons.sh b/packaging/icons.sh new file mode 100755 index 00000000..afeb13f8 --- /dev/null +++ b/packaging/icons.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Regenerate every app icon file from assets/app-icon/effectcraft.svg (the canonical vector). +# +# Needs: resvg (brew install resvg / cargo install resvg). On macOS, iconutil also writes the +# .icns. The outputs are committed, so builds and packaging never need these tools. +# +# packaging/icons.sh +# +# effectcraft.svg and effectcraft-small.svg (the lighter 1024 px trace) come from craftrules +# (assets/app-icons/effectcraft/, which also keeps the source drawing) and are copied in by hand. +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DIR="$ROOT/assets/app-icon" +SVG="$DIR/effectcraft.svg" +ID="ai.storyteller.effectcraft" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +command -v resvg >/dev/null || { echo "error: resvg not found (brew install resvg)" >&2; exit 1; } + +render() { resvg -w "$2" -h "$2" "$1" "$3" ||' \ + -e 's|[[:space:]]*$||' "$SVG" >"$MAC" +grep -q 'viewBox="0 0 1024 1024"' "$MAC" || { echo "error: unexpected header in $SVG" >&2; exit 1; } + +render "$SVG" 1024 "$DIR/effectcraft-1024.png" +# Runtime window/Dock icon on macOS (apps/effectcraft/src/main.rs embeds it). +render "$MAC" 512 "$DIR/effectcraft-macos-512.png" + +# Linux hicolor theme (the 256 px one is also the runtime icon on Windows and Linux). +for s in 16 24 32 48 64 128 256 512; do + mkdir -p "$DIR/hicolor/${s}x${s}/apps" + render "$SVG" "$s" "$DIR/hicolor/${s}x${s}/apps/$ID.png" +done +mkdir -p "$DIR/hicolor/scalable/apps" +cp "$DIR/effectcraft-small.svg" "$DIR/hicolor/scalable/apps/$ID.svg" + +# Windows .ico. +ICO_PNGS=() +for s in 16 20 24 32 40 48 64 128 256; do + render "$SVG" "$s" "$TMP/ico-$s.png" + ICO_PNGS+=("$TMP/ico-$s.png") +done +(cd "$ROOT" && cargo run -q -p xtask -- ico "$DIR/effectcraft.ico" "${ICO_PNGS[@]}") + +# macOS .icns. +if command -v iconutil >/dev/null; then + SET="$TMP/effectcraft.iconset" + mkdir -p "$SET" + for s in 16 32 128 256 512; do + render "$MAC" "$s" "$SET/icon_${s}x${s}.png" + render "$MAC" $((s * 2)) "$SET/icon_${s}x${s}@2x.png" + done + iconutil -c icns -o "$DIR/effectcraft.icns" "$SET" +else + echo "warning: iconutil not found (macOS only); effectcraft.icns not regenerated" >&2 +fi +echo "icons written to $DIR" diff --git a/packaging/linux/ai.storyteller.effectcraft.desktop b/packaging/linux/ai.storyteller.effectcraft.desktop new file mode 100644 index 00000000..f7cf2dce --- /dev/null +++ b/packaging/linux/ai.storyteller.effectcraft.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=EffectCraft +GenericName=Motion Graphics Editor +Comment=Motion graphics and visual effects compositor +Exec=effectcraft %F +TryExec=effectcraft +Icon=ai.storyteller.effectcraft +Terminal=false +StartupNotify=true +StartupWMClass=ai.storyteller.effectcraft +Categories=AudioVideo;Video;Graphics;2DGraphics; +Keywords=motion;graphics;animation;compositor;effects;keyframe;vfx; diff --git a/xtask/src/ico.rs b/xtask/src/ico.rs new file mode 100644 index 00000000..a8ecd9b7 --- /dev/null +++ b/xtask/src/ico.rs @@ -0,0 +1,104 @@ +//! `cargo xtask ico …`: pack PNG images into a Windows `.ico`. +//! +//! Vista and later read PNG-compressed icon entries, so each PNG is stored as is. The size comes +//! from the PNG's IHDR chunk; 256 px is written as 0, per the ICO format. + +use std::path::Path; + +/// Width and height from a PNG's IHDR chunk. +pub fn png_size(png: &[u8]) -> Result<(u32, u32), String> { + const SIG: &[u8] = b"\x89PNG\r\n\x1a\n"; + if png.len() < 24 || &png[..8] != SIG || &png[12..16] != b"IHDR" { + return Err("not a PNG file".into()); + } + let w = u32::from_be_bytes([png[16], png[17], png[18], png[19]]); + let h = u32::from_be_bytes([png[20], png[21], png[22], png[23]]); + Ok((w, h)) +} + +/// An ICO file holding `pngs`, which must be square and at most 256 px. +pub fn pack(pngs: &[Vec]) -> Result, String> { + if pngs.is_empty() || pngs.len() > u16::MAX as usize { + return Err("need at least one PNG".into()); + } + let mut out = Vec::new(); + out.extend_from_slice(&0u16.to_le_bytes()); // reserved + out.extend_from_slice(&1u16.to_le_bytes()); // type: icon + out.extend_from_slice(&(pngs.len() as u16).to_le_bytes()); + let mut offset = 6 + 16 * pngs.len(); + for png in pngs { + let (w, h) = png_size(png)?; + if w != h || w == 0 || w > 256 { + return Err(format!("icon images must be square and at most 256 px, got {w}x{h}")); + } + let dim = if w == 256 { 0 } else { w as u8 }; + out.extend_from_slice(&[dim, dim, 0, 0]); // width, height, palette size, reserved + out.extend_from_slice(&1u16.to_le_bytes()); // colour planes + out.extend_from_slice(&32u16.to_le_bytes()); // bits per pixel + out.extend_from_slice(&(png.len() as u32).to_le_bytes()); + out.extend_from_slice(&(offset as u32).to_le_bytes()); + offset += png.len(); + } + for png in pngs { + out.extend_from_slice(png); + } + Ok(out) +} + +pub fn run(args: &[&str]) -> Result<(), String> { + let [out, inputs @ ..] = args else { + return Err("usage: cargo xtask ico …".into()); + }; + let pngs = inputs.iter().map(|p| std::fs::read(p).map_err(|e| format!("read {p}: {e}"))).collect::, _>>()?; + let ico = pack(&pngs)?; + std::fs::write(Path::new(out), &ico).map_err(|e| format!("write {out}: {e}"))?; + println!("{out}: {} images, {} bytes", pngs.len(), ico.len()); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn fake_png(size: u32) -> Vec { + let mut v = b"\x89PNG\r\n\x1a\n\0\0\0\x0dIHDR".to_vec(); + v.extend_from_slice(&size.to_be_bytes()); + v.extend_from_slice(&size.to_be_bytes()); + v.extend_from_slice(&[8, 6, 0, 0, 0, 1, 2, 3, 4]); + v + } + + #[test] + fn packs_directory_and_payloads() { + let a = fake_png(16); + let b = fake_png(256); + let ico = pack(&[a.clone(), b.clone()]).unwrap(); + assert_eq!(&ico[..6], &[0, 0, 1, 0, 2, 0]); + // Entry 0: 16 px at offset 6 + 32. + assert_eq!(&ico[6..8], &[16, 16]); + assert_eq!(u32::from_le_bytes(ico[14..18].try_into().unwrap()), a.len() as u32); + assert_eq!(u32::from_le_bytes(ico[18..22].try_into().unwrap()), 38); + // Entry 1: 256 px stored as 0, right after the first payload. + assert_eq!(&ico[22..24], &[0, 0]); + assert_eq!(u32::from_le_bytes(ico[34..38].try_into().unwrap()), 38 + a.len() as u32); + assert_eq!(&ico[38..38 + a.len()], &a[..]); + assert_eq!(&ico[38 + a.len()..], &b[..]); + } + + #[test] + fn rejects_bad_input() { + assert!(pack(&[]).is_err()); + assert!(pack(&[fake_png(512)]).is_err()); + assert!(pack(&[b"GIF89a".to_vec()]).is_err()); + assert_eq!(png_size(&fake_png(48)).unwrap(), (48, 48)); + } + + #[test] + fn committed_icon_is_valid() { + let path = crate::root().join("assets/app-icon/effectcraft.ico"); + let Ok(ico) = std::fs::read(&path) else { return }; + assert_eq!(&ico[..4], &[0, 0, 1, 0]); + let n = u16::from_le_bytes([ico[4], ico[5]]) as usize; + assert!(n >= 4, "expected several sizes in {}", path.display()); + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 323b9ca7..9a04da13 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,4 @@ -//! Workspace automation: `cargo xtask `. +//! Workspace automation: `cargo xtask `. //! //! - `layers`: enforces the dependency layering of `docs/architecture.md` §1 (downward-only edges, //! listed same-layer edges, no UI/OS crates below L5). @@ -8,8 +8,11 @@ //! `/web/dist` with `wasm-bindgen` (docs/web.md); `--serve` serves it on localhost. //! - `assets`: every asset file (image, icon, font, LUT, audio, video…) has a complete //! `.attribution` sidecar and an entry in `ATTRIBUTION.md` (AGENTS.md §1). +//! - `ico …`: pack PNGs into a Windows `.ico` (used by `packaging/icons.sh`). //! - `ci`: fmt check, clippy -D warnings, tests, layers, assets, wasm. +mod ico; + use std::process::{Command, ExitCode}; use serde_json::Value; @@ -357,7 +360,11 @@ fn main() -> ExitCode { "assets" => assets(), "ci" => ci(), "web" => web(&std::env::args().skip(2).collect::>()), - _ => Err("usage: cargo xtask ".into()), + "ico" => { + let rest: Vec = std::env::args().skip(2).collect(); + ico::run(&rest.iter().map(String::as_str).collect::>()) + } + _ => Err("usage: cargo xtask ".into()), }; match r { Ok(()) => ExitCode::SUCCESS, @@ -367,3 +374,8 @@ fn main() -> ExitCode { } } } + +/// The workspace root (the parent of `xtask/`). +pub fn root() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask has a parent dir").to_path_buf() +}