app icon: unicorn (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs
- assets/app-icon/: master and light SVGs, 1024 px PNG, .icns (Apple grid), .ico (16-256), hicolor 16-512 + scalable, LICENSE.txt, README with palette (#e0368f / #efe9dc / #0b0b0c), tile geometry and provenance; attribution sidecars + ATTRIBUTION.md rows. - packaging/icons.sh regenerates everything with resvg, iconutil and the new `cargo xtask ico`. - Runtime icon via ViewportBuilder::with_icon (padded variant on macOS) and Wayland app ID ai.storyteller.effectcraft; packaging/linux/ai.storyteller.effectcraft.desktop. - apps/effectcraft/build.rs embeds the .ico and VERSIONINFO into effectcraft.exe (winresource, Windows targets only; a missing resource compiler is a warning). - Web favicon.svg is now the unicorn icon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="EffectCraft app icon: an engraved unicorn on EffectCraft pink (#e0368f)" src="assets/app-icon/effectcraft-1024.png" width="128">
|
||||
</p>
|
||||
|
||||
<h1 align="center">EffectCraft</h1>
|
||||
|
||||
<p align="center">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 343 KiB |
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
@ -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<u16> = 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()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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/<n>x<n>/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.
|
||||
|
After Width: | Height: | Size: 347 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 114 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 343 KiB |
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 89 KiB |
|
|
@ -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).
|
||||
|
After Width: | Height: | Size: 682 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 733 B |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 147 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 343 KiB |
|
|
@ -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.
|
||||
|
|
@ -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" </dev/null; }
|
||||
|
||||
# The tile fills its 512 viewBox edge to edge, which suits Windows and Linux. macOS icons keep
|
||||
# Apple's grid instead: an 824 px body centred on a 1024 canvas with a transparent margin.
|
||||
MAC="$TMP/mac.svg"
|
||||
sed -e 's|<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">|<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><g transform="translate(100 100) scale(1.609375)">|' \
|
||||
-e 's|</svg>[[:space:]]*$|</g></svg>|' "$SVG" >"$MAC"
|
||||
grep -q 'viewBox="0 0 1024 1024"' "$MAC" || { echo "error: unexpected <svg> 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"
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
//! `cargo xtask ico <out.ico> <in.png>…`: 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<u8>]) -> Result<Vec<u8>, 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 <out.ico> <in.png>…".into());
|
||||
};
|
||||
let pngs = inputs.iter().map(|p| std::fs::read(p).map_err(|e| format!("read {p}: {e}"))).collect::<Result<Vec<_>, _>>()?;
|
||||
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<u8> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//! Workspace automation: `cargo xtask <layers|assets|wasm|web|ci>`.
|
||||
//! Workspace automation: `cargo xtask <layers|assets|wasm|web|ico|ci>`.
|
||||
//!
|
||||
//! - `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 @@
|
|||
//! `<target>/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
|
||||
//! `<file>.attribution` sidecar and an entry in `ATTRIBUTION.md` (AGENTS.md §1).
|
||||
//! - `ico <out.ico> <in.png>…`: 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::<Vec<_>>()),
|
||||
_ => Err("usage: cargo xtask <layers|assets|wasm|web [--dev] [--serve PORT]|ci>".into()),
|
||||
"ico" => {
|
||||
let rest: Vec<String> = std::env::args().skip(2).collect();
|
||||
ico::run(&rest.iter().map(String::as_str).collect::<Vec<_>>())
|
||||
}
|
||||
_ => Err("usage: cargo xtask <layers|assets|wasm|web [--dev] [--serve PORT]|ico OUT.ico IN.png…|ci>".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()
|
||||
}
|
||||
|
|
|
|||