diff --git a/AGENTS.md b/AGENTS.md index b199c7f96d..1e889b730d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,8 +73,8 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** Don't commit font files here (Inter, already in `assets/fonts/`, is the one exception); add new fonts to craft-fonts. LightCraft uses it as the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts` - then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p lightcraft` (or any cargo/xtask command). `crates/engine/build.rs` - embeds the manifest's fonts as `lightcraft_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web` + then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p dac-app` (or any cargo/xtask command). `crates/engine/build.rs` + embeds the manifest's fonts as `dac_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web` ships Noto Sans CJK SC Regular as a separate asset loaded by the UI before startup. The UI (`theme::font_definitions`) and the export watermark renderer use CJK faces (picked by script) as fallbacks after Inter. Unset, `CRAFT_FONTS` is empty: everything builds, tests and runs, but Chinese and Japanese text lack glyphs. Releases always build @@ -82,7 +82,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` without it; the FreeBSD CI job runs them with it. Rules: craftrules [`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md). - Icons drawn in code (e.g. `crates/ui-egui/src/icons.rs`) are original work and are recorded in `assets/ATTRIBUTION.md` as such; do not trace them from Adobe icons. -- Demo/test images: generated procedurally by `lightcraft-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published. +- Demo/test images: generated procedurally by `dac-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published. - **Enforced:** `cargo xtask assets` (in `ci`) fails when an image/icon/font/sound/video/raw/ICC/XMP file is not matched by a path pattern in the first column of `assets/ATTRIBUTION.md`, when a referenced licence file is missing, when a file name suggests Adobe material, or when an Adobe profile/template format (`.dcp`, `.lcp`, `.lrtemplate`, …) appears. @@ -90,7 +90,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - When in doubt about an asset's licence: don't use it. ## Running and looking at the app -- `cargo run --release -p lightcraft -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`). +- `cargo run --release -p dac-app -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`). - **Never send OS-level synthetic input** (osascript/System Events keystrokes or clicks, `cliclick`, accessibility automation): it goes to whatever window is frontmost — the user's terminal or other apps. Drive LightCraft only through its control channel (`ui.key`, `ui.pointer`, `ui.clickWidget`, `ui.menu.invoke`) or headless snapshots. @@ -101,7 +101,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` `ui.screenshot {"path": …, "headless": true}`; windowed screenshots fall back to headless after 2 s. - MCP: `lightcraft-cli mcp` (headless, `--demo` for the procedural library) or `lightcraft-cli mcp --connect` (drives the running app). See `docs/mcp.md`. Quick non-UI checks: `lightcraft-cli render in.jpg -o out.jpg --set light.exposure=1`. -- Export goes through `lightcraft_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in +- Export goes through `dac_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in `crates/ui-egui/src/menus.rs`. - Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`. - Parallel agents: separate git worktrees and `CARGO_TARGET_DIR=target/agent-`; each agent uses its **own control port** (pick one in 18000–19999, never the default 7980) and its own scratch subfolder (`//`) — never `rm -rf` shared paths; delete your target dir when done (disk is shared); keep every `Cargo.toml` valid at all times (the `crates/*` glob means one broken manifest breaks everyone). @@ -110,7 +110,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` ## Testing & performance (do this often) - Unit/property tests next to the code; end-to-end tests drive real binaries (`crates/mcp/tests/e2e.rs`, - `apps/lightcraft-cli/tests/`). New features need at least one test that would fail without them. + `apps/cli/tests/`). New features need at least one test that would fail without them. - **Run the app after every user-visible change**: launch with `--control 7980`, drive it with a JSON-lines script (`docs/showcase/run.py file.jsonl`), take `ui.screenshot`, and look at it. Check `ui.inspect` → `perf`. - **Benchmarks:** `cargo xtask bench` (24 MP raw from corpus; CPU and GPU columns) appends to `target/bench/history.jsonl` @@ -118,7 +118,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - **Measure, don't guess**: `LIGHTCRAFT_PROFILE=1` prints per-stage pipeline timings to stderr; time CLI renders with `/usr/bin/time`. Record numbers in `plan/STATUS.md` → Metrics. Budgets: slider update ≤ 16 ms (draft) / loupe ≤ 60 ms on ~2.5 MP; export ≤ 1 s per 24 MP JPEG. -- Never commit media: test images are procedural (`lightcraft-scenes`) or CC0 downloads in the gitignored `corpus/`. +- Never commit media: test images are procedural (`dac-scenes`) or CC0 downloads in the gitignored `corpus/`. ## Map of the code `geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine` diff --git a/CLAUDE.md b/CLAUDE.md index 994920d3d8..7e678f38b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,8 +73,8 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** Don't commit font files here (Inter, already in `assets/fonts/`, is the one exception); add new fonts to craft-fonts. LightCraft uses it as the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts` - then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p lightcraft` (or any cargo/xtask command). `crates/engine/build.rs` - embeds the manifest's fonts as `lightcraft_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web` + then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p dac-app` (or any cargo/xtask command). `crates/engine/build.rs` + embeds the manifest's fonts as `dac_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web` ships Noto Sans CJK SC Regular as a separate asset loaded by the UI before startup. The UI (`theme::font_definitions`) and the export watermark renderer use CJK faces (picked by script) as fallbacks after Inter. Unset, `CRAFT_FONTS` is empty: everything builds, tests and runs, but Chinese and Japanese text lack glyphs. Releases always build @@ -82,7 +82,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` without it; the FreeBSD CI job runs them with it. Rules: craftrules [`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md). - Icons drawn in code (e.g. `crates/ui-egui/src/icons.rs`) are original work and are recorded in `assets/ATTRIBUTION.md` as such; do not trace them from Adobe icons. -- Demo/test images: generated procedurally by `lightcraft-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published. +- Demo/test images: generated procedurally by `dac-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published. - **Enforced:** `cargo xtask assets` (in `ci`) fails when an image/icon/font/sound/video/raw/ICC/XMP file is not matched by a path pattern in the first column of `assets/ATTRIBUTION.md`, when a referenced licence file is missing, when a file name suggests Adobe material, or when an Adobe profile/template format (`.dcp`, `.lcp`, `.lrtemplate`, …) appears. @@ -90,7 +90,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - When in doubt about an asset's licence: don't use it. ## Running and looking at the app -- `cargo run --release -p lightcraft -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`). +- `cargo run --release -p dac-app -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`). - **Never send OS-level synthetic input** (osascript/System Events keystrokes or clicks, `cliclick`, accessibility automation): it goes to whatever window is frontmost — the user's terminal or other apps. Drive LightCraft only through its control channel (`ui.key`, `ui.pointer`, `ui.clickWidget`, `ui.menu.invoke`) or headless snapshots. @@ -101,7 +101,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` `ui.screenshot {"path": …, "headless": true}`; windowed screenshots fall back to headless after 2 s. - MCP: `lightcraft-cli mcp` (headless, `--demo` for the procedural library) or `lightcraft-cli mcp --connect` (drives the running app). See `docs/mcp.md`. Quick non-UI checks: `lightcraft-cli render in.jpg -o out.jpg --set light.exposure=1`. -- Export goes through `lightcraft_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in +- Export goes through `dac_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in `crates/ui-egui/src/menus.rs`. - Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`. - Parallel agents: separate git worktrees and `CARGO_TARGET_DIR=target/agent-`; each agent uses its **own control port** (pick one in 18000–19999, never the default 7980) and its own scratch subfolder (`//`) — never `rm -rf` shared paths; delete your target dir when done (disk is shared); keep every `Cargo.toml` valid at all times (the `crates/*` glob means one broken manifest breaks everyone). @@ -110,7 +110,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` ## Testing & performance (do this often) - Unit/property tests next to the code; end-to-end tests drive real binaries (`crates/mcp/tests/e2e.rs`, - `apps/lightcraft-cli/tests/`). New features need at least one test that would fail without them. + `apps/cli/tests/`). New features need at least one test that would fail without them. - **Run the app after every user-visible change**: launch with `--control 7980`, drive it with a JSON-lines script (`docs/showcase/run.py file.jsonl`), take `ui.screenshot`, and look at it. Check `ui.inspect` → `perf`. - **Benchmarks:** `cargo xtask bench` (24 MP raw from corpus; CPU and GPU columns) appends to `target/bench/history.jsonl` @@ -118,7 +118,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md` - **Measure, don't guess**: `LIGHTCRAFT_PROFILE=1` prints per-stage pipeline timings to stderr; time CLI renders with `/usr/bin/time`. Record numbers in `plan/STATUS.md` → Metrics. Budgets: slider update ≤ 16 ms (draft) / loupe ≤ 60 ms on ~2.5 MP; export ≤ 1 s per 24 MP JPEG. -- Never commit media: test images are procedural (`lightcraft-scenes`) or CC0 downloads in the gitignored `corpus/`. +- Never commit media: test images are procedural (`dac-scenes`) or CC0 downloads in the gitignored `corpus/`. ## Map of the code `geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine` diff --git a/Cargo.lock b/Cargo.lock index 45d8ff15c9..0ee1920db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,6 +1175,410 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "dac-app" +version = "0.4.0" +dependencies = [ + "dac-codecs", + "dac-engine", + "dac-raster", + "dac-sysmem", + "dac-ui-egui", + "eframe", + "egui", + "log", + "muda", + "rfd", + "serde_json", + "winresource", +] + +[[package]] +name = "dac-catalog" +version = "0.4.0" +dependencies = [ + "crc32fast", + "dac-develop", + "dac-meta", + "log", + "proptest", + "serde", + "serde_json", + "thiserror 2.0.21", + "web-time", +] + +[[package]] +name = "dac-cli" +version = "0.4.0" +dependencies = [ + "dac-codecs", + "dac-engine", + "dac-mcp", + "dac-merge", + "dac-raster", + "dac-sysmem", + "dac-ui-egui", + "dhat", + "serde_json", +] + +[[package]] +name = "dac-codecs" +version = "0.4.0" +dependencies = [ + "bytemuck", + "dac-color", + "dac-geom", + "dac-heif", + "dac-raster", + "image", + "image-webp", + "jpeg-decoder", + "jpeg-encoder", + "jxl-oxide", + "moxcms 0.9.1", + "png", + "proptest", + "ravif", + "rayon", + "serde", + "thiserror 2.0.21", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "dac-color" +version = "0.4.0" +dependencies = [ + "dac-geom", + "proptest", + "serde", +] + +[[package]] +name = "dac-denoise" +version = "0.4.0" +dependencies = [ + "dac-denoise-core", + "dac-raster", + "faer-core", + "miniz_oxide 0.8.9", + "proptest", + "rayon", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.21", + "web-time", +] + +[[package]] +name = "dac-denoise-core" +version = "0.4.0" +dependencies = [ + "dac-raster", + "miniz_oxide 0.8.9", + "proptest", + "rayon", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.21", + "web-time", +] + +[[package]] +name = "dac-develop" +version = "0.4.0" +dependencies = [ + "dac-color", + "dac-geom", + "miniz_oxide 0.8.9", + "proptest", + "serde", + "serde_json", +] + +[[package]] +name = "dac-engine" +version = "0.4.0" +dependencies = [ + "ab_glyph", + "dac-catalog", + "dac-codecs", + "dac-color", + "dac-denoise", + "dac-denoise-core", + "dac-develop", + "dac-faces", + "dac-fetch", + "dac-geom", + "dac-gpu", + "dac-merge", + "dac-meta", + "dac-pipeline", + "dac-preview", + "dac-raster", + "dac-raw", + "dac-scenes", + "dac-segment", + "dac-tiff", + "harfrust", + "libc", + "log", + "miniz_oxide 0.8.9", + "rayon", + "serde", + "serde_json", + "thiserror 2.0.21", + "unicode-segmentation", + "web-time", +] + +[[package]] +name = "dac-faces" +version = "0.4.0" +dependencies = [ + "matrixmultiply", + "ndarray", + "proptest", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.21", + "web-time", +] + +[[package]] +name = "dac-fetch" +version = "0.4.0" +dependencies = [ + "log", + "rustls", + "rustls-rustcrypto", + "sha2", + "webpki-roots", +] + +[[package]] +name = "dac-geom" +version = "0.4.0" +dependencies = [ + "proptest", + "serde", +] + +[[package]] +name = "dac-gpu" +version = "0.4.0" +dependencies = [ + "bytemuck", + "dac-color", + "dac-denoise", + "dac-develop", + "dac-geom", + "dac-pipeline", + "dac-raster", + "dac-scenes", + "log", + "pollster 1.0.1", + "wgpu", +] + +[[package]] +name = "dac-heif" +version = "0.4.0" +dependencies = [ + "heic-rs", +] + +[[package]] +name = "dac-mcp" +version = "0.4.0" +dependencies = [ + "dac-codecs", + "dac-engine", + "dac-raster", + "log", + "serde_json", +] + +[[package]] +name = "dac-merge" +version = "0.4.0" +dependencies = [ + "dac-codecs", + "dac-color", + "dac-geom", + "dac-meta", + "dac-raster", + "dac-raw", + "dac-scenes", + "rayon", + "serde", + "thiserror 2.0.21", +] + +[[package]] +name = "dac-meta" +version = "0.4.0" +dependencies = [ + "dac-geom", + "dac-tiff", + "proptest", + "quick-xml 0.38.4", + "serde", + "serde_json", +] + +[[package]] +name = "dac-pipeline" +version = "0.4.0" +dependencies = [ + "dac-color", + "dac-develop", + "dac-geom", + "dac-raster", + "dac-raw", + "dac-scenes", + "image", + "proptest", + "rayon", + "serde", + "serde_json", +] + +[[package]] +name = "dac-preview" +version = "0.4.0" +dependencies = [ + "dac-raster", + "jpeg-encoder", + "log", + "siphasher", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "dac-raster" +version = "0.4.0" +dependencies = [ + "dac-color", + "dac-geom", + "libc", + "proptest", + "rayon", + "serde", +] + +[[package]] +name = "dac-raw" +version = "0.4.0" +dependencies = [ + "dac-color", + "dac-geom", + "dac-meta", + "dac-raster", + "dac-tiff", + "jpeg-encoder", + "jxl-oxide", + "miniz_oxide 0.8.9", + "proptest", + "rayon", + "serde", + "sha2", + "thiserror 2.0.21", + "zune-core", + "zune-jpeg", + "zune-jpegxl", +] + +[[package]] +name = "dac-scenes" +version = "0.4.0" +dependencies = [ + "dac-color", + "dac-raster", + "image", + "rayon", + "serde", +] + +[[package]] +name = "dac-segment" +version = "0.4.0" +dependencies = [ + "candle-core", + "candle-nn", + "dac-fetch", + "log", + "serde", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "dac-sysmem" +version = "0.4.0" + +[[package]] +name = "dac-tiff" +version = "0.4.0" +dependencies = [ + "proptest", + "serde", + "thiserror 2.0.21", +] + +[[package]] +name = "dac-ui-egui" +version = "0.4.0" +dependencies = [ + "dac-catalog", + "dac-codecs", + "dac-color", + "dac-denoise", + "dac-develop", + "dac-engine", + "dac-faces", + "dac-geom", + "dac-meta", + "dac-pipeline", + "dac-preview", + "dac-raster", + "egui", + "log", + "serde", + "serde_json", + "toml", + "web-time", +] + +[[package]] +name = "dac-web" +version = "0.4.0" +dependencies = [ + "crc32fast", + "dac-codecs", + "dac-engine", + "dac-preview", + "dac-raster", + "dac-ui-egui", + "eframe", + "egui", + "js-sys", + "log", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "der" version = "0.7.10" @@ -2974,410 +3378,6 @@ dependencies = [ "redox_syscall 0.9.4", ] -[[package]] -name = "lightcraft" -version = "0.4.0" -dependencies = [ - "eframe", - "egui", - "lightcraft-codecs", - "lightcraft-engine", - "lightcraft-raster", - "lightcraft-sysmem", - "lightcraft-ui-egui", - "log", - "muda", - "rfd", - "serde_json", - "winresource", -] - -[[package]] -name = "lightcraft-catalog" -version = "0.4.0" -dependencies = [ - "crc32fast", - "lightcraft-develop", - "lightcraft-meta", - "log", - "proptest", - "serde", - "serde_json", - "thiserror 2.0.21", - "web-time", -] - -[[package]] -name = "lightcraft-cli" -version = "0.4.0" -dependencies = [ - "dhat", - "lightcraft-codecs", - "lightcraft-engine", - "lightcraft-mcp", - "lightcraft-merge", - "lightcraft-raster", - "lightcraft-sysmem", - "lightcraft-ui-egui", - "serde_json", -] - -[[package]] -name = "lightcraft-codecs" -version = "0.4.0" -dependencies = [ - "bytemuck", - "image", - "image-webp", - "jpeg-decoder", - "jpeg-encoder", - "jxl-oxide", - "lightcraft-color", - "lightcraft-geom", - "lightcraft-heif", - "lightcraft-raster", - "moxcms 0.9.1", - "png", - "proptest", - "ravif", - "rayon", - "serde", - "thiserror 2.0.21", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "lightcraft-color" -version = "0.4.0" -dependencies = [ - "lightcraft-geom", - "proptest", - "serde", -] - -[[package]] -name = "lightcraft-denoise" -version = "0.4.0" -dependencies = [ - "faer-core", - "lightcraft-denoise-core", - "lightcraft-raster", - "miniz_oxide 0.8.9", - "proptest", - "rayon", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.21", - "web-time", -] - -[[package]] -name = "lightcraft-denoise-core" -version = "0.4.0" -dependencies = [ - "lightcraft-raster", - "miniz_oxide 0.8.9", - "proptest", - "rayon", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.21", - "web-time", -] - -[[package]] -name = "lightcraft-develop" -version = "0.4.0" -dependencies = [ - "lightcraft-color", - "lightcraft-geom", - "miniz_oxide 0.8.9", - "proptest", - "serde", - "serde_json", -] - -[[package]] -name = "lightcraft-engine" -version = "0.4.0" -dependencies = [ - "ab_glyph", - "harfrust", - "libc", - "lightcraft-catalog", - "lightcraft-codecs", - "lightcraft-color", - "lightcraft-denoise", - "lightcraft-denoise-core", - "lightcraft-develop", - "lightcraft-faces", - "lightcraft-fetch", - "lightcraft-geom", - "lightcraft-gpu", - "lightcraft-merge", - "lightcraft-meta", - "lightcraft-pipeline", - "lightcraft-preview", - "lightcraft-raster", - "lightcraft-raw", - "lightcraft-scenes", - "lightcraft-segment", - "lightcraft-tiff", - "log", - "miniz_oxide 0.8.9", - "rayon", - "serde", - "serde_json", - "thiserror 2.0.21", - "unicode-segmentation", - "web-time", -] - -[[package]] -name = "lightcraft-faces" -version = "0.4.0" -dependencies = [ - "matrixmultiply", - "ndarray", - "proptest", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.21", - "web-time", -] - -[[package]] -name = "lightcraft-fetch" -version = "0.4.0" -dependencies = [ - "log", - "rustls", - "rustls-rustcrypto", - "sha2", - "webpki-roots", -] - -[[package]] -name = "lightcraft-geom" -version = "0.4.0" -dependencies = [ - "proptest", - "serde", -] - -[[package]] -name = "lightcraft-gpu" -version = "0.4.0" -dependencies = [ - "bytemuck", - "lightcraft-color", - "lightcraft-denoise", - "lightcraft-develop", - "lightcraft-geom", - "lightcraft-pipeline", - "lightcraft-raster", - "lightcraft-scenes", - "log", - "pollster 1.0.1", - "wgpu", -] - -[[package]] -name = "lightcraft-heif" -version = "0.4.0" -dependencies = [ - "heic-rs", -] - -[[package]] -name = "lightcraft-mcp" -version = "0.4.0" -dependencies = [ - "lightcraft-codecs", - "lightcraft-engine", - "lightcraft-raster", - "log", - "serde_json", -] - -[[package]] -name = "lightcraft-merge" -version = "0.4.0" -dependencies = [ - "lightcraft-codecs", - "lightcraft-color", - "lightcraft-geom", - "lightcraft-meta", - "lightcraft-raster", - "lightcraft-raw", - "lightcraft-scenes", - "rayon", - "serde", - "thiserror 2.0.21", -] - -[[package]] -name = "lightcraft-meta" -version = "0.4.0" -dependencies = [ - "lightcraft-geom", - "lightcraft-tiff", - "proptest", - "quick-xml 0.38.4", - "serde", - "serde_json", -] - -[[package]] -name = "lightcraft-pipeline" -version = "0.4.0" -dependencies = [ - "image", - "lightcraft-color", - "lightcraft-develop", - "lightcraft-geom", - "lightcraft-raster", - "lightcraft-raw", - "lightcraft-scenes", - "proptest", - "rayon", - "serde", - "serde_json", -] - -[[package]] -name = "lightcraft-preview" -version = "0.4.0" -dependencies = [ - "jpeg-encoder", - "lightcraft-raster", - "log", - "siphasher", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "lightcraft-raster" -version = "0.4.0" -dependencies = [ - "libc", - "lightcraft-color", - "lightcraft-geom", - "proptest", - "rayon", - "serde", -] - -[[package]] -name = "lightcraft-raw" -version = "0.4.0" -dependencies = [ - "jpeg-encoder", - "jxl-oxide", - "lightcraft-color", - "lightcraft-geom", - "lightcraft-meta", - "lightcraft-raster", - "lightcraft-tiff", - "miniz_oxide 0.8.9", - "proptest", - "rayon", - "serde", - "sha2", - "thiserror 2.0.21", - "zune-core", - "zune-jpeg", - "zune-jpegxl", -] - -[[package]] -name = "lightcraft-scenes" -version = "0.4.0" -dependencies = [ - "image", - "lightcraft-color", - "lightcraft-raster", - "rayon", - "serde", -] - -[[package]] -name = "lightcraft-segment" -version = "0.4.0" -dependencies = [ - "candle-core", - "candle-nn", - "lightcraft-fetch", - "log", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "lightcraft-sysmem" -version = "0.4.0" - -[[package]] -name = "lightcraft-tiff" -version = "0.4.0" -dependencies = [ - "proptest", - "serde", - "thiserror 2.0.21", -] - -[[package]] -name = "lightcraft-ui-egui" -version = "0.4.0" -dependencies = [ - "egui", - "lightcraft-catalog", - "lightcraft-codecs", - "lightcraft-color", - "lightcraft-denoise", - "lightcraft-develop", - "lightcraft-engine", - "lightcraft-faces", - "lightcraft-geom", - "lightcraft-meta", - "lightcraft-pipeline", - "lightcraft-preview", - "lightcraft-raster", - "log", - "serde", - "serde_json", - "toml", - "web-time", -] - -[[package]] -name = "lightcraft-web" -version = "0.4.0" -dependencies = [ - "crc32fast", - "eframe", - "egui", - "js-sys", - "lightcraft-codecs", - "lightcraft-engine", - "lightcraft-preview", - "lightcraft-raster", - "lightcraft-ui-egui", - "log", - "serde", - "serde_json", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "linebender_resource_handle" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5f7c85db6b..93840caeb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,30 +11,30 @@ repository = "https://github.com/storytold/lightcraft" [workspace.dependencies] # internal -lightcraft-denoise-core = { path = "crates/denoise-core" } -lightcraft-denoise = { path = "crates/denoise", default-features = false } -lightcraft-geom = { path = "crates/geom" } -lightcraft-faces = { path = "crates/faces" } -lightcraft-sysmem = { path = "crates/sysmem" } -lightcraft-color = { path = "crates/color" } -lightcraft-raster = { path = "crates/raster" } -lightcraft-tiff = { path = "crates/tiff" } -lightcraft-raw = { path = "crates/raw" } -lightcraft-codecs = { path = "crates/codecs" } -lightcraft-meta = { path = "crates/meta" } -lightcraft-develop = { path = "crates/develop" } -lightcraft-scenes = { path = "crates/scenes" } -lightcraft-pipeline = { path = "crates/pipeline" } -lightcraft-gpu = { path = "crates/gpu" } -lightcraft-catalog = { path = "crates/catalog" } -lightcraft-preview = { path = "crates/preview" } -lightcraft-merge = { path = "crates/merge" } -lightcraft-engine = { path = "crates/engine" } -lightcraft-ui-egui = { path = "crates/ui-egui" } -lightcraft-mcp = { path = "crates/mcp" } -lightcraft-fetch = { path = "crates/fetch" } -lightcraft-heif = { path = "crates/heif" } -lightcraft-segment = { path = "crates/segment" } +dac-denoise-core = { path = "crates/denoise-core" } +dac-denoise = { path = "crates/denoise", default-features = false } +dac-geom = { path = "crates/geom" } +dac-faces = { path = "crates/faces" } +dac-sysmem = { path = "crates/sysmem" } +dac-color = { path = "crates/color" } +dac-raster = { path = "crates/raster" } +dac-tiff = { path = "crates/tiff" } +dac-raw = { path = "crates/raw" } +dac-codecs = { path = "crates/codecs" } +dac-meta = { path = "crates/meta" } +dac-develop = { path = "crates/develop" } +dac-scenes = { path = "crates/scenes" } +dac-pipeline = { path = "crates/pipeline" } +dac-gpu = { path = "crates/gpu" } +dac-catalog = { path = "crates/catalog" } +dac-preview = { path = "crates/preview" } +dac-merge = { path = "crates/merge" } +dac-engine = { path = "crates/engine" } +dac-ui-egui = { path = "crates/ui-egui" } +dac-mcp = { path = "crates/mcp" } +dac-fetch = { path = "crates/fetch" } +dac-heif = { path = "crates/heif" } +dac-segment = { path = "crates/segment" } # foundation thiserror = "2" @@ -70,7 +70,7 @@ zune-jpegxl = { version = "0.5", default-features = false, features = ["std"] } ravif = { version = "0.13", default-features = false, features = ["threading"] } # no "asm": it needs nasm (non-Rust) # gpu (crates/gpu; native backends only, no GL: everything stays pure Rust) -# SAM 3 inference (lightcraft-segment). 0.9.2: later releases pull `tokenizers` with the +# SAM 3 inference (dac-segment). 0.9.2: later releases pull `tokenizers` with the # Oniguruma C library into candle-core, and the product is pure Rust. candle-core = { version = "=0.9.2", default-features = false } candle-nn = { version = "=0.9.2", default-features = false } @@ -86,7 +86,7 @@ rfd = "0.17" muda = { version = "0.21", default-features = false } # native menu bar (macOS: objc2/AppKit, pure Rust; no GTK) web-time = "1" -# web (apps/lightcraft-web) +# web (apps/web) wasm-bindgen = "0.2.129" wasm-bindgen-futures = "0.4" js-sys = "0.3" @@ -132,15 +132,15 @@ panic = "abort" opt-level = "s" [profile.web.package] -lightcraft-pipeline = { opt-level = 3 } -lightcraft-raster = { opt-level = 3 } -lightcraft-color = { opt-level = 3 } -lightcraft-develop = { opt-level = 3 } -lightcraft-geom = { opt-level = 3 } -lightcraft-raw = { opt-level = 3 } -lightcraft-codecs = { opt-level = 3 } -lightcraft-scenes = { opt-level = 3 } -lightcraft-preview = { opt-level = 3 } +dac-pipeline = { opt-level = 3 } +dac-raster = { opt-level = 3 } +dac-color = { opt-level = 3 } +dac-develop = { opt-level = 3 } +dac-geom = { opt-level = 3 } +dac-raw = { opt-level = 3 } +dac-codecs = { opt-level = 3 } +dac-scenes = { opt-level = 3 } +dac-preview = { opt-level = 3 } zune-jpeg = { opt-level = 3 } zune-core = { opt-level = 3 } jpeg-decoder = { opt-level = 3 } diff --git a/NOTICE b/NOTICE index 254e262800..b4115d0b6b 100644 --- a/NOTICE +++ b/NOTICE @@ -15,7 +15,7 @@ list (author, source, license) is in assets/ATTRIBUTION.md. Inter (assets/fonts/Inter-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-Inter.txt. Showcase photographs by Ansel Adams, Dorothea Lange and NASA (docs/images/) — public domain. See assets/ATTRIBUTION.md. -crates/segment (lightcraft-segment) ports the SAM 3 model code of Hugging Face Transformers +crates/segment (dac-segment) ports the SAM 3 model code of Hugging Face Transformers (https://github.com/huggingface/transformers, src/transformers/models/sam3 and sam3_tracker), Copyright The HuggingFace Team and Meta Platforms, Inc., licensed under the Apache License 2.0. The port was modified by the LightCraft contributors (2026): translated from Python/PyTorch to diff --git a/README.md b/README.md index bca63f8a8a..0152518f48 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@

- LightCraft on getartcraft.com · + LightCraft on getartcraft.com · ArtCraft · All Crafting Apps

@@ -241,7 +241,7 @@ lightcraft --control 7980 ~/Pictures/trip to the running app with screenshots, clicks and gestures. See [docs/mcp.md](docs/mcp.md). ```sh - cargo build --release -p lightcraft-cli + cargo build --release -p dac-cli claude mcp add lightcraft -- "$PWD/target/release/lightcraft-cli" mcp ~/Pictures/shoot # headless claude mcp add lightcraft-app -- "$PWD/target/release/lightcraft-cli" mcp --connect 127.0.0.1:7980 # live app ``` @@ -327,12 +327,12 @@ LightCraft is young and moving fast. **Where we honestly stand** (details in the ```sh git clone https://github.com/storytold/lightcraft && cd lightcraft -cargo run --release -p lightcraft # opens your library (~/Pictures/LightCraft Library; a new one starts with demo photos) -cargo run --release -p lightcraft -- ~/Pictures/trip # import your photos (folders are scanned, duplicates skipped) -cargo run --release -p lightcraft -- --memory # a throwaway in-memory demo session (writes nothing) -cargo run --release -p lightcraft -- --control 7980 # with the automation channel +cargo run --release -p dac-app # opens your library (~/Pictures/LightCraft Library; a new one starts with demo photos) +cargo run --release -p dac-app -- ~/Pictures/trip # import your photos (folders are scanned, duplicates skipped) +cargo run --release -p dac-app -- --memory # a throwaway in-memory demo session (writes nothing) +cargo run --release -p dac-app -- --control 7980 # with the automation channel cargo xtask web --serve # the same app in the browser: http://127.0.0.1:8080/ -cargo run --release -p lightcraft-cli -- render photo.jpg -o out.jpg --set light.exposure=0.5 +cargo run --release -p dac-cli -- render photo.jpg -o out.jpg --set light.exposure=0.5 cargo xtask ci # fmt, clippy, tests, layering, wasm checks ``` @@ -348,7 +348,7 @@ override these defaults. Ordinary development commands keep their own settings. ```sh git clone https://github.com/storytold/craft-fonts ../craft-fonts -CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p lightcraft +CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p dac-app ``` Without it LightCraft builds and runs the same, but Chinese and Japanese text have no glyphs. Fonts are never committed to this @@ -367,9 +367,9 @@ next start; the file stops growing at 16 MiB, `--version` and `--help` write non `LIGHTCRAFT_NO_PREFS` log to standard error only. By default LightCraft's own crates log at `info` and everything else at `warn`. `LIGHTCRAFT_LOG=info` or `debug` works as before (that level for LightCraft's own crates, warnings and errors from the rest; any other value: warnings and errors only) and wins over `RUST_LOG`, which otherwise replaces -the default with env_logger-style directives such as `RUST_LOG=debug` or `RUST_LOG=warn,lightcraft_pipeline=trace` (a +the default with env_logger-style directives such as `RUST_LOG=debug` or `RUST_LOG=warn,dac_pipeline=trace` (a directive ending in `*` covers every target starting with it, as in `lightcraft*=debug`). Panics are recorded there -too, and still in `lightcraft-panics.log` in the temp folder. The logger is `apps/lightcraft/src/logging.rs`. +too, and still in `lightcraft-panics.log` in the temp folder. The logger is `apps/app/src/logging.rs`. The web build needs the `wasm32-unknown-unknown` target and the matching `wasm-bindgen` CLI (`cargo xtask web` prints the exact install command); see [docs/web.md](docs/web.md). @@ -432,7 +432,7 @@ Questions, ideas or a bug you'd like to talk through first? Bring them to [Disco ## Downloads -**New to LightCraft?** Download it from the [LightCraft page on getartcraft.com](https://getartcraft.com/apps/lightcraft). That's the easiest way to install it. +**New to LightCraft?** Download it from the [LightCraft page on getartcraft.com](https://getartcraft.com/apps/app). That's the easiest way to install it. **Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/lightcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/lightcraft/releases) has earlier versions and their notes. `` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file. @@ -488,7 +488,7 @@ stand on its own. | | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) | | | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) | | | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) | -| | **LightCraft** | **Photo library and raw development · you are here** | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) | +| | **LightCraft** | **Photo library and raw development · you are here** | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/app) | | | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) | | | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) | | | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) | @@ -514,7 +514,7 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud discord.gg/artcraft · getartcraft.com · The Crafting Apps · - LightCraft + LightCraft


diff --git a/apps/lightcraft/Cargo.toml b/apps/app/Cargo.toml similarity index 58% rename from apps/lightcraft/Cargo.toml rename to apps/app/Cargo.toml index e4d7f2b3b3..6152c0ec96 100644 --- a/apps/lightcraft/Cargo.toml +++ b/apps/app/Cargo.toml @@ -1,28 +1,32 @@ [package] -name = "lightcraft" +name = "dac-app" version.workspace = true edition.workspace = true license.workspace = true publish = false -default-run = "lightcraft" +default-run = "app" + +[[bin]] +name = "app" +path = "src/main.rs" [lints] workspace = true [features] default = ["denoise"] -denoise = ["lightcraft-engine/denoise"] -rawnind-model = ["denoise", "lightcraft-engine/rawnind-model"] -# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose +denoise = ["dac-engine/denoise"] +rawnind-model = ["denoise", "dac-engine/rawnind-model"] +# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose # it (HEVC patents); build with `--features heif` to include it. -heif = ["lightcraft-codecs/heif"] +heif = ["dac-codecs/heif"] [dependencies] -lightcraft-engine = { workspace = true, features = ["sam"] } -lightcraft-sysmem = { workspace = true } -lightcraft-ui-egui = { workspace = true } -lightcraft-codecs = { workspace = true } -lightcraft-raster = { workspace = true } +dac-engine = { workspace = true, features = ["sam"] } +dac-sysmem = { workspace = true } +dac-ui-egui = { workspace = true } +dac-codecs = { workspace = true } +dac-raster = { workspace = true } eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] } egui = { workspace = true } serde_json = { workspace = true } diff --git a/apps/lightcraft/build.rs b/apps/app/build.rs similarity index 100% rename from apps/lightcraft/build.rs rename to apps/app/build.rs diff --git a/apps/lightcraft/examples/gen_photos.rs b/apps/app/examples/gen_photos.rs similarity index 79% rename from apps/lightcraft/examples/gen_photos.rs rename to apps/app/examples/gen_photos.rs index ee7ba9a5eb..539a614a20 100644 --- a/apps/lightcraft/examples/gen_photos.rs +++ b/apps/app/examples/gen_photos.rs @@ -1,12 +1,12 @@ //! Generate a folder of procedural test photos (JPEG) for import/startup benchmarks. //! -//! `cargo run --release -p lightcraft --example gen_photos -- OUT_DIR [COUNT=200] [LONG_EDGE=2000]` +//! `cargo run --release -p dac-app --example gen_photos -- OUT_DIR [COUNT=200] [LONG_EDGE=2000]` //! -//! Each photo is a `lightcraft-scenes` demo scene rendered through the pipeline with a varied +//! Each photo is a `dac-scenes` demo scene rendered through the pipeline with a varied //! look (exposure, temperature, vibrance), so every file has distinct bytes. The output is our own //! procedural content; keep it out of the repository (write to a scratch or gitignored folder). -use lightcraft_engine::Session; +use dac_engine::Session; use serde_json::json; fn main() { @@ -30,11 +30,11 @@ fn main() { }); let _ = s.execute("develop.set", &json!({"values": v})); let img = s.render_now(id, edge, edge).expect("render"); - let bytes = lightcraft_codecs::encode_jpeg( - &lightcraft_codecs::EncodeImage::rgba8(&img.image), + let bytes = dac_codecs::encode_jpeg( + &dac_codecs::EncodeImage::rgba8(&img.image), 88, - lightcraft_codecs::ChromaSubsampling::S420, - &lightcraft_codecs::EncodeMeta::default(), + dac_codecs::ChromaSubsampling::S420, + &dac_codecs::EncodeMeta::default(), ) .expect("encode"); let dir = out.join(format!("roll-{:02}", i / 50 + 1)); diff --git a/apps/app/src/alloc_release.rs b/apps/app/src/alloc_release.rs new file mode 100644 index 0000000000..d396ebc11b --- /dev/null +++ b/apps/app/src/alloc_release.rs @@ -0,0 +1,12 @@ +//! Give memory the system allocator keeps after frees back to the system +//! ([`dac_engine::memory::set_release_hook`]). The platform call lives in +//! `dac-sysmem`, the workspace's only crate allowed `unsafe`. + +/// Install the hook. +pub fn install() { + dac_engine::memory::set_release_hook(release); +} + +fn release() { + let _ = dac_sysmem::release_free_memory(); +} diff --git a/apps/lightcraft/src/control_server.rs b/apps/app/src/control_server.rs similarity index 99% rename from apps/lightcraft/src/control_server.rs rename to apps/app/src/control_server.rs index 16bd5df0fa..64b62ca01b 100644 --- a/apps/lightcraft/src/control_server.rs +++ b/apps/app/src/control_server.rs @@ -14,7 +14,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{Receiver, Sender, channel}; use std::time::Duration; -use lightcraft_ui_egui::ControlRequest; +use dac_ui_egui::ControlRequest; use serde_json::{Value, json}; /// Longest accepted request line (bytes, without the newline). Requests are small JSON objects; diff --git a/apps/lightcraft/src/dialog_filter.rs b/apps/app/src/dialog_filter.rs similarity index 97% rename from apps/lightcraft/src/dialog_filter.rs rename to apps/app/src/dialog_filter.rs index 87760f7994..d82dece8d9 100644 --- a/apps/lightcraft/src/dialog_filter.rs +++ b/apps/app/src/dialog_filter.rs @@ -126,8 +126,8 @@ mod tests { /// The app's own extension list (the one the folder scan uses) is covered whole. #[test] fn import_extensions_are_all_covered_in_both_cases() { - let pats = dialog_extensions_for(lightcraft_engine::import::EXTENSIONS, true); - for e in lightcraft_engine::import::EXTENSIONS { + let pats = dialog_extensions_for(dac_engine::import::EXTENSIONS, true); + for e in dac_engine::import::EXTENSIONS { assert!(pats.iter().any(|p| glob_ext(p, &e.to_uppercase())), "{e}"); assert!(pats.iter().any(|p| p == e), "{e}"); } diff --git a/apps/lightcraft/src/logging.rs b/apps/app/src/logging.rs similarity index 96% rename from apps/lightcraft/src/logging.rs rename to apps/app/src/logging.rs index 718392f359..ac47eb695b 100644 --- a/apps/lightcraft/src/logging.rs +++ b/apps/app/src/logging.rs @@ -12,7 +12,7 @@ //! and naga are chatty). `LIGHTCRAFT_LOG` keeps the meaning it had with the old logger: `info` or //! `debug` lowers LightCraft's own crates to that level, any other value means warnings and errors //! only. Without it, `RUST_LOG` replaces the default with env_logger-style directives: `debug`, -//! `warn,lightcraft_pipeline=trace`, `wgpu_core=info`. A directive ending in `*` matches every +//! `warn,dac_pipeline=trace`, `wgpu_core=info`. A directive ending in `*` matches every //! target that starts with it (`lightcraft*=debug`). //! //! Records logged before the settings folder is known are kept (up to [`MAX_PENDING`]) and @@ -329,7 +329,7 @@ pub fn record_panics(logger: &'static AppLogger) { previous(info); let thread = std::thread::current(); let at = info.location().map(|l| format!(" at {}:{}", l.file(), l.line())).unwrap_or_default(); - let report = format!("panic{at}: {}", lightcraft_engine::guard::panic_message(info.payload())); + let report = format!("panic{at}: {}", dac_engine::guard::panic_message(info.payload())); logger.record_panic(thread.name().unwrap_or("?"), &report); })); } @@ -372,8 +372,8 @@ mod tests { let f = Filter::parse(DEFAULT_FILTER); assert_eq!(f.level_for("lightcraft"), LevelFilter::Info); assert_eq!(f.level_for("lightcraft::control_server"), LevelFilter::Info); - assert_eq!(f.level_for("lightcraft_engine::guard"), LevelFilter::Info); - assert_eq!(f.level_for("lightcraft_ui_egui::render"), LevelFilter::Info); + assert_eq!(f.level_for("dac_engine::guard"), LevelFilter::Info); + assert_eq!(f.level_for("dac_ui_egui::render"), LevelFilter::Info); assert_eq!(f.level_for("wgpu_core::device"), LevelFilter::Warn); assert_eq!(f.level_for("naga"), LevelFilter::Warn); assert_eq!(f.max(), LevelFilter::Info); @@ -385,8 +385,7 @@ mod tests { /// `LIGHTCRAFT_LOG` keeps working exactly as it did with the old stderr-only logger. #[test] fn lightcraft_log_keeps_its_meaning() { - let targets = - ["lightcraft", "lightcraft::control_server", "lightcraft_engine::segment", "lightcraft_gpu", "wgpu_core::device", "naga", "eframe"]; + let targets = ["dac-app", "lightcraft::control_server", "dac_engine::segment", "dac_gpu", "wgpu_core::device", "naga", "eframe"]; let levels = [log::Level::Error, log::Level::Warn, log::Level::Info, log::Level::Debug, log::Level::Trace]; for value in ["info", "debug", "warn", "error", "verbose", "INFO"] { let f = Filter::parse(&filter_spec(Some(value), None)); @@ -408,21 +407,21 @@ mod tests { // The app's own variable is the more specific choice. let f = Filter::parse(&filter_spec(Some("info"), Some("trace"))); assert_eq!(f.level_for("eframe"), LevelFilter::Warn); - assert_eq!(f.level_for("lightcraft_engine"), LevelFilter::Info); + assert_eq!(f.level_for("dac_engine"), LevelFilter::Info); } #[test] fn directives_follow_env_logger_and_the_most_specific_one_wins() { - let f = Filter::parse("info,wgpu_core=error,lightcraft_pipeline=trace,lightcraft_pipeline::tiles=off"); + let f = Filter::parse("info,wgpu_core=error,dac_pipeline=trace,dac_pipeline::tiles=off"); assert_eq!(f.level_for("eframe"), LevelFilter::Info); assert_eq!(f.level_for("wgpu_core::instance"), LevelFilter::Error); - assert_eq!(f.level_for("lightcraft_pipeline::stage"), LevelFilter::Trace); - assert_eq!(f.level_for("lightcraft_pipeline::tiles"), LevelFilter::Off); + assert_eq!(f.level_for("dac_pipeline::stage"), LevelFilter::Trace); + assert_eq!(f.level_for("dac_pipeline::tiles"), LevelFilter::Off); assert_eq!(f.max(), LevelFilter::Trace); // A module name is matched at `::` boundaries, not as a bare prefix. assert_eq!(f.level_for("wgpu_core_extra"), LevelFilter::Info); // A trailing `*` is a prefix. - assert_eq!(Filter::parse("lightcraft*=debug").level_for("lightcraft_raw::cr2"), LevelFilter::Debug); + assert_eq!(Filter::parse("lightcraft*=debug").level_for("dac_raw::cr2"), LevelFilter::Debug); assert_eq!(Filter::parse("lightcraft*=debug").level_for("eframe"), LevelFilter::Error); // A bare target name sets that target to the most verbose level, as env_logger does. assert_eq!(Filter::parse("naga").level_for("naga::front"), LevelFilter::Trace); @@ -598,15 +597,15 @@ mod tests { let path = logger.attach_dir(&dir).expect("attach"); assert_eq!(path, dir.join(LOG_FILE)); record(&logger, log::Level::Warn, "wgpu_hal::vulkan", "a real warning"); - record(&logger, log::Level::Error, "lightcraft_engine::guard", "`develop.reset` failed unexpectedly: boom"); + record(&logger, log::Level::Error, "dac_engine::guard", "`develop.reset` failed unexpectedly: boom"); record(&logger, log::Level::Debug, "lightcraft", "below info"); let text = read(&path); let lines: Vec<&str> = text.lines().collect(); assert_eq!(lines.len(), 3, "{text}"); assert!(lines[0].contains(" INFO [") && lines[0].ends_with("lightcraft: before the settings directory"), "{text}"); assert!(lines[1].ends_with("wgpu_hal::vulkan: a real warning"), "{text}"); - assert!(lines[2].contains(" ERROR [") && lines[2].ends_with("lightcraft_engine::guard: `develop.reset` failed unexpectedly: boom"), "{text}"); - assert!(logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("lightcraft_ui_egui").build())); + assert!(lines[2].contains(" ERROR [") && lines[2].ends_with("dac_engine::guard: `develop.reset` failed unexpectedly: boom"), "{text}"); + assert!(logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("dac_ui_egui").build())); assert!(!logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("naga").build())); // Attaching again rotates: the first log becomes `.1`. logger.attach_dir(&dir).expect("attach again"); diff --git a/apps/lightcraft/src/main.rs b/apps/app/src/main.rs similarity index 91% rename from apps/lightcraft/src/main.rs rename to apps/app/src/main.rs index 832b158b33..f9cfadf91a 100644 --- a/apps/lightcraft/src/main.rs +++ b/apps/app/src/main.rs @@ -9,11 +9,11 @@ //! line are imported (duplicates are skipped). `--memory` runs an in-memory session that writes //! nothing (demo photos unless files are given; used by the README showcase scripts). A library //! that can't be opened is never replaced silently: the window says why and asks what to do -//! (`lightcraft_ui_egui::panels::library_problem`). +//! (`dac_ui_egui::panels::library_problem`). //! //! `--control ` (or `LIGHTCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server: //! `{"id":1,"method":"ui.inspect","params":{}}` → `{"id":1,"ok":true,"result":…}`. -//! See `lightcraft_ui_egui::control` for the methods. +//! See `dac_ui_egui::control` for the methods. //! //! On Windows, release builds are GUI-subsystem programs: launching the app opens no console //! window (issue #7). Their `--help` / `--version` output and diagnostics then have no console to @@ -29,10 +29,10 @@ mod logging; #[cfg(target_os = "macos")] mod native_menu; +use dac_engine::Session; +use dac_ui_egui::panels::library_problem::LibraryProblem; +use dac_ui_egui::{DacApp, Services, UiState}; use dialog_filter::FileDialogExt as _; -use lightcraft_engine::Session; -use lightcraft_ui_egui::panels::library_problem::LibraryProblem; -use lightcraft_ui_egui::{LightcraftApp, Services, UiState}; /// Reverse-DNS app id: Wayland app id, `.desktop` file and hicolor icon name. const APP_ID: &str = "ai.storyteller.lightcraft"; @@ -47,14 +47,14 @@ fn app_icon() -> egui::IconData { eframe::icon_data::from_png_bytes(png).unwrap_or_default() } -struct App(LightcraftApp, PrefsWriter, #[cfg(target_os = "macos")] Option); +struct App(DacApp, PrefsWriter, #[cfg(target_os = "macos")] Option); impl eframe::App for App { fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { #[cfg(target_os = "macos")] if let Some(m) = self.2.as_mut() && m.update(&mut self.0, ctx) - && lightcraft_ui_egui::panels::notices::may_close(&mut self.0) + && dac_ui_egui::panels::notices::may_close(&mut self.0) { // Quit from the menu bar: save exactly what closing the window saves (settings and // library, after the unsaved-changes check; otherwise its prompt shows), then end the @@ -88,14 +88,14 @@ impl eframe::App for App { fn window_wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration { let mut c = eframe::egui_wgpu::WgpuConfiguration::default(); if let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = &mut c.wgpu_setup { - n.instance_descriptor.backends = lightcraft_engine::gpu::backend::window_backends(); - n.instance_descriptor.backend_options = lightcraft_engine::gpu::backend::backend_options(); + n.instance_descriptor.backends = dac_engine::gpu::backend::window_backends(); + n.instance_descriptor.backend_options = dac_engine::gpu::backend::backend_options(); } c } /// Written while the GPU compute device is created, removed once that returned -/// (`lightcraft_gpu::backend::set_init_marker`). Not with `LIGHTCRAFT_NO_PREFS` (tests, scripts). +/// (`dac_gpu::backend::set_init_marker`). Not with `LIGHTCRAFT_NO_PREFS` (tests, scripts). fn gpu_marker_path() -> Option { if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() { return None; @@ -114,7 +114,7 @@ fn gpu_crash_check(in_memory: bool) -> Option { } fn gpu_crash_check_at(marker: Option, in_memory: bool) -> Option { - use lightcraft_engine::gpu::backend; + use dac_engine::gpu::backend; let left = if in_memory { marker.as_deref().and_then(backend::read_init_marker) } else { @@ -134,7 +134,7 @@ fn gpu_crash_notice(what: &str) -> String { } fn config_dir() -> Option { - lightcraft_engine::config::config_dir() + dac_engine::config::config_dir() } /// `/ui.json`, the saved UI state and app settings — `None` when nothing is read or @@ -234,7 +234,7 @@ struct PrefsWriter { } impl PrefsWriter { - fn save(&mut self, app: &LightcraftApp) -> Result<(), String> { + fn save(&mut self, app: &DacApp) -> Result<(), String> { self.save_ui(&app.ui, app.library_problem.is_some()) } @@ -258,7 +258,7 @@ impl PrefsWriter { Ok(()) } - fn tick(&mut self, app: &mut LightcraftApp, ctx: &egui::Context) { + fn tick(&mut self, app: &mut DacApp, ctx: &egui::Context) { let now = ctx.input(|i| i.time); let moved = app.ui.settings.library_path != self.library; if !moved && now - self.checked < 3.0 { @@ -329,8 +329,8 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services Services { // Commands' file dialogs run on their own thread (#191): a dialog on the UI thread // stopped the window answering the compositor, which then reported the app as hung. - picker: Some(Box::new(move |req: lightcraft_ui_egui::pick::PickRequest| { - use lightcraft_ui_egui::pick::PickKind; + picker: Some(Box::new(move |req: dac_ui_egui::pick::PickRequest| { + use dac_ui_egui::pick::PickKind; let (tx, rx) = std::sync::mpsc::channel(); let repaint = ctx.clone(); std::thread::Builder::new() @@ -363,13 +363,13 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services log_file: log_file.map(|p| p.display().to_string()), pick_lightroom_catalog: Some(Box::new(|| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Import Lightroom Catalog")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Lightroom Classic Catalog"), &["lrcat"]) + .set_title(dac_ui_egui::i18n::tr("Import Lightroom Catalog")) + .add_filter_nocase(dac_ui_egui::i18n::tr("Lightroom Classic Catalog"), &["lrcat"]) .pick_file() .map(|p| p.to_string_lossy().to_string()) })), pick_folder: Some(Box::new(|| { - rfd::FileDialog::new().set_title(lightcraft_ui_egui::i18n::tr("Open Library")).pick_folder().map(|p| p.to_string_lossy().to_string()) + rfd::FileDialog::new().set_title(dac_ui_egui::i18n::tr("Open Library")).pick_folder().map(|p| p.to_string_lossy().to_string()) })), open_with: Some(Box::new(|path: &str, app: &str| { // spawned, never waited for: the editor runs alongside @@ -421,7 +421,7 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services })), pick_files: Some(Box::new(|| { rfd::FileDialog::new() - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Photos"), lightcraft_engine::import::EXTENSIONS) + .add_filter_nocase(dac_ui_egui::i18n::tr("Photos"), dac_engine::import::EXTENSIONS) .pick_files() .unwrap_or_default() .into_iter() @@ -430,16 +430,16 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services })), pick_denoise_model: Some(Box::new(|| { rfd::FileDialog::new() - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("ONNX model"), &["onnx"]) + .add_filter_nocase(dac_ui_egui::i18n::tr("ONNX model"), &["onnx"]) .pick_file() .map(|p| vec![p.to_string_lossy().into_owned()]) .unwrap_or_default() })), pick_preset_files: Some(Box::new(|| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Import Presets")) + .set_title(dac_ui_egui::i18n::tr("Import Presets")) .add_filter_nocase( - lightcraft_ui_egui::i18n::tr("Presets & Profiles"), + dac_ui_egui::i18n::tr("Presets & Profiles"), &["lcpreset", "xmp", "lrtemplate", "zip", "dng", "lmp", "mplumpack", "cube"], ) .pick_files() @@ -450,32 +450,32 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services })), pick_model_file: Some(Box::new(|| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Add a Face Recognition Model")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Face models (ONNX)"), &["onnx"]) + .set_title(dac_ui_egui::i18n::tr("Add a Face Recognition Model")) + .add_filter_nocase(dac_ui_egui::i18n::tr("Face models (ONNX)"), &["onnx"]) .pick_file() .map(|p| vec![p.to_string_lossy().to_string()]) .unwrap_or_default() })), pick_tracklog: Some(Box::new(|| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Auto-Tag from Tracklog")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("GPS Track Log"), &["gpx"]) + .set_title(dac_ui_egui::i18n::tr("Auto-Tag from Tracklog")) + .add_filter_nocase(dac_ui_egui::i18n::tr("GPS Track Log"), &["gpx"]) .pick_file() .map(|p| vec![p.to_string_lossy().to_string()]) .unwrap_or_default() })), save_preset_file: Some(Box::new(|name: &str| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Export Presets")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("LightCraft Preset"), &["lcpreset"]) + .set_title(dac_ui_egui::i18n::tr("Export Presets")) + .add_filter_nocase(dac_ui_egui::i18n::tr("LightCraft Preset"), &["lcpreset"]) .set_file_name(name) .save_file() .map(|p| p.to_string_lossy().to_string()) })), pick_curve_preset_files: Some(Box::new(|| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Import Point Curve Presets")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve", "json"]) + .set_title(dac_ui_egui::i18n::tr("Import Point Curve Presets")) + .add_filter_nocase(dac_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve", "json"]) .pick_files() .unwrap_or_default() .into_iter() @@ -484,17 +484,17 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services })), save_curve_preset_file: Some(Box::new(|name: &str| { rfd::FileDialog::new() - .set_title(lightcraft_ui_egui::i18n::tr("Export Point Curve Presets")) - .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve"]) + .set_title(dac_ui_egui::i18n::tr("Export Point Curve Presets")) + .add_filter_nocase(dac_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve"]) .set_file_name(name) .save_file() .map(|p| p.to_string_lossy().to_string()) })), // atomic (temp file + sync + rename): a failed write never leaves a truncated file - write_shared: Some(std::sync::Arc::new(lightcraft_engine::export::write_file)), - write: Some(Box::new(lightcraft_engine::export::write_file)), - png: Some(Box::new(|img: &lightcraft_raster::Rgba8| { - lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default() + write_shared: Some(std::sync::Arc::new(dac_engine::export::write_file)), + write: Some(Box::new(dac_engine::export::write_file)), + png: Some(Box::new(|img: &dac_raster::Rgba8| { + dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(img), &dac_codecs::EncodeMeta::default()).unwrap_or_default() })), // the library is a folder on disk: backed up with the user's other files backup_library: None, @@ -505,7 +505,7 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services /// The library session with the folder face models are kept in (`/models`). fn open_session(in_memory: bool, dir: Option, seed_demo: bool) -> (Session, Option) { let (mut s, problem) = open_library_session(in_memory, dir, seed_demo); - s.face_models_dir = lightcraft_engine::config::default_face_models_dir(); + s.face_models_dir = dac_engine::config::default_face_models_dir(); (s, problem) } @@ -569,7 +569,7 @@ ENVIRONMENT: "; /// The launch arguments' files and folders to import (absolute), and a notice for each one that -/// is not imported (see [`lightcraft_engine::import::launch_path`]; issue #374). +/// is not imported (see [`dac_engine::import::launch_path`]; issue #374). fn launch_imports(args: &[String]) -> (Vec, Vec) { let cwd = std::env::current_dir().ok(); let home = std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).filter(|h| !h.is_empty()).map(std::path::PathBuf::from); @@ -577,10 +577,10 @@ fn launch_imports(args: &[String]) -> (Vec, Vec) { } fn launch_imports_in(args: &[String], cwd: Option<&std::path::Path>, home: Option<&std::path::Path>) -> (Vec, Vec) { - use lightcraft_engine::import::{LaunchPath, launch_path}; + use dac_engine::import::{LaunchPath, launch_path}; let (mut files, mut notices) = (Vec::new(), Vec::new()); for a in args { - match launch_path(a, cwd, home, lightcraft_engine::devices::dcim_in) { + match launch_path(a, cwd, home, dac_engine::devices::dcim_in) { Some(LaunchPath::Import(p)) => files.push(p), Some(LaunchPath::Refused { path, why }) => { log::warn!("not importing {path} given at launch: {why}"); @@ -616,7 +616,7 @@ fn control_port_from(what: &str, value: Option) -> Option { fn main() -> eframe::Result { // First, so every start-up record is kept for the log file (`logging`). let logger = logging::install(); - lightcraft_engine::guard::install_hook(std::env::temp_dir().join("lightcraft-panics.log")); + dac_engine::guard::install_hook(std::env::temp_dir().join("lightcraft-panics.log")); if let Some(logger) = logger { logging::record_panics(logger); } @@ -671,7 +671,7 @@ fn main() -> eframe::Result { let (prefs, prefs_warning, keep_prefs_file) = load_prefs(in_memory); // the saved language from the start, so a failed start is reported in it too if let Some(ui) = &prefs { - lightcraft_ui_egui::i18n::set_language(ui.language); + dac_ui_egui::i18n::set_language(ui.language); } // --library, else the library last opened from Settings, else the default location let library_dir = library_dir.or_else(|| { @@ -681,12 +681,12 @@ fn main() -> eframe::Result { .filter(|p| !p.is_empty() && std::env::var_os("LIGHTCRAFT_LIBRARY").is_none()) .map(Into::into) }); - let library_dir = library_dir.or_else(lightcraft_engine::library::default_dir); + let library_dir = library_dir.or_else(dac_engine::library::default_dir); // GPU compute: off if the preference says so, or if the last launch died creating the device // (issue #136) — before anything can create it let gpu_crash = gpu_crash_check(in_memory); let gpu_on = prefs.as_ref().is_none_or(|u| u.settings.gpu) && gpu_crash.is_none(); - lightcraft_engine::gpu::set_enabled(gpu_on); + dac_engine::gpu::set_enabled(gpu_on); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("LightCraft") @@ -715,11 +715,11 @@ fn main() -> eframe::Result { std::env::var_os("LIGHTCRAFT_SAM3_DIR").map(std::path::PathBuf::from).or_else(|| config_dir().map(|d| d.join("models").join("sam3"))); // the user's own download locations, one base URL per line (LIGHTCRAFT_SAM3_MIRRORS too) session.segmenter.mirrors_file = config_dir().map(|d| d.join("models").join("sam3-mirrors.txt")); - let mut app = LightcraftApp::new(session, services(cc.egui_ctx.clone(), app_log_file.as_deref())); + let mut app = DacApp::new(session, services(cc.egui_ctx.clone(), app_log_file.as_deref())); if let Some(ui) = prefs { app.ui = ui; } - lightcraft_ui_egui::i18n::set_language(app.ui.language); + dac_ui_egui::i18n::set_language(app.ui.language); app.integrated_titlebar = cfg!(target_os = "macos"); app.notices.extend(prefs_warning); app.notices.extend(launch_notices); @@ -748,10 +748,10 @@ fn main() -> eframe::Result { // listed and read on the import worker once the window shows (issue #374: never // here, before the first frame, where a large folder kept the window from // appearing) - if let Err(e) = lightcraft_ui_egui::import::start_paths(&mut app, files) { + if let Err(e) = dac_ui_egui::import::start_paths(&mut app, files) { log::warn!("import at launch: {e}"); } - app.ui.view = lightcraft_ui_egui::state::ViewMode::PhotoGrid; + app.ui.view = dac_ui_egui::state::ViewMode::PhotoGrid; } // native menu bar generated from the command registry (macOS; elsewhere the menus are // drawn in the window's top bar). LIGHTCRAFT_NO_NATIVE_MENU=1 keeps the in-window menus. @@ -779,7 +779,7 @@ fn startup_failed(error: &str, log_file: Option<&std::path::Path>) { if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() { return; // tests and scripts: no dialog to click away } - let (title, text) = lightcraft_ui_egui::i18n::startup_failed_message(error, log_file.map(|p| p.display().to_string()).as_deref()); + let (title, text) = dac_ui_egui::i18n::startup_failed_message(error, log_file.map(|p| p.display().to_string()).as_deref()); let _ = rfd::MessageDialog::new() .set_level(rfd::MessageLevel::Error) .set_title(title) @@ -903,10 +903,10 @@ mod tests { assert!(m.exists(), "the marker stays for the next ordinary launch"); // the next ordinary launch reports it once and removes it assert!(gpu_crash_check_at(Some(m.clone()), false).is_some()); - lightcraft_engine::gpu::backend::set_init_marker(None); + dac_engine::gpu::backend::set_init_marker(None); assert!(!m.exists()); assert_eq!(gpu_crash_check_at(Some(m.clone()), false), None); - lightcraft_engine::gpu::backend::set_init_marker(None); + dac_engine::gpu::backend::set_init_marker(None); let _ = std::fs::remove_dir_all(&d); } @@ -915,9 +915,9 @@ mod tests { fn gpu_crash_marker_is_reported_once() { let d = dir("gpu-marker"); let m = d.join("gpu-init.marker"); - assert_eq!(lightcraft_engine::gpu::backend::take_init_marker(&m), None); + assert_eq!(dac_engine::gpu::backend::take_init_marker(&m), None); std::fs::write(&m, "GPU device creation started (backends VULKAN | DX12)").unwrap(); - let what = lightcraft_engine::gpu::backend::take_init_marker(&m).unwrap(); + let what = dac_engine::gpu::backend::take_init_marker(&m).unwrap(); let notice = gpu_crash_notice(&what); assert!(notice.contains("GPU rendering is now off") && notice.contains("Use the GPU for rendering"), "{notice}"); assert!(!m.exists()); diff --git a/apps/lightcraft/src/native_menu.rs b/apps/app/src/native_menu.rs similarity index 87% rename from apps/lightcraft/src/native_menu.rs rename to apps/app/src/native_menu.rs index 256faa1f81..999bc059cd 100644 --- a/apps/lightcraft/src/native_menu.rs +++ b/apps/app/src/native_menu.rs @@ -1,12 +1,12 @@ //! The native macOS menu bar (via `muda`, pure Rust over AppKit), generated from the same model as -//! the in-window menus ([`lightcraft_ui_egui::menubar`]): the registry's menu paths, shortcuts, +//! the in-window menus ([`dac_ui_egui::menubar`]): the registry's menu paths, shortcuts, //! enabled and checked state, and live labels ("Undo Exposure"). //! //! Key handling: AppKit offers a key press to the menu bar only when it carries ⌘ or ⌃, or is a //! function key (F1…). Every other key (`E`, `1`, `⇧P`, `⌥Y`…) goes straight to the window, whose //! winit view always takes it, so a menu item never fires from it. Hence //! - shortcuts the menu bar really receives ([`menu_delivers`]) are listed in -//! `LightcraftApp::native_shortcuts` and skipped by the egui shortcut handler (nothing fires +//! `DacApp::native_shortcuts` and skipped by the egui shortcut handler (nothing fires //! twice); the others stay on their menu items for display and egui runs them; //! - while a text field has keyboard focus, accelerators without ⌘ (`G`, `1`, `Delete`…) and the //! text-editing ones (⌘A/⌘C/⌘V/⌘X/⌘Z) are removed, so typing and text editing work; they come @@ -15,8 +15,8 @@ use std::collections::{HashMap, HashSet}; use std::sync::mpsc::{Receiver, channel}; -use lightcraft_ui_egui::LightcraftApp; -use lightcraft_ui_egui::menubar::{MenuNode, menu_bar}; +use dac_ui_egui::DacApp; +use dac_ui_egui::menubar::{MenuNode, menu_bar}; use muda::accelerator::{Accelerator, Code, Modifiers}; use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu}; use serde_json::Value; @@ -159,14 +159,14 @@ fn menu_delivers(sc: &str) -> bool { sc.split('+').any(|part| matches!(part, "Cmd" | "Ctrl") || part.strip_prefix('F').is_some_and(|n| n.parse::().is_ok())) } -/// The shortcuts the menu bar runs itself, for `LightcraftApp::native_shortcuts`. +/// The shortcuts the menu bar runs itself, for `DacApp::native_shortcuts`. fn owned_by_menu<'a>(installed: impl Iterator, text_focus: bool) -> HashSet { installed.filter(|sc| menu_delivers(sc) && !(text_focus && yields_to_text(sc))).map(str::to_string).collect() } /// `&` marks a mnemonic in muda labels. fn label_text(s: &str) -> String { - lightcraft_ui_egui::i18n::tr(s).replace('&', "&&") + dac_ui_egui::i18n::tr(s).replace('&', "&&") } fn structure_of(bar: &[(String, Vec)]) -> String { @@ -189,7 +189,7 @@ fn structure_of(bar: &[(String, Vec)]) -> String { out.push(';'); } } - let mut s = format!("{};", lightcraft_ui_egui::i18n::language().code()); + let mut s = format!("{};", dac_ui_egui::i18n::language().code()); for (t, items) in bar { s.push_str(t); walk(items, &mut s); @@ -207,7 +207,7 @@ fn tidy_separators(mut nodes: Vec) -> Vec { impl NativeMenu { /// Build the menu bar and install it as the application menu (main thread, after launch). - pub fn install(app: &mut LightcraftApp, ctx: &egui::Context) -> NativeMenu { + pub fn install(app: &mut DacApp, ctx: &egui::Context) -> NativeMenu { let (tx, rx) = channel::(); let repaint = ctx.clone(); MenuEvent::set_event_handler(Some(move |e: MenuEvent| { @@ -220,7 +220,7 @@ impl NativeMenu { m } - fn rebuild(&mut self, app: &mut LightcraftApp) { + fn rebuild(&mut self, app: &mut DacApp) { let bar = menu_bar(app); self.structure = structure_of(&bar); self.menu = Menu::new(); @@ -228,19 +228,19 @@ impl NativeMenu { // the application menu let app_menu = Submenu::new("LightCraft", true); - let about = MenuItem::with_id("app.about", lightcraft_ui_egui::i18n::tr("About LightCraft"), true, None); - let settings = MenuItem::with_id(SETTINGS, lightcraft_ui_egui::i18n::tr("Settings…"), true, accelerator(SETTINGS_KEY)); - let quit = MenuItem::with_id(QUIT, lightcraft_ui_egui::i18n::tr("Quit LightCraft"), true, accelerator("Cmd+Q")); + let about = MenuItem::with_id("app.about", dac_ui_egui::i18n::tr("About LightCraft"), true, None); + let settings = MenuItem::with_id(SETTINGS, dac_ui_egui::i18n::tr("Settings…"), true, accelerator(SETTINGS_KEY)); + let quit = MenuItem::with_id(QUIT, dac_ui_egui::i18n::tr("Quit LightCraft"), true, accelerator("Cmd+Q")); let _ = app_menu.append_items(&[ &about, &PredefinedMenuItem::separator(), &settings, &PredefinedMenuItem::separator(), - &PredefinedMenuItem::services(Some(lightcraft_ui_egui::i18n::tr("Services"))), + &PredefinedMenuItem::services(Some(dac_ui_egui::i18n::tr("Services"))), &PredefinedMenuItem::separator(), - &PredefinedMenuItem::hide(Some(lightcraft_ui_egui::i18n::tr("Hide LightCraft"))), - &PredefinedMenuItem::hide_others(Some(lightcraft_ui_egui::i18n::tr("Hide Others"))), - &PredefinedMenuItem::show_all(Some(lightcraft_ui_egui::i18n::tr("Show All"))), + &PredefinedMenuItem::hide(Some(dac_ui_egui::i18n::tr("Hide LightCraft"))), + &PredefinedMenuItem::hide_others(Some(dac_ui_egui::i18n::tr("Hide Others"))), + &PredefinedMenuItem::show_all(Some(dac_ui_egui::i18n::tr("Show All"))), &PredefinedMenuItem::separator(), &quit, ]); @@ -250,8 +250,8 @@ impl NativeMenu { let sub = Submenu::new(label_text(title), true); if title == "Window" { let _ = sub.append_items(&[ - &PredefinedMenuItem::minimize(Some(lightcraft_ui_egui::i18n::tr("Minimize"))), - &PredefinedMenuItem::maximize(Some(lightcraft_ui_egui::i18n::tr("Zoom"))), + &PredefinedMenuItem::minimize(Some(dac_ui_egui::i18n::tr("Minimize"))), + &PredefinedMenuItem::maximize(Some(dac_ui_egui::i18n::tr("Zoom"))), &PredefinedMenuItem::separator(), ]); } @@ -267,13 +267,13 @@ impl NativeMenu { // ⌘W, the system's own item let _ = sub.append_items(&[ &PredefinedMenuItem::separator(), - &PredefinedMenuItem::close_window(Some(lightcraft_ui_egui::i18n::tr("Close Window"))), + &PredefinedMenuItem::close_window(Some(dac_ui_egui::i18n::tr("Close Window"))), ]); } if title == "Window" { let _ = sub.append_items(&[ &PredefinedMenuItem::separator(), - &PredefinedMenuItem::bring_all_to_front(Some(lightcraft_ui_egui::i18n::tr("Bring All to Front"))), + &PredefinedMenuItem::bring_all_to_front(Some(dac_ui_egui::i18n::tr("Bring All to Front"))), ]); sub.set_as_windows_menu_for_nsapp(); } @@ -309,7 +309,7 @@ impl NativeMenu { Some(c) => { let it = CheckMenuItem::with_id( key.clone(), - lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"), + dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"), *enabled, *c, accel, @@ -320,7 +320,7 @@ impl NativeMenu { None => { let it = MenuItem::with_id( key.clone(), - lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"), + dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"), *enabled, accel, ); @@ -347,7 +347,7 @@ impl NativeMenu { } /// Tell the egui shortcut handler which shortcuts the menu bar currently owns. - fn publish_shortcuts(&self, app: &mut LightcraftApp) { + fn publish_shortcuts(&self, app: &mut DacApp) { let installed = self.items.values().filter(|i| i.accel.is_some()).filter_map(|i| i.shortcut.as_deref()); // (none while the keymap editor records a shortcut: every key goes to it) app.native_shortcuts = if self.capturing { HashSet::new() } else { owned_by_menu(installed, self.text_focus) }; @@ -358,8 +358,8 @@ impl NativeMenu { /// accelerators with the app state. Returns whether Quit was chosen: the caller saves /// everything and exits the process itself (closing the viewport while AppKit terminates the /// app can crash in winit's teardown, PR #444). - pub fn update(&mut self, app: &mut LightcraftApp, ctx: &egui::Context) -> bool { - lightcraft_ui_egui::i18n::set_language(app.ui.language); + pub fn update(&mut self, app: &mut DacApp, ctx: &egui::Context) -> bool { + dac_ui_egui::i18n::set_language(app.ui.language); let mut quit = false; while let Ok(key) = self.rx.try_recv() { if key == QUIT { @@ -372,7 +372,7 @@ impl NativeMenu { } if let Some(it) = self.items.get(&key) { let (cmd, params) = (it.cmd.clone(), it.params.clone()); - let _ = lightcraft_ui_egui::menubar::run_item(app, &cmd, params); + let _ = dac_ui_egui::menubar::run_item(app, &cmd, params); } } let bar = menu_bar(app); @@ -388,8 +388,8 @@ impl NativeMenu { let Some(it) = items.get_mut(&MenuNode::key(id, params)) else { continue }; if it.label != *label { match &it.handle { - Handle::Plain(h) => h.set_text(lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")), - Handle::Check(h) => h.set_text(lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")), + Handle::Plain(h) => h.set_text(dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")), + Handle::Check(h) => h.set_text(dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")), } it.label = label.clone(); } @@ -445,8 +445,8 @@ mod tests { /// languages — must change the structure key. #[test] fn switching_language_rebuilds_native_menu_structure() { - use lightcraft_ui_egui::i18n::{Locale, set_language}; - let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default()); + use dac_ui_egui::i18n::{Locale, set_language}; + let app = DacApp::new(dac_engine::Session::with_demo(), Default::default()); set_language(Locale::En); let bar = menu_bar(&app); let mut seen = std::collections::BTreeSet::new(); @@ -470,7 +470,7 @@ mod tests { assert!(yields_to_text("G") && yields_to_text("Delete") && yields_to_text("Cmd+V")); assert!(!yields_to_text("Cmd+Shift+E")); // every registry shortcut that a menu shows maps (or is deliberately left to egui) - let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default()); + let app = DacApp::new(dac_engine::Session::with_demo(), Default::default()); fn all(n: &[MenuNode], out: &mut Vec) { for x in n { match x { @@ -495,7 +495,7 @@ mod tests { fn single_key_shortcuts_stay_with_egui() { assert!(menu_delivers("Cmd+Shift+H") && menu_delivers("Ctrl+H") && menu_delivers("F2") && menu_delivers("Cmd+F11")); assert!(!menu_delivers("E") && !menu_delivers("Shift+P") && !menu_delivers("Alt+Y") && !menu_delivers("1") && !menu_delivers("F")); - let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default()); + let app = DacApp::new(dac_engine::Session::with_demo(), Default::default()); fn all(n: &[MenuNode], out: &mut Vec) { for x in n { match x { diff --git a/apps/lightcraft-cli/Cargo.toml b/apps/cli/Cargo.toml similarity index 50% rename from apps/lightcraft-cli/Cargo.toml rename to apps/cli/Cargo.toml index 6471ab86d6..f5834b79a2 100644 --- a/apps/lightcraft-cli/Cargo.toml +++ b/apps/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lightcraft-cli" +name = "dac-cli" version.workspace = true edition.workspace = true license.workspace = true @@ -7,32 +7,32 @@ publish = false description = "LightCraft command-line tool: headless render, command listing and the MCP server" [[bin]] -name = "lightcraft-cli" +name = "app-cli" path = "src/main.rs" [lints] workspace = true [dependencies] -lightcraft-engine = { workspace = true } -lightcraft-sysmem = { workspace = true } -lightcraft-mcp = { workspace = true } -lightcraft-ui-egui = { workspace = true } -lightcraft-codecs = { workspace = true } -lightcraft-raster = { workspace = true } -lightcraft-merge = { workspace = true } +dac-engine = { workspace = true } +dac-sysmem = { workspace = true } +dac-mcp = { workspace = true } +dac-ui-egui = { workspace = true } +dac-codecs = { workspace = true } +dac-raster = { workspace = true } +dac-merge = { workspace = true } serde_json = { workspace = true } dhat = { workspace = true, optional = true } [features] default = ["denoise"] -denoise = ["lightcraft-engine/denoise"] -rawnind-model = ["denoise", "lightcraft-engine/rawnind-model"] -# Heap profiling: `cargo run --release -p lightcraft-cli --features dhat-heap -- snapshot …` counts +denoise = ["dac-engine/denoise"] +rawnind-model = ["denoise", "dac-engine/rawnind-model"] +# Heap profiling: `cargo run --release -p dac-cli --features dhat-heap -- snapshot …` counts # every allocation (live/peak bytes in `library.memory` / `ui.inspect`) and writes dhat-heap.json # (open it in DHAT's viewer: allocation sites by bytes at the peak). Slows allocation-heavy work. dhat-heap = ["dep:dhat"] -# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose +# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose # it (HEVC patents); build with `--features heif` to include it. -heif = ["lightcraft-codecs/heif"] +heif = ["dac-codecs/heif"] diff --git a/apps/cli/src/alloc_release.rs b/apps/cli/src/alloc_release.rs new file mode 100644 index 0000000000..d396ebc11b --- /dev/null +++ b/apps/cli/src/alloc_release.rs @@ -0,0 +1,12 @@ +//! Give memory the system allocator keeps after frees back to the system +//! ([`dac_engine::memory::set_release_hook`]). The platform call lives in +//! `dac-sysmem`, the workspace's only crate allowed `unsafe`. + +/// Install the hook. +pub fn install() { + dac_engine::memory::set_release_hook(release); +} + +fn release() { + let _ = dac_sysmem::release_free_memory(); +} diff --git a/apps/lightcraft-cli/src/main.rs b/apps/cli/src/main.rs similarity index 93% rename from apps/lightcraft-cli/src/main.rs rename to apps/cli/src/main.rs index 7c9d255e93..0cb3b6995a 100644 --- a/apps/lightcraft-cli/src/main.rs +++ b/apps/cli/src/main.rs @@ -20,8 +20,8 @@ use std::io::{BufReader, Write}; use std::path::Path; use std::process::ExitCode; -use lightcraft_engine::Session; -use lightcraft_mcp::{Backend, DEFAULT_ADDR, Headless, Remote, Server, expand_paths}; +use dac_engine::Session; +use dac_mcp::{Backend, DEFAULT_ADDR, Headless, Remote, Server, expand_paths}; use serde_json::{Value, json}; const USAGE: &str = "\ @@ -110,9 +110,9 @@ fn library_warnings(session: &mut Session, who: &str) { } } -fn library_error(dir: &str, e: lightcraft_engine::EngineError) -> String { +fn library_error(dir: &str, e: dac_engine::EngineError) -> String { match e { - lightcraft_engine::EngineError::LibraryInUse(why) => format!( + dac_engine::EngineError::LibraryInUse(why) => format!( "{dir}: {why}\nTo work with the library while the app has it open, start the app with `--control PORT` and use `lightcraft-cli mcp --connect 127.0.0.1:PORT`." ), e => format!("{dir}: {e}"), @@ -125,16 +125,16 @@ fn main() -> ExitCode { #[cfg(feature = "dhat-heap")] let _heap = { let file = std::env::var("LIGHTCRAFT_DHAT_FILE").unwrap_or_else(|_| "dhat-heap.json".into()); - lightcraft_engine::memory::set_heap_stats(|| { + dac_engine::memory::set_heap_stats(|| { let s = dhat::HeapStats::get(); - lightcraft_engine::memory::HeapUsage { current: s.curr_bytes as u64, peak: s.max_bytes as u64 } + dac_engine::memory::HeapUsage { current: s.curr_bytes as u64, peak: s.max_bytes as u64 } }); dhat::Profiler::builder().file_name(file).build() }; alloc_release::install(); // Warnings (a GPU render redone on the CPU, an unknown backend name) on stderr; LIGHTCRAFT_LOG // or RUST_LOG picks another level (#168). - lightcraft_engine::logging::install("lightcraft-cli"); + dac_engine::logging::install("dac-cli"); let args: Vec = std::env::args().skip(1).collect(); let r = match args.first().map(String::as_str) { Some("run") => run(&args[1..]), @@ -217,15 +217,15 @@ fn calibrate(args: &[String]) -> Result<(), String> { files = (0..max).filter_map(|k| files.get(k * found / max).cloned()).collect(); } eprintln!("calibrate: {} of {found} raw files", files.len()); - let dir = out.or_else(lightcraft_engine::camera_profiles::dir).ok_or("no profiles folder: pass --out DIR")?; + let dir = out.or_else(dac_engine::camera_profiles::dir).ok_or("no profiles folder: pass --out DIR")?; let workers = std::thread::available_parallelism().map_or(4, |n| n.get()).clamp(1, 6); let next = std::sync::atomic::AtomicUsize::new(0); let done = std::sync::atomic::AtomicUsize::new(0); - let pools: Vec = std::thread::scope(|scope| { + let pools: Vec = std::thread::scope(|scope| { let handles: Vec<_> = (0..workers) .map(|_| { scope.spawn(|| { - let mut pool = lightcraft_engine::camera_profiles::Pool::default(); + let mut pool = dac_engine::camera_profiles::Pool::default(); while let Some(path) = files.get(next.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) { let result = std::fs::read(path).map_err(|e| e.to_string()).and_then(|bytes| pool.add(&bytes)); let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; @@ -241,7 +241,7 @@ fn calibrate(args: &[String]) -> Result<(), String> { .collect(); handles.into_iter().filter_map(|h| h.join().ok()).collect() }); - let mut pool = lightcraft_engine::camera_profiles::Pool::default(); + let mut pool = dac_engine::camera_profiles::Pool::default(); for p in pools { pool.merge(p); } @@ -249,7 +249,7 @@ fn calibrate(args: &[String]) -> Result<(), String> { for result in pool.fit(5) { match result { Ok(profile) => { - let path = lightcraft_engine::camera_profiles::save(&profile, &dir)?; + let path = dac_engine::camera_profiles::save(&profile, &dir)?; println!( "{}: {} photos, {} colour pairs, hue/saturation table {} → {}", profile.model, @@ -399,8 +399,8 @@ fn merge(args: &[String]) -> Result<(), String> { ids.sort_by_key(|id| { paths.iter().position(|f| { s.catalog - .photo(lightcraft_engine::catalog::PhotoId(*id)) - .is_some_and(|ph| matches!(&ph.source, lightcraft_engine::catalog::Source::File { path } if path == f)) + .photo(dac_engine::catalog::PhotoId(*id)) + .is_some_and(|ph| matches!(&ph.source, dac_engine::catalog::Source::File { path } if path == f)) }) }); p.insert("ids".into(), json!(ids)); @@ -427,22 +427,20 @@ fn synth_merge(args: &[String]) -> Result<(), String> { let mut written = Vec::new(); match kind { "hdr" => { - for (k, b) in lightcraft_merge::synth::bracket_dngs(1800, 1200, &[-2.0, 0.0, 2.0]).map_err(|e| e.to_string())?.into_iter().enumerate() { + for (k, b) in dac_merge::synth::bracket_dngs(1800, 1200, &[-2.0, 0.0, 2.0]).map_err(|e| e.to_string())?.into_iter().enumerate() { let p = Path::new(&dir).join(format!("bracket-{k}.dng")); std::fs::write(&p, b).map_err(|e| e.to_string())?; written.push(p); } } "panorama" | "pano" => { - for (k, v) in lightcraft_merge::synth::pano_views(1200, 900, 1000.0, &[-50.0, -25.0, 0.0, 25.0, 50.0]) - .map_err(|e| e.to_string())? - .into_iter() - .enumerate() + for (k, v) in + dac_merge::synth::pano_views(1200, 900, 1000.0, &[-50.0, -25.0, 0.0, 25.0, 50.0]).map_err(|e| e.to_string())?.into_iter().enumerate() { let p = Path::new(&dir).join(format!("view-{k}.png")); let img = v.to_srgb8(); - let png = lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &lightcraft_codecs::EncodeMeta::default()) - .map_err(|e| e.to_string())?; + let png = + dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(&img), &dac_codecs::EncodeMeta::default()).map_err(|e| e.to_string())?; std::fs::write(&p, png).map_err(|e| e.to_string())?; written.push(p); } @@ -562,7 +560,7 @@ fn run(args: &[String]) -> Result<(), String> { Some(dir) => { let mut h = Headless::default(); h.session.open_library(dir, demo).map_err(|e| library_error(dir, e))?; - library_warnings(&mut h.session, "lightcraft-cli"); + library_warnings(&mut h.session, "dac-cli"); h } None if demo => Headless::demo(), @@ -667,7 +665,7 @@ fn render(args: &[String]) -> Result<(), String> { if !values.is_empty() { run(&mut s, "develop.set", json!({"values": values}))?; } - use lightcraft_engine::export::{ExportFormat, ExportOptions, export_photo}; + use dac_engine::export::{ExportFormat, ExportOptions, export_photo}; let mut p = json!({"quality": quality}); if let Some(n) = size { p["longEdge"] = json!(n); @@ -681,7 +679,7 @@ fn render(args: &[String]) -> Result<(), String> { o.format = ExportFormat::parse(&ext) .ok_or_else(|| format!("{output}: unknown extension (use .jpg .png .tif .webp .avif .dng or --opt format=…)"))?; } - let e = export_photo(&mut s, lightcraft_engine::catalog::PhotoId(id), &o, 1)?; + let e = export_photo(&mut s, dac_engine::catalog::PhotoId(id), &o, 1)?; // never over the input (or its sidecar), however it is spelled: `render IMG.jpg -o IMG.jpg` let sidecars: Vec<(String, &Vec)> = e.sidecars.iter().map(|(ext, bytes)| (Path::new(&output).with_extension(ext).to_string_lossy().to_string(), bytes)).collect(); @@ -689,9 +687,9 @@ fn render(args: &[String]) -> Result<(), String> { for p in std::iter::once(&output).chain(sidecars.iter().map(|(p, _)| p)) { guard.check(Path::new(p)).map_err(|err| format!("render: {err}"))?; } - lightcraft_engine::export::write_file(&output, &e.bytes)?; + dac_engine::export::write_file(&output, &e.bytes)?; for (sc, bytes) in &sidecars { - lightcraft_engine::export::write_file(sc, bytes)?; + dac_engine::export::write_file(sc, bytes)?; eprintln!("lightcraft-cli: wrote {sc}"); } eprintln!("lightcraft-cli: wrote {output} ({}×{})", e.width, e.height); @@ -699,7 +697,7 @@ fn render(args: &[String]) -> Result<(), String> { } fn snapshot(args: &[String]) -> Result<(), String> { - use lightcraft_ui_egui::headless::Headless; + use dac_ui_egui::headless::Headless; use std::time::{Duration, Instant}; let mut library: Option = None; let mut script: Option = None; @@ -728,7 +726,7 @@ fn snapshot(args: &[String]) -> Result<(), String> { if script.is_none() && output.is_none() { return Err("snapshot: give -o OUT.png and/or --script FILE".into()); } - lightcraft_ui_egui::headless::viewport_pixels(size, scale).map_err(|e| format!("snapshot: bad --size/--scale: {e}"))?; + dac_ui_egui::headless::viewport_pixels(size, scale).map_err(|e| format!("snapshot: bad --size/--scale: {e}"))?; let t0 = Instant::now(); let mut session = match &library { Some(dir) => { @@ -747,15 +745,15 @@ fn snapshot(args: &[String]) -> Result<(), String> { let n = r["imported"].as_array().map_or(0, Vec::len); eprintln!("lightcraft-cli snapshot: imported {n} files in {:.0} ms", ti.elapsed().as_secs_f64() * 1e3); } - let services = lightcraft_ui_egui::Services { - write_shared: Some(std::sync::Arc::new(lightcraft_engine::export::write_file)), - write: Some(Box::new(lightcraft_engine::export::write_file)), - png: Some(Box::new(|img: &lightcraft_raster::Rgba8| { - lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default() + let services = dac_ui_egui::Services { + write_shared: Some(std::sync::Arc::new(dac_engine::export::write_file)), + write: Some(Box::new(dac_engine::export::write_file)), + png: Some(Box::new(|img: &dac_raster::Rgba8| { + dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(img), &dac_codecs::EncodeMeta::default()).unwrap_or_default() })), ..Default::default() }; - let mut app = lightcraft_ui_egui::LightcraftApp::new(session, services); + let mut app = dac_ui_egui::DacApp::new(session, services); // Snapshot sessions promise no GPU: disable photo compute as well as the compositor // before the first frame can start background adapter discovery. // This UI state is session-local: the CLI neither loads nor saves desktop ui.json. diff --git a/apps/lightcraft-cli/tests/cli.rs b/apps/cli/tests/cli.rs similarity index 94% rename from apps/lightcraft-cli/tests/cli.rs rename to apps/cli/tests/cli.rs index 2009e818db..945b09c7e5 100644 --- a/apps/lightcraft-cli/tests/cli.rs +++ b/apps/cli/tests/cli.rs @@ -5,7 +5,7 @@ use std::process::{Command, Stdio}; use serde_json::{Value, json}; -const BIN: &str = env!("CARGO_BIN_EXE_lightcraft-cli"); +const BIN: &str = env!("CARGO_BIN_EXE_app-cli"); fn tmp(name: &str) -> std::path::PathBuf { let d = std::env::temp_dir().join(format!("lightcraft-cli-test-{}", std::process::id())); @@ -14,8 +14,8 @@ fn tmp(name: &str) -> std::path::PathBuf { } fn gradient_png(path: &std::path::Path) { - let img = lightcraft_raster::Rgba8::from_fn(120, 80, |x, y| [(x * 2) as u8, (y * 3) as u8, 100, 255]); - let png = lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &Default::default()).unwrap(); + let img = dac_raster::Rgba8::from_fn(120, 80, |x, y| [(x * 2) as u8, (y * 3) as u8, 100, 255]); + let png = dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(&img), &Default::default()).unwrap(); std::fs::write(path, png).unwrap(); } @@ -33,7 +33,7 @@ fn calibrate_reads_raf_inputs_and_reports_bad_files() { } fn mean(path: &std::path::Path) -> f64 { - let d = lightcraft_codecs::decode(&std::fs::read(path).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(path).unwrap(), Default::default()).unwrap(); let img = d.to_srgb8(); img.data.iter().map(|p| (p[0] as u32 + p[1] as u32 + p[2] as u32) as f64).sum::() / (3 * img.data.len()) as f64 } @@ -88,9 +88,9 @@ fn render_subcommand() { .status() .unwrap(); assert!(st.success()); - let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (60, 40)); - let d = lightcraft_codecs::decode(&std::fs::read(&plain).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&plain).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (120, 80)); assert!(mean(&out) > mean(&plain) + 10.0); // Bad control id fails cleanly. @@ -128,9 +128,9 @@ fn render_export_options() { .status() .unwrap(); assert!(st.success()); - let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (60, 40)); - assert_eq!(d.space.named, Some(lightcraft_codecs::NamedSpace::DisplayP3)); + assert_eq!(d.space.named, Some(dac_codecs::NamedSpace::DisplayP3)); // original + sidecar named after the output let out = tmp("o-copy.png"); let st = Command::new(BIN) @@ -188,7 +188,7 @@ fn snapshot_subcommand_renders_the_ui_headlessly() { assert_eq!(replies.len(), 6); assert!(replies.iter().all(|r| r["ok"] == true), "{replies:?}"); for (p, dimmed) in [(&a, false), (&b, true)] { - let d = lightcraft_codecs::decode(&std::fs::read(p).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(p).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (640, 400)); // the export dialog dims everything around it assert_eq!(mean(p) < mean(&a) - 2.0, dimmed, "{}", p.display()); @@ -196,7 +196,7 @@ fn snapshot_subcommand_renders_the_ui_headlessly() { // no script: one settled screenshot, at 2× scale let o = Command::new(BIN).args(["snapshot", "-o", a.to_str().unwrap(), "--size", "320x240", "--scale", "2"]).output().unwrap(); assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr)); - let d = lightcraft_codecs::decode(&std::fs::read(&a).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&a).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (640, 480)); } @@ -229,7 +229,7 @@ fn snapshot_script_failure_exits_non_zero() { assert!(replies[0]["error"].as_str().unwrap_or("").contains("grid"), "{replies:?}"); assert_eq!(replies[1]["ok"], true, "{replies:?}"); // continue-and-report: the final screenshot is still written - let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (320, 200)); } @@ -251,7 +251,7 @@ fn snapshot_ui_zoom_keeps_requested_pixel_dimensions() { let replies: Vec = String::from_utf8_lossy(&o.stdout).lines().map(|line| serde_json::from_str(line).unwrap()).collect(); assert!(replies.iter().all(|reply| reply["ok"] == true), "{replies:?}"); assert!(replies[1]["result"]["pixelsPerPoint"].as_f64().unwrap() > scale.parse::().unwrap()); - let decoded = lightcraft_codecs::decode(&std::fs::read(out).unwrap(), Default::default()).unwrap(); + let decoded = dac_codecs::decode(&std::fs::read(out).unwrap(), Default::default()).unwrap(); assert_eq!((decoded.width, decoded.height), expected); } } @@ -281,7 +281,7 @@ fn snapshot_defaults_to_cpu_without_gpu_environment_overrides() { assert_eq!(gpu["available"], false, "{gpu}"); assert_eq!(gpu["adapter"], Value::Null, "no device is created: {gpu}"); assert!(gpu["reason"].as_str().is_some_and(|reason| reason.contains("preference")), "{gpu}"); - let decoded = lightcraft_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap(); + let decoded = dac_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap(); assert_eq!((decoded.width, decoded.height), (480, 320)); } @@ -316,7 +316,7 @@ fn snapshot_starts_without_a_gpu() { assert_eq!(gpu["available"], false, "{var}: {gpu}"); assert_eq!(gpu["adapter"], Value::Null, "{var}: no device was created: {gpu}"); assert!(gpu["reason"].as_str().is_some_and(|r| r.contains(reason)), "{var}: {gpu}"); - let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); + let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap(); assert_eq!((d.width, d.height), (480, 320)); } } @@ -484,7 +484,7 @@ fn snapshot_preserves_fractional_scale_rounding() { let output = Command::new(BIN).env("LIGHTCRAFT_GPU", "0").args(["snapshot", "--size", "345x200", "--scale", "0.9", "-o"]).arg(&image).output().unwrap(); assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); - let decoded = lightcraft_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap(); + let decoded = dac_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap(); assert_eq!((decoded.width, decoded.height), (311, 180)); } @@ -494,14 +494,14 @@ fn snapshot_preserves_fractional_scale_rounding() { fn snapshot_leaves_desktop_gpu_preferences_untouched() { let dir = tmp("snapshot-desktop-preferences"); let library = dir.join("library"); - let mut session = lightcraft_engine::Session::new().with_fs(); + let mut session = dac_engine::Session::new().with_fs(); session.open_library(&library, true).unwrap(); drop(session); let config = dir.join("config"); let app_config = config.join("lightcraft"); std::fs::create_dir_all(&app_config).unwrap(); let ui_path = app_config.join("ui.json"); - let mut ui = lightcraft_ui_egui::UiState::default(); + let mut ui = dac_ui_egui::UiState::default(); ui.settings.gpu = true; let saved = serde_json::to_vec_pretty(&ui).unwrap(); std::fs::write(&ui_path, &saved).unwrap(); diff --git a/apps/lightcraft-cli/src/alloc_release.rs b/apps/lightcraft-cli/src/alloc_release.rs deleted file mode 100644 index 83056aef44..0000000000 --- a/apps/lightcraft-cli/src/alloc_release.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Give memory the system allocator keeps after frees back to the system -//! ([`lightcraft_engine::memory::set_release_hook`]). The platform call lives in -//! `lightcraft-sysmem`, the workspace's only crate allowed `unsafe`. - -/// Install the hook. -pub fn install() { - lightcraft_engine::memory::set_release_hook(release); -} - -fn release() { - let _ = lightcraft_sysmem::release_free_memory(); -} diff --git a/apps/lightcraft/src/alloc_release.rs b/apps/lightcraft/src/alloc_release.rs deleted file mode 100644 index 83056aef44..0000000000 --- a/apps/lightcraft/src/alloc_release.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Give memory the system allocator keeps after frees back to the system -//! ([`lightcraft_engine::memory::set_release_hook`]). The platform call lives in -//! `lightcraft-sysmem`, the workspace's only crate allowed `unsafe`. - -/// Install the hook. -pub fn install() { - lightcraft_engine::memory::set_release_hook(release); -} - -fn release() { - let _ = lightcraft_sysmem::release_free_memory(); -} diff --git a/apps/lightcraft-web/Cargo.toml b/apps/web/Cargo.toml similarity index 82% rename from apps/lightcraft-web/Cargo.toml rename to apps/web/Cargo.toml index aaa14a648b..c45c52573b 100644 --- a/apps/lightcraft-web/Cargo.toml +++ b/apps/web/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lightcraft-web" +name = "dac-web" version.workspace = true edition.workspace = true license.workspace = true @@ -13,11 +13,11 @@ workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -lightcraft-engine = { workspace = true } -lightcraft-ui-egui = { workspace = true } -lightcraft-codecs = { workspace = true } -lightcraft-raster = { workspace = true } -lightcraft-preview = { workspace = true } +dac-engine = { workspace = true } +dac-ui-egui = { workspace = true } +dac-codecs = { workspace = true } +dac-raster = { workspace = true } +dac-preview = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } log = { workspace = true } @@ -73,7 +73,7 @@ web-sys = { workspace = true, features = [ ] } [features] -# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose +# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose # it (HEVC patents); build with `--features heif` to include it. On wasm32 panics abort, so # heic-rs's `catch_unwind` guard does not apply there. -heif = ["lightcraft-codecs/heif"] +heif = ["dac-codecs/heif"] diff --git a/apps/lightcraft-web/index.html b/apps/web/index.html similarity index 94% rename from apps/lightcraft-web/index.html rename to apps/web/index.html index b088ad18cd..6fb74707da 100644 --- a/apps/lightcraft-web/index.html +++ b/apps/web/index.html @@ -22,7 +22,7 @@
Loading LightCraft…