From 4537732c6dbad8fae78972bc010fbfbc24cdcf72 Mon Sep 17 00:00:00 2001 From: Baptiste Parmantier Date: Sat, 26 Sep 2026 23:28:33 +0200 Subject: [PATCH] feat(install): one git install command, with the studio behind a feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cargo install --git https://github.com/LeadcodeDev/rustmotion` did not work at all before this: cargo refused it with "multiple packages with binaries found: rustmotion, rustmotion-studio". That check is not about the workspace — cargo searches the whole repository for any Cargo.toml and demands that exactly one package declare a `[[bin]]`. Verified, not assumed: neither `default-members` nor `required-features` changes the count. So one package has to own both binaries, and it has to be `rustmotion`, because `cargo install rustmotion` from crates.io must keep delivering a command. The studio was a package that depended on `rustmotion` for `loader` and `encode`, so making it a dependency of `rustmotion` was a cycle — which cargo refuses even for an optional dependency. It becomes a module instead: `crates/rustmotion/src/studio/`. With no second package there is no cycle, and because `lib.rs` already declares `extern crate self as rustmotion`, all of the studio's `rustmotion::loader::…` paths stay valid verbatim. The only code change is requalifying its 82 internal `crate::` paths to `crate::studio::`. The alternative — extracting `loader`, `encode`, `include`, `assets` and the `engine` extensions into a third crate — would have moved 14 900 lines and added two crates to the published set to reach the same command. cargo install --git # CLI cargo install --git --features studio # CLI + studio `studio` is out of the default build on purpose: it pulls gpui and a native GUI toolchain, which do not build everywhere the CLI builds, a headless server being the obvious case. Its seven dependencies are optional and the second `[[bin]]` carries `required-features = ["studio"]`. Two consequences worth naming. CI: `--workspace` used to compile the studio because it was a member. Behind a non-default feature it would not, so clippy and the tests would have stayed green over 11 600 lines nobody checks any more. Both jobs now pass `--features rustmotion/studio`. Test count is unchanged at 1701 with the feature, 1510 without. `serde_json/preserve_order` moves into the `studio` feature rather than the default: the studio rewrites the user's scenario files and must not reorder their keys. `validate --fix` would want the same thing, but turning it on by default would change the key order of every file it rewrites, which is a separate decision. --- .github/workflows/ci.yaml | 17 ++++-- .github/workflows/publish.yaml | 12 ++-- Cargo.lock | 27 +++------ Cargo.toml | 8 +-- README.md | 20 ++++++- crates/rustmotion-components/src/lib.rs | 2 +- crates/rustmotion-core/tests/audit_ws_k.rs | 2 +- crates/rustmotion-studio/Cargo.toml | 28 --------- crates/rustmotion/CLAUDE.md | 34 ++++++++--- crates/rustmotion/Cargo.toml | 57 +++++++++++++++++-- .../main.rs => rustmotion/src/bin/studio.rs} | 2 +- crates/rustmotion/src/cli/mod.rs | 12 ++-- crates/rustmotion/src/lib.rs | 7 +++ .../src => rustmotion/src/studio}/app/mod.rs | 15 +++-- .../src/studio}/app/overlays.rs | 0 .../src => rustmotion/src/studio}/app/root.rs | 10 ++-- .../src/studio}/app/state.rs | 6 +- .../src/studio}/app/window.rs | 10 ++-- .../src/studio}/editor/annotations.rs | 11 ++-- .../src/studio}/editor/audio.rs | 0 .../src/studio}/editor/diff_panel.rs | 8 +-- .../src/studio}/editor/export.rs | 2 +- .../src/studio}/editor/frames.rs | 0 .../src/studio}/editor/inspector/controls.rs | 25 ++++---- .../src/studio}/editor/inspector/mod.rs | 12 ++-- .../src/studio}/editor/inspector/sections.rs | 0 .../src/studio}/editor/inspector/write.rs | 8 +-- .../src/studio}/editor/mod.rs | 0 .../src/studio}/editor/overlay.rs | 4 +- .../src/studio}/editor/playback.rs | 6 +- .../src/studio}/editor/prefetch.rs | 8 +-- .../src/studio}/editor/properties.rs | 0 .../src/studio}/editor/surface.rs | 6 +- .../src/studio}/editor/topbar.rs | 12 ++-- .../src/studio}/editor/view.rs | 6 +- .../src/studio}/library/data.rs | 0 .../src/studio}/library/mod.rs | 0 .../src/studio}/library/view.rs | 14 ++--- .../lib.rs => rustmotion/src/studio/mod.rs} | 0 .../src/studio}/scenario/baseline.rs | 0 .../src/studio}/scenario/diff.rs | 0 .../src/studio}/scenario/edit.rs | 0 .../src/studio}/scenario/history.rs | 2 +- .../src/studio}/scenario/mod.rs | 0 .../src/studio}/scenario/model.rs | 0 .../src/studio}/scenario/optimistic.rs | 8 +-- .../src/studio}/scenario/sidecar.rs | 0 .../src/studio}/theme/mod.rs | 2 +- .../src/studio}/theme/persist.rs | 2 +- .../tests/studio_audit_ws_g.rs} | 8 ++- .../studio-dioxus-notes.md | 0 51 files changed, 246 insertions(+), 167 deletions(-) delete mode 100644 crates/rustmotion-studio/Cargo.toml rename crates/{rustmotion-studio/src/bin/main.rs => rustmotion/src/bin/studio.rs} (64%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/app/mod.rs (90%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/app/overlays.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/app/root.rs (85%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/app/state.rs (91%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/app/window.rs (87%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/annotations.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/audio.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/diff_panel.rs (97%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/export.rs (99%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/frames.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/inspector/controls.rs (97%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/inspector/mod.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/inspector/sections.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/inspector/write.rs (97%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/mod.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/overlay.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/playback.rs (99%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/prefetch.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/properties.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/surface.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/topbar.rs (97%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/editor/view.rs (99%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/library/data.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/library/mod.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/library/view.rs (97%) rename crates/{rustmotion-studio/src/lib.rs => rustmotion/src/studio/mod.rs} (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/baseline.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/diff.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/edit.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/history.rs (99%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/mod.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/model.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/optimistic.rs (98%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/scenario/sidecar.rs (100%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/theme/mod.rs (94%) rename crates/{rustmotion-studio/src => rustmotion/src/studio}/theme/persist.rs (97%) rename crates/{rustmotion-studio/tests/audit_ws_g.rs => rustmotion/tests/studio_audit_ws_g.rs} (98%) rename crates/rustmotion-studio/DIOXUS_NOTES.md => docs/studio-dioxus-notes.md (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 65240d15..153b0f0b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install system dependencies - # xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui). + # xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui). # clippy passes without them — it never links — so the failure only shows in tests. # asound: required by cpal, which rodio pulls in for preview audio run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev @@ -33,21 +33,28 @@ jobs: components: clippy - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - name: Clippy - run: cargo clippy --workspace --all-targets -- -D warnings + # `--features rustmotion/studio` n'est pas décoratif : le studio est + # derrière un feature non-défaut depuis qu'il est un module de + # `rustmotion`, et `--workspace` seul ne compilerait plus une seule de + # ses 11 600 lignes. Sans ce drapeau, clippy reste vert sur du code que + # personne ne vérifie plus. + run: cargo clippy --workspace --all-targets --features rustmotion/studio -- -D warnings test: runs-on: ubuntu-latest steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install system dependencies - # xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui). + # xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui). # clippy passes without them — it never links — so the failure only shows in tests. # asound: required by cpal, which rodio pulls in for preview audio run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable channel, 2026-09-22 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - name: Run tests - run: cargo test --workspace + # Même raison qu'au job clippy : sans le feature, les tests du studio + # (`tests/studio_audit_ws_g.rs`) ne sont même pas compilés. + run: cargo test --workspace --features rustmotion/studio audit: runs-on: ubuntu-latest @@ -73,7 +80,7 @@ jobs: # Via rayon-core <- exr <- image, reaches rustmotion-core/-components. Fix: >=0.9.20. # RUSTSEC-2026-0195, RUSTSEC-2026-0194 — quick-xml 0.38.4 / 0.39.4, DoS + quadratic runtime. # 0.38.4 via syntect reaches the published crates; 0.39.4 via dioxus-desktop/rfd is - # rustmotion-studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0. + # studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0. # RUSTSEC-2026-0285 — rustls 0.23.37, TLS 1.3 handshake level-boundary bug. # Via ureq, used by rustmotion/rustmotion-core for Google Fonts + Iconify fetches. Fix: >=0.23.45. # RUSTSEC-2026-0104, RUSTSEC-2026-0098, RUSTSEC-2026-0099, RUSTSEC-2026-0049 — rustls-webpki diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index f54fd937..96c7da21 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -13,9 +13,9 @@ jobs: - name: Install system dependencies # Doit rester identique à ci.yaml : l'étape « Run tests » ci-dessous lance - # `cargo test --workspace`, qui compile ET LIE rustmotion-studio. Sans ces - # paquets l'édition de liens échoue et la release s'arrête avant toute - # publication. + # `cargo test --workspace --features rustmotion/studio`, qui compile ET + # LIE le studio. Sans ces paquets l'édition de liens échoue et la + # release s'arrête avant toute publication. # xkbcommon-x11/wayland/xcb-cursor : exigés par gpui, à l'édition de liens # seulement. clippy passe sans eux puisqu'il ne lie pas — la panne # n'apparaît donc qu'au job de test. C'est ce qui a cassé le premier CI du @@ -65,6 +65,10 @@ jobs: # pouvait donc jamais reussir, et la release 0.6.0 s'est arretee la — apres # avoir publie rustmotion-core, definitivement. # - # rustmotion-studio porte `publish = false` : cargo la saute tout seul. + # Le studio n'est plus un paquet : c'est un module de `rustmotion` derrière + # le feature `studio`, donc il part avec elle et il n'y a plus rien à + # sauter. Ses dépendances (gpui-kit, gpui-component, rfd, rodio, palette) + # sont optionnelles mais doivent rester résolvables depuis le registre, + # sans quoi la publication de `rustmotion` échoue. - name: Publish all crates run: cargo publish --workspace --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 059a5dad..8c2803f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6582,13 +6582,18 @@ dependencies = [ "crossterm", "dirs 6.0.0", "gif 0.13.3", + "gpui-component", + "gpui-kit", "image", "minimp4", "notify", "openh264", + "palette", "ratatui", "rayon", "resvg 0.44.0", + "rfd", + "rodio", "rubato", "rustfft", "rustmotion-components", @@ -6598,8 +6603,10 @@ dependencies = [ "serde", "serde_json", "skia-safe", + "smallvec", "symphonia", "tiny-skia", + "tokio", "ureq", "usvg 0.44.0", ] @@ -6655,26 +6662,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "rustmotion-studio" -version = "0.7.1" -dependencies = [ - "clap", - "dirs 6.0.0", - "gpui-component", - "gpui-kit", - "image", - "notify", - "palette", - "rfd", - "rodio", - "rustmotion", - "schemars 0.8.22", - "serde_json", - "smallvec", - "tokio", -] - [[package]] name = "rustversion" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 130af36b..4febefcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,10 @@ members = [ "crates/rustmotion-core", "crates/rustmotion-components", "crates/rustmotion", - "crates/rustmotion-studio", "crates/rustmotion-html", ] -# Les cinq crates avancent ensemble. La version se change ici, et nulle part +# Les quatre crates avancent ensemble. La version se change ici, et nulle part # ailleurs : elle était auparavant répétée dix fois — une par manifeste, plus une # par dépendance interne — et une seule oubliée fait échouer la publication après # que les précédentes soient parties, ce qui ne se rattrape pas. @@ -34,6 +33,7 @@ debug-assertions = false overflow-checks = false # The image crate's encoders are generic and monomorphize into the calling -# crate, so the studio itself must be optimized for fast preview encoding. -[profile.dev.package.rustmotion-studio] +# crate, so the studio itself must be optimized for fast preview encoding. The +# studio is a module of `rustmotion` now, so this covers the whole crate. +[profile.dev.package.rustmotion] opt-level = 3 diff --git a/README.md b/README.md index c119f1d9..6755d142 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,23 @@ MIT-licensed: no licence key, no telemetry, no per-render billing. See [Non-goal cargo install rustmotion ``` +From this repository, without waiting for a release: + +```bash +cargo install --git https://github.com/LeadcodeDev/rustmotion +``` + +Either command installs the `rustmotion` CLI. Add the `studio` feature to get +the live preview window (`rustmotion-studio`) alongside it: + +```bash +cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio +``` + +The studio is not in the default build because it pulls gpui and a native GUI +toolchain, which do not build everywhere the CLI builds — a headless server +being the obvious case. + **Requirements:** Rust toolchain + C++ compiler (for openh264). **Recommended:** `ffmpeg` CLI for 10-bit H.264 and H.265/VP9/ProRes/WebM/GIF output. ### Shell Completions @@ -2155,11 +2172,12 @@ crates/ │ └── *.rs # one file per component (Painter implementation) └── rustmotion/src/ ├── cli/ # the `rustmotion` binary (clap subcommands) + ├── studio/ # the `rustmotion-studio` binary (feature `studio`) ├── encode/ # video/audio encoders and muxing └── loader.rs # JSON/HTML → ResolvedScenario ``` -The `rustmotion` crate is where the binary lives — a crate with only a `[lib]` target installs nothing executable via `cargo install`. +The `rustmotion` crate is where both binaries live — a crate with only a `[lib]` target installs nothing executable via `cargo install`, and `cargo install --git ` refuses a repository in which more than one package declares a `[[bin]]`. That second constraint is why the studio is a module of this crate (`src/studio/`, behind the `studio` feature) rather than a package of its own. ## License diff --git a/crates/rustmotion-components/src/lib.rs b/crates/rustmotion-components/src/lib.rs index ff0064d0..8639ef79 100644 --- a/crates/rustmotion-components/src/lib.rs +++ b/crates/rustmotion-components/src/lib.rs @@ -19,7 +19,7 @@ // never lints here regardless of this attribute. This single crate-root // allow silences only that internal noise; it does not extend to any other // crate, so a hand-written construction in `rustmotion-html`, -// `rustmotion-studio`, or this crate's own `tests/` integration suite (each +// the studio, or this crate's own `tests/` integration suite (each // a separate compilation unit) still warns. Verified empirically before // relying on it: see the phase-B report for issue #333. #![allow(deprecated)] diff --git a/crates/rustmotion-core/tests/audit_ws_k.rs b/crates/rustmotion-core/tests/audit_ws_k.rs index 948fc3f6..b12e52c7 100644 --- a/crates/rustmotion-core/tests/audit_ws_k.rs +++ b/crates/rustmotion-core/tests/audit_ws_k.rs @@ -128,7 +128,7 @@ const KNOWN_INERT_FIELDS: &[(&str, &str)] = &[ "target", "Not one of workstream K's 9 named findings — surfaced by this guard test itself, with \ a caveat this test can't resolve on its own: Annotation.target is written by \ - rustmotion-studio (crates/rustmotion-studio/src/editor/annotations.rs:94) as raw JSON \ + the studio (crates/rustmotion/src/studio/editor/annotations.rs:94) as raw JSON \ (a `\"target\": {...}` object literal, not a `.target` field access — this grep-based \ check only matches Rust member access), so it may be consumed by the `apply-annotations` \ Claude Code skill reading the scenario file's raw JSON rather than by any Rust code path. \ diff --git a/crates/rustmotion-studio/Cargo.toml b/crates/rustmotion-studio/Cargo.toml deleted file mode 100644 index b2ecc592..00000000 --- a/crates/rustmotion-studio/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "rustmotion-studio" -version.workspace = true -edition = "2021" -publish = false - -[lib] -path = "src/lib.rs" - -[[bin]] -name = "rustmotion-studio" -path = "src/bin/main.rs" - -[dependencies] -rustmotion = { path = "../rustmotion" } -gpui-kit = "=0.6.6" -gpui-component = "0.6.6" -clap = { version = "4", features = ["derive"] } -notify = "7" -image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] } -smallvec = "1" -serde_json = { version = "1", features = ["preserve_order"] } -tokio = { version = "1", features = ["time"] } -rfd = "0.17" -rodio = { version = "0.22", default-features = false, features = ["playback"] } -dirs = "6.0.0" -palette = { version = "0.7.6", default-features = false, features = ["std"] } -schemars = "0.8" diff --git a/crates/rustmotion/CLAUDE.md b/crates/rustmotion/CLAUDE.md index 370a9c22..0ce0f1d0 100644 --- a/crates/rustmotion/CLAUDE.md +++ b/crates/rustmotion/CLAUDE.md @@ -201,20 +201,38 @@ crates/ └── rustmotion/src/ ├── cli/ # Le binaire `rustmotion` (clap + sous-commandes) │ └── commands/ # validate, render, schema, info + ├── studio/ # Le binaire `rustmotion-studio` (feature `studio`) ├── encode/ # Encodeurs vidéo/audio, mux └── loader.rs # Chargement JSON/HTML → ResolvedScenario ``` -> Le binaire vit dans la crate publiée `rustmotion` : une crate qui n'a qu'une -> `[lib]` n'installe rien d'exécutable via `cargo install`. Il n'y a pas de -> sous-commande `studio` — `rustmotion-studio` dépend de `rustmotion`, donc la -> dépendance inverse serait un cycle. Le studio s'ouvre par son propre binaire. - -### `rustmotion-studio` : aucun commentaire +> Les deux binaires vivent dans la crate publiée `rustmotion` : une crate qui +> n'a qu'une `[lib]` n'installe rien d'exécutable via `cargo install`, et +> `cargo install --git ` **refuse** un dépôt où plus d'un paquet déclare un +> `[[bin]]` (« multiple packages with binaries found » — ni `default-members` ni +> `required-features` ne changent ce décompte). C'est pour ça que le studio est +> un module de cette crate et non un paquet à part : en paquet, il dépendait de +> `loader`/`encode`, donc en faire une dépendance de `rustmotion` était un cycle, +> que cargo refuse même optionnel. +> +> ```bash +> cargo install --git https://github.com/LeadcodeDev/rustmotion # CLI +> cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio # CLI + studio +> ``` +> +> `studio` est hors du build par défaut : il tire gpui et une toolchain GUI, qui +> ne compilent pas partout où le CLI compile. Il n'y a pas de **sous-commande** +> `studio` non plus — elle devrait disparaître du `--help` selon le feature. +> `--workspace` seul ne compile plus le studio : CI passe +> `--features rustmotion/studio` à clippy et aux tests, sans quoi 11 600 lignes +> cessent d'être vérifiées en restant vertes. + +### `src/studio/` : aucun commentaire Le code du studio ne porte **aucun commentaire** — ni `//`, ni `///`, ni `//!`. -La règle ne vaut que pour cette crate : les quatre autres sont publiées, et -vider leurs doc comments viderait leurs pages docs.rs. +La règle suivait la crate ; elle suit maintenant le dossier +`crates/rustmotion/src/studio/`, la fusion du paquet n'ayant rien changé à son +bien-fondé. Quand un commentaire semble nécessaire, c'est le signal qu'il faut **renommer la liaison ou extraire une fonction nommée** : l'explication va dans un diff --git a/crates/rustmotion/Cargo.toml b/crates/rustmotion/Cargo.toml index eb4196ea..b4d35f09 100644 --- a/crates/rustmotion/Cargo.toml +++ b/crates/rustmotion/Cargo.toml @@ -12,14 +12,30 @@ path = "src/lib.rs" # Le binaire vit ici, et pas dans une crate à part, parce que c'est la seule # façon que `cargo install rustmotion` livre une commande : une crate qui n'a -# qu'une `[lib]` installe une bibliothèque et rien d'exécutable. La -# sous-commande `studio` ne peut pas y être : `rustmotion-studio` dépend de -# `rustmotion`, donc la dépendance inverse — même optionnelle — est un cycle -# que cargo refuse. Le studio garde son propre binaire `rustmotion-studio`. +# qu'une `[lib]` installe une bibliothèque et rien d'exécutable. +# +# Le studio a rejoint cette crate (`src/studio/`) pour la même raison, poussée +# d'un cran : `cargo install --git ` sans nom de paquet refuse un dépôt où +# plus d'un paquet déclare un `[[bin]]` (« multiple packages with binaries +# found »), et ni `default-members` ni `required-features` ne changent ce +# décompte — vérifié, pas supposé. Un seul paquet peut donc porter des +# binaires. Il était un paquet à part, qui dépendait de `rustmotion` pour +# `loader` et `encode` : en faire une dépendance de `rustmotion` aurait été un +# cycle, que cargo refuse même optionnel. Devenu un module, il n'y a plus deux +# paquets, donc plus de cycle, et ses chemins `rustmotion::…` restent valides +# tels quels grâce au `extern crate self as rustmotion` de `lib.rs`. [[bin]] name = "rustmotion" path = "src/bin/main.rs" +# Derrière `required-features`, hors du build par défaut : gpui et sa toolchain +# GUI ne doivent pas entrer dans un `cargo install rustmotion` fait sur une +# machine sans affichage, où elles peuvent ne pas compiler du tout. +[[bin]] +name = "rustmotion-studio" +path = "src/bin/studio.rs" +required-features = ["studio"] + [dependencies] rustmotion-core.workspace = true rustmotion-components = { workspace = true, features = ["lottie-native"] } @@ -50,7 +66,40 @@ tiny-skia = "0.11" ureq = "3" rustfft = "6" +# Dépendances du studio (`src/studio/`), toutes optionnelles : le feature +# `studio` les active, le build par défaut ne les voit pas. +gpui-kit = { version = "=0.6.6", optional = true } +gpui-component = { version = "0.6.6", optional = true } +smallvec = { version = "1", optional = true } +tokio = { version = "1", features = ["time"], optional = true } +rfd = { version = "0.17", optional = true } +rodio = { version = "0.22", default-features = false, features = [ + "playback", +], optional = true } +palette = { version = "0.7.6", default-features = false, features = [ + "std", +], optional = true } + [features] +## Compile et installe aussi le binaire `rustmotion-studio` : +## `cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio`. +## Hors du build par défaut parce qu'il tire gpui et sa toolchain GUI, qui ne +## compilent pas partout où le CLI compile. +## +## `serde_json/preserve_order` en fait partie parce que le studio réécrit les +## fichiers de scénario de l'utilisateur et ne doit pas en réordonner les clés. +## C'est aussi ce que voudrait `validate --fix`, mais l'activer par défaut +## changerait l'ordre de ses réécritures : à décider ailleurs qu'ici. +studio = [ + "dep:gpui-kit", + "dep:gpui-component", + "dep:smallvec", + "dep:tokio", + "dep:rfd", + "dep:rodio", + "dep:palette", + "serde_json/preserve_order", +] # Opt-in: integration tests that shell out to a real ffmpeg binary. ffmpeg_integration = [] ## Re-expose native Lottie decoding (default-on). Activating this feature here diff --git a/crates/rustmotion-studio/src/bin/main.rs b/crates/rustmotion/src/bin/studio.rs similarity index 64% rename from crates/rustmotion-studio/src/bin/main.rs rename to crates/rustmotion/src/bin/studio.rs index b45610d2..018716b8 100644 --- a/crates/rustmotion-studio/src/bin/main.rs +++ b/crates/rustmotion/src/bin/studio.rs @@ -1,5 +1,5 @@ fn main() { - if let Err(e) = rustmotion_studio::run() { + if let Err(e) = rustmotion::studio::run() { eprintln!("Error: {}", e); std::process::exit(1); } diff --git a/crates/rustmotion/src/cli/mod.rs b/crates/rustmotion/src/cli/mod.rs index 4a0b6156..c8392f2b 100644 --- a/crates/rustmotion/src/cli/mod.rs +++ b/crates/rustmotion/src/cli/mod.rs @@ -1,11 +1,13 @@ //! Le binaire `rustmotion` : analyse des arguments et aiguillage vers //! `commands`. //! -//! Il n'y a pas de sous-commande `studio` ici. Le studio est une app Dioxus -//! qui dépend de cette crate ; l'appeler depuis ce module ferait dépendre -//! `rustmotion` de `rustmotion-studio`, donc d'elle-même, et cargo refuse le -//! cycle. Le studio s'ouvre par son propre binaire, `rustmotion-studio -f -//! scenario.json`. +//! Il n'y a pas de sous-commande `studio` ici, et c'est délibéré : le studio +//! n'entre dans le build que derrière le feature `studio`, donc une +//! sous-commande devrait soit disparaître du `--help` selon le feature, soit +//! échouer à l'exécution en expliquant qu'il faut réinstaller. Il garde son +//! binaire, `rustmotion-studio -f scenario.json`, que `cargo install +//! --features studio` livre à côté de celui-ci. Le code, lui, vit dans cette +//! crate depuis la fusion du paquet — voir `crate::studio`. mod claude_md; mod commands; diff --git a/crates/rustmotion/src/lib.rs b/crates/rustmotion/src/lib.rs index 70a89d65..7c481fdd 100644 --- a/crates/rustmotion/src/lib.rs +++ b/crates/rustmotion/src/lib.rs @@ -31,5 +31,12 @@ pub mod encode; pub mod include; pub mod loader; +/// Le studio de prévisualisation. Un module et non une crate à part parce que +/// `cargo install --git ` refuse un dépôt où plus d'un paquet déclare un +/// binaire, et qu'une crate séparée dépendant de `loader`/`encode` ne pouvait +/// pas devenir une dépendance de celle-ci sans cycle. Voir le `Cargo.toml`. +#[cfg(feature = "studio")] +pub mod studio; + #[cfg(test)] mod tests; diff --git a/crates/rustmotion-studio/src/app/mod.rs b/crates/rustmotion/src/studio/app/mod.rs similarity index 90% rename from crates/rustmotion-studio/src/app/mod.rs rename to crates/rustmotion/src/studio/app/mod.rs index a321ea77..23055667 100644 --- a/crates/rustmotion-studio/src/app/mod.rs +++ b/crates/rustmotion/src/studio/app/mod.rs @@ -11,8 +11,8 @@ use rustmotion::engine; use rustmotion::error::Result; use rustmotion::schema::ResolvedScenario; -use crate::library::{LibraryState, SharedLibrary, WatchMsg}; -use crate::scenario::{empty_scenario, Shared, StudioModel, View}; +use crate::studio::library::{LibraryState, SharedLibrary, WatchMsg}; +use crate::studio::scenario::{empty_scenario, Shared, StudioModel, View}; fn default_workspace() -> PathBuf { std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) @@ -86,7 +86,7 @@ pub fn run_preview_root( } else { View::Library }; - let theme_pref = crate::theme::persist::load_theme_pref(); + let theme_pref = crate::studio::theme::persist::load_theme_pref(); gpui_kit::application() .with_assets(gpui_kit::assets::AllAssets::new("")) @@ -158,8 +158,8 @@ fn spawn_watcher(shared: Shared) -> Sender { WatchMsg::Changed => { if let Some(p) = current.clone() { if let Ok(content) = std::fs::read_to_string(&p) { - if crate::scenario::is_self_write( - &crate::scenario::self_write_slot(), + if crate::studio::scenario::is_self_write( + &crate::studio::scenario::self_write_slot(), &p, &content, ) { @@ -168,7 +168,10 @@ fn spawn_watcher(shared: Shared) -> Sender { } let (scenario, error) = match rustmotion::loader::load_input(&p) { Ok(s) => (s, None), - Err(e) => (crate::scenario::empty_scenario(), Some(e.to_string())), + Err(e) => ( + crate::studio::scenario::empty_scenario(), + Some(e.to_string()), + ), }; let mut m = shared.lock().unwrap_or_else(|e| e.into_inner()); let g = m.generation.wrapping_add(1); diff --git a/crates/rustmotion-studio/src/app/overlays.rs b/crates/rustmotion/src/studio/app/overlays.rs similarity index 100% rename from crates/rustmotion-studio/src/app/overlays.rs rename to crates/rustmotion/src/studio/app/overlays.rs diff --git a/crates/rustmotion-studio/src/app/root.rs b/crates/rustmotion/src/studio/app/root.rs similarity index 85% rename from crates/rustmotion-studio/src/app/root.rs rename to crates/rustmotion/src/studio/app/root.rs index 2e285ae8..b4f27951 100644 --- a/crates/rustmotion-studio/src/app/root.rs +++ b/crates/rustmotion/src/studio/app/root.rs @@ -2,11 +2,11 @@ use gpui_component::ActiveTheme; use gpui_kit::base::v_flex; use gpui_kit::*; -use crate::app::overlays; -use crate::app::state::StudioState; -use crate::editor::view::EditorView; -use crate::library::Library; -use crate::scenario::View; +use crate::studio::app::overlays; +use crate::studio::app::state::StudioState; +use crate::studio::editor::view::EditorView; +use crate::studio::library::Library; +use crate::studio::scenario::View; pub struct StudioRoot { state: Entity, diff --git a/crates/rustmotion-studio/src/app/state.rs b/crates/rustmotion/src/studio/app/state.rs similarity index 91% rename from crates/rustmotion-studio/src/app/state.rs rename to crates/rustmotion/src/studio/app/state.rs index 547e7b7e..d57d50af 100644 --- a/crates/rustmotion-studio/src/app/state.rs +++ b/crates/rustmotion/src/studio/app/state.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use gpui_kit::RenderImage; -use crate::editor::diff_panel::DiffSide; -use crate::library::SharedLibrary; -use crate::scenario::{Shared, View}; +use crate::studio::editor::diff_panel::DiffSide; +use crate::studio::library::SharedLibrary; +use crate::studio::scenario::{Shared, View}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Selection { diff --git a/crates/rustmotion-studio/src/app/window.rs b/crates/rustmotion/src/studio/app/window.rs similarity index 87% rename from crates/rustmotion-studio/src/app/window.rs rename to crates/rustmotion/src/studio/app/window.rs index 3b673478..aad7b068 100644 --- a/crates/rustmotion-studio/src/app/window.rs +++ b/crates/rustmotion/src/studio/app/window.rs @@ -4,11 +4,11 @@ use gpui_kit::{ WindowOptions, }; -use crate::app::root::StudioRoot; -use crate::app::state::{StudioState, ThemePref}; -use crate::library::SharedLibrary; -use crate::scenario::{Shared, View}; -use crate::theme; +use crate::studio::app::root::StudioRoot; +use crate::studio::app::state::{StudioState, ThemePref}; +use crate::studio::library::SharedLibrary; +use crate::studio::scenario::{Shared, View}; +use crate::studio::theme; pub fn open( shared: Shared, diff --git a/crates/rustmotion-studio/src/editor/annotations.rs b/crates/rustmotion/src/studio/editor/annotations.rs similarity index 98% rename from crates/rustmotion-studio/src/editor/annotations.rs rename to crates/rustmotion/src/studio/editor/annotations.rs index cc7b4d94..fae7a79e 100644 --- a/crates/rustmotion-studio/src/editor/annotations.rs +++ b/crates/rustmotion/src/studio/editor/annotations.rs @@ -7,8 +7,8 @@ use gpui_kit::{ ParentElement, Render, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, }; -use crate::app::state::EditorState; -use crate::scenario::{ +use crate::studio::app::state::EditorState; +use crate::studio::scenario::{ append_annotation, append_sidecar_annotation, history_slot, record_edit, remove_annotation, remove_sidecar_annotation, Shared, }; @@ -245,7 +245,10 @@ fn annotation_card( .xsmall() .on_click(move |_, _, cx| { goto_editor.update(cx, |state, cx| { - crate::editor::playback::seek_from_user(state, frame as u32); + crate::studio::editor::playback::seek_from_user( + state, + frame as u32, + ); cx.notify(); }); }), @@ -339,7 +342,7 @@ mod tests { use super::*; use std::sync::{Arc, Mutex}; - use crate::scenario::StudioModel; + use crate::studio::scenario::StudioModel; const SCENARIO_JSON: &str = r##"{ "video": { "width": 10, "height": 10, "fps": 10 }, diff --git a/crates/rustmotion-studio/src/editor/audio.rs b/crates/rustmotion/src/studio/editor/audio.rs similarity index 100% rename from crates/rustmotion-studio/src/editor/audio.rs rename to crates/rustmotion/src/studio/editor/audio.rs diff --git a/crates/rustmotion-studio/src/editor/diff_panel.rs b/crates/rustmotion/src/studio/editor/diff_panel.rs similarity index 97% rename from crates/rustmotion-studio/src/editor/diff_panel.rs rename to crates/rustmotion/src/studio/editor/diff_panel.rs index 043156f9..25202148 100644 --- a/crates/rustmotion-studio/src/editor/diff_panel.rs +++ b/crates/rustmotion/src/studio/editor/diff_panel.rs @@ -6,8 +6,8 @@ use gpui_kit::{ SharedString, StatefulInteractiveElement, Styled, Window, }; -use crate::app::state::{EditorState, Selection}; -use crate::scenario::{ChangeKind, ElementChange, Shared}; +use crate::studio::app::state::{EditorState, Selection}; +use crate::studio::scenario::{ChangeKind, ElementChange, Shared}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffSide { @@ -191,7 +191,7 @@ fn change_entry( .on_click(move |_, _, cx| { if let Some(frame) = frame_for_change(&shared, &click_target) { editor.update(cx, |state, cx| { - crate::editor::playback::seek_from_user(state, frame); + crate::studio::editor::playback::seek_from_user(state, frame); cx.notify(); }); } @@ -279,7 +279,7 @@ mod tests { use super::*; use std::sync::{Arc, Mutex}; - use crate::scenario::StudioModel; + use crate::studio::scenario::StudioModel; const TWO_SCENES: &str = r##"{ "video": { "width": 10, "height": 10, "fps": 10 }, diff --git a/crates/rustmotion-studio/src/editor/export.rs b/crates/rustmotion/src/studio/editor/export.rs similarity index 99% rename from crates/rustmotion-studio/src/editor/export.rs rename to crates/rustmotion/src/studio/editor/export.rs index 803a1958..fef31732 100644 --- a/crates/rustmotion-studio/src/editor/export.rs +++ b/crates/rustmotion/src/studio/editor/export.rs @@ -6,7 +6,7 @@ use gpui_component::notification::Notification; use gpui_component::WindowExt as _; use gpui_kit::{div, Context, IntoElement, Render, Window}; -use crate::scenario::Shared; +use crate::studio::scenario::Shared; #[derive(Debug, Clone, PartialEq)] pub enum ExportStatus { diff --git a/crates/rustmotion-studio/src/editor/frames.rs b/crates/rustmotion/src/studio/editor/frames.rs similarity index 100% rename from crates/rustmotion-studio/src/editor/frames.rs rename to crates/rustmotion/src/studio/editor/frames.rs diff --git a/crates/rustmotion-studio/src/editor/inspector/controls.rs b/crates/rustmotion/src/studio/editor/inspector/controls.rs similarity index 97% rename from crates/rustmotion-studio/src/editor/inspector/controls.rs rename to crates/rustmotion/src/studio/editor/inspector/controls.rs index 71a887e9..6e0ce347 100644 --- a/crates/rustmotion-studio/src/editor/inspector/controls.rs +++ b/crates/rustmotion/src/studio/editor/inspector/controls.rs @@ -12,7 +12,7 @@ use gpui_kit::{ SharedString, Styled, Window, }; -use crate::editor::properties::{FillMode, PropKind, PropSpec}; +use crate::studio::editor::properties::{FillMode, PropKind, PropSpec}; use super::sections::{fmt_num, fmt_unit, parse_num}; use super::write::Target; @@ -488,7 +488,7 @@ impl InspectorPanel { PropKind::Color => ScalarWidget::Color(self.build_color(value, target, window, cx)), PropKind::Float => { if let Some((min, max, step)) = - crate::editor::properties::slider_range(row_prop_name(&target)) + crate::studio::editor::properties::slider_range(row_prop_name(&target)) { let (slider, text) = self.build_slider(value, min, max, step, "", target, window, cx); @@ -506,7 +506,10 @@ impl InspectorPanel { ScalarWidget::Number(self.build_text(value, None, target, window, cx)) } PropKind::String - if crate::editor::properties::is_multiline(row_prop_name(&target), value) => + if crate::studio::editor::properties::is_multiline( + row_prop_name(&target), + value, + ) => { let monospace = row_prop_name(&target) == "code"; let state = self.build_multiline(value, target, window, cx); @@ -514,7 +517,7 @@ impl InspectorPanel { } PropKind::Unit | PropKind::String => { let placeholder = - crate::editor::properties::engine_placeholder(row_prop_name(&target)); + crate::studio::editor::properties::engine_placeholder(row_prop_name(&target)); ScalarWidget::Text(self.build_text(value, placeholder, target, window, cx)) } _ => ScalarWidget::Json(self.build_json(value, target, window, cx)), @@ -678,7 +681,7 @@ impl InspectorPanel { .collect(); let angle_text = angle_state.read(cx).value().to_string(); let angle = parse_num(&angle_text).unwrap_or(0.0); - let value = crate::editor::properties::fill_to_value(mode, &colors, angle); + let value = crate::studio::editor::properties::fill_to_value(mode, &colors, angle); this.write_root_field(&field, value, window, cx); } }; @@ -784,7 +787,8 @@ impl InspectorPanel { .into_iter() .filter_map(|v| v.as_str().map(str::to_string)) .collect(); - let next = crate::editor::properties::next_entries_on_add(¤t, "#ffffff", prefill); + let next = + crate::studio::editor::properties::next_entries_on_add(¤t, "#ffffff", prefill); let value: Vec = next.into_iter().map(serde_json::Value::String).collect(); self.write_root_field(field, serde_json::Value::Array(value), window, cx); @@ -821,11 +825,11 @@ impl InspectorPanel { cx: &mut Context, ) { let raw = self.current_value(field); - let (_, mut colors, angle) = crate::editor::properties::parse_fill(&raw); + let (_, mut colors, angle) = crate::studio::editor::properties::parse_fill(&raw); if colors.is_empty() { colors.push("#ffffff".to_string()); } - let value = crate::editor::properties::fill_to_value(mode, &colors, angle); + let value = crate::studio::editor::properties::fill_to_value(mode, &colors, angle); self.write_root_field(field, value, window, cx); self.force_rebuild = true; cx.notify(); @@ -844,7 +848,8 @@ impl InspectorPanel { cx: &mut Context, ) -> Row { let value_str = display_value; - let prefill = crate::editor::properties::palette_prefill(host_tag, &spec.name, true); + let prefill = + crate::studio::editor::properties::palette_prefill(host_tag, &spec.name, true); let widget = match &spec.kind { PropKind::ColorList => { let colors: Vec = raw_value @@ -863,7 +868,7 @@ impl InspectorPanel { .get(&spec.name) .cloned() .unwrap_or(serde_json::Value::Null); - let (mode, colors, angle) = crate::editor::properties::parse_fill(&raw); + let (mode, colors, angle) = crate::studio::editor::properties::parse_fill(&raw); let colors = if colors.is_empty() { vec!["#ffffff".to_string()] } else { diff --git a/crates/rustmotion-studio/src/editor/inspector/mod.rs b/crates/rustmotion/src/studio/editor/inspector/mod.rs similarity index 98% rename from crates/rustmotion-studio/src/editor/inspector/mod.rs rename to crates/rustmotion/src/studio/editor/inspector/mod.rs index 16cb6f48..1726ed70 100644 --- a/crates/rustmotion-studio/src/editor/inspector/mod.rs +++ b/crates/rustmotion/src/studio/editor/inspector/mod.rs @@ -13,13 +13,13 @@ use gpui_kit::{ Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window, }; -use crate::app::state::EditorState; -use crate::editor::annotations::AnnotationCaptureBox; -use crate::editor::properties::{ +use crate::studio::app::state::EditorState; +use crate::studio::editor::annotations::AnnotationCaptureBox; +use crate::studio::editor::properties::{ component_props, css_family, css_row_value, css_section_props, effective_element, visible_sections, CssSection, PropKind, }; -use crate::scenario::{read_field, read_style_object, scene_duration_for_pointer, Shared}; +use crate::studio::scenario::{read_field, read_style_object, scene_duration_for_pointer, Shared}; use controls::{Row, ScalarWidget}; use sections::{family, Ctrl, Family}; @@ -50,7 +50,7 @@ enum CuratedRow { pub struct InspectorPanel { shared: Shared, editor: Entity, - selection: Option, + selection: Option, controls_pointer: Option, force_rebuild: bool, open_picker: Option, @@ -574,7 +574,7 @@ fn collapsible_section( fn render_curated_row( row: &CuratedRow, shared: &Shared, - selection: Option<&crate::app::state::Selection>, + selection: Option<&crate::studio::app::state::Selection>, cx: &mut Context, ) -> gpui_kit::AnyElement { match row { diff --git a/crates/rustmotion-studio/src/editor/inspector/sections.rs b/crates/rustmotion/src/studio/editor/inspector/sections.rs similarity index 100% rename from crates/rustmotion-studio/src/editor/inspector/sections.rs rename to crates/rustmotion/src/studio/editor/inspector/sections.rs diff --git a/crates/rustmotion-studio/src/editor/inspector/write.rs b/crates/rustmotion/src/studio/editor/inspector/write.rs similarity index 97% rename from crates/rustmotion-studio/src/editor/inspector/write.rs rename to crates/rustmotion/src/studio/editor/inspector/write.rs index 17a51189..f2030bfb 100644 --- a/crates/rustmotion-studio/src/editor/inspector/write.rs +++ b/crates/rustmotion/src/studio/editor/inspector/write.rs @@ -2,8 +2,8 @@ use std::time::Duration; use gpui_kit::{Context, Window}; -use crate::editor::properties::PropKind; -use crate::scenario::{ +use crate::studio::editor::properties::PropKind; +use crate::studio::scenario::{ apply_optimistic, history_slot, note_self_write, pending_write_slot, queue_mutation, resolve_flush, self_write_slot, set_saving, take_pending, Mutation, }; @@ -53,7 +53,7 @@ pub fn mutate_nested( key: &str, leaf: serde_json::Value, ) -> serde_json::Value { - crate::editor::properties::mutate_object_field(root_value, key, leaf) + crate::studio::editor::properties::mutate_object_field(root_value, key, leaf) } impl InspectorPanel { @@ -224,7 +224,7 @@ impl InspectorPanel { Err(e) => Err(e.clone()), }; if let (Ok(true), Ok(snapshot)) = (&result, &read) { - crate::scenario::record_edit(&history_slot(), &path, snapshot.clone()); + crate::studio::scenario::record_edit(&history_slot(), &path, snapshot.clone()); } let mut m = shared.lock().unwrap_or_else(|e| e.into_inner()); diff --git a/crates/rustmotion-studio/src/editor/mod.rs b/crates/rustmotion/src/studio/editor/mod.rs similarity index 100% rename from crates/rustmotion-studio/src/editor/mod.rs rename to crates/rustmotion/src/studio/editor/mod.rs diff --git a/crates/rustmotion-studio/src/editor/overlay.rs b/crates/rustmotion/src/studio/editor/overlay.rs similarity index 98% rename from crates/rustmotion-studio/src/editor/overlay.rs rename to crates/rustmotion/src/studio/editor/overlay.rs index 2854506c..1ff18547 100644 --- a/crates/rustmotion-studio/src/editor/overlay.rs +++ b/crates/rustmotion/src/studio/editor/overlay.rs @@ -5,8 +5,8 @@ use gpui_kit::{ SharedString, StatefulInteractiveElement, Styled, }; -use crate::app::state::Selection; -use crate::scenario::ChangeKind; +use crate::studio::app::state::Selection; +use crate::studio::scenario::ChangeKind; use super::frames::HitPct; use super::view::EditorView; diff --git a/crates/rustmotion-studio/src/editor/playback.rs b/crates/rustmotion/src/studio/editor/playback.rs similarity index 99% rename from crates/rustmotion-studio/src/editor/playback.rs rename to crates/rustmotion/src/studio/editor/playback.rs index e4648fb9..beaa32e0 100644 --- a/crates/rustmotion-studio/src/editor/playback.rs +++ b/crates/rustmotion/src/studio/editor/playback.rs @@ -9,8 +9,8 @@ use gpui_kit::{ ParentElement, RenderOnce, SharedString, Styled, Window, }; -use crate::app::state::EditorState; -use crate::scenario::Shared; +use crate::studio::app::state::EditorState; +use crate::studio::scenario::Shared; use super::diff_panel::DiffSide; use super::prefetch::{set_preview_scale_pct, PREVIEW_SCALE_CHOICES}; @@ -442,7 +442,7 @@ mod tests { show_annotations: false, show_hits: true, diff_active: false, - diff_side: crate::editor::diff_panel::DiffSide::B, + diff_side: crate::studio::editor::diff_panel::DiffSide::B, preview_scale: 50, frame: None, } diff --git a/crates/rustmotion-studio/src/editor/prefetch.rs b/crates/rustmotion/src/studio/editor/prefetch.rs similarity index 98% rename from crates/rustmotion-studio/src/editor/prefetch.rs rename to crates/rustmotion/src/studio/editor/prefetch.rs index b7d8217b..7eeff846 100644 --- a/crates/rustmotion-studio/src/editor/prefetch.rs +++ b/crates/rustmotion/src/studio/editor/prefetch.rs @@ -7,7 +7,7 @@ use std::time::Duration; use rustmotion::encode::video::FrameTask; use rustmotion::schema::ResolvedScenario; -use crate::scenario::{baseline_slot, get_baseline}; +use crate::studio::scenario::{baseline_slot, get_baseline}; use super::diff_panel::DiffSide; use super::frames::{baseline_arcs, render_frame}; @@ -231,7 +231,7 @@ pub fn ensure_prefetcher() { .unwrap_or(4); for _ in 0..worker_count(cores) { let _ = std::thread::Builder::new() - .stack_size(crate::editor::frames::RENDER_STACK) + .stack_size(crate::studio::editor::frames::RENDER_STACK) .spawn(prefetch_loop); } }); @@ -536,7 +536,7 @@ mod tests { #[test] #[ignore] fn soak_full_pipeline_rss() { - use crate::editor::frames::frame_hits; + use crate::studio::editor::frames::frame_hits; use std::sync::atomic::{AtomicU32, Ordering}; let Ok(src) = std::fs::read_to_string("../../examples/dynamic-glass.json") else { @@ -592,7 +592,7 @@ mod tests { if !hit { MISSES.fetch_add(1, Ordering::Relaxed); let bytes = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - crate::editor::frames::render_frame( + crate::studio::editor::frames::render_frame( &s2, &t2, current, diff --git a/crates/rustmotion-studio/src/editor/properties.rs b/crates/rustmotion/src/studio/editor/properties.rs similarity index 100% rename from crates/rustmotion-studio/src/editor/properties.rs rename to crates/rustmotion/src/studio/editor/properties.rs diff --git a/crates/rustmotion-studio/src/editor/surface.rs b/crates/rustmotion/src/studio/editor/surface.rs similarity index 98% rename from crates/rustmotion-studio/src/editor/surface.rs rename to crates/rustmotion/src/studio/editor/surface.rs index 8892c894..1af2e906 100644 --- a/crates/rustmotion-studio/src/editor/surface.rs +++ b/crates/rustmotion/src/studio/editor/surface.rs @@ -11,8 +11,8 @@ use gpui_kit::{ use rustmotion::encode::video::FrameTask; use rustmotion::schema::ResolvedScenario; -use crate::app::state::EditorState; -use crate::scenario::Shared; +use crate::studio::app::state::EditorState; +use crate::studio::scenario::Shared; use super::diff_panel::DiffSide; use super::frames::render_frame_rgba_deep; @@ -181,7 +181,7 @@ mod tests { } fn shared_model() -> Shared { - use crate::scenario::StudioModel; + use crate::studio::scenario::StudioModel; use std::sync::{Arc as StdArc, Mutex}; let scenario = rustmotion::loader::load_scenario_from_source(None, Some(SCENARIO)).unwrap(); StdArc::new(Mutex::new(StudioModel::new(scenario, None, None))) diff --git a/crates/rustmotion-studio/src/editor/topbar.rs b/crates/rustmotion/src/studio/editor/topbar.rs similarity index 97% rename from crates/rustmotion-studio/src/editor/topbar.rs rename to crates/rustmotion/src/studio/editor/topbar.rs index 4c731d8e..f8d14341 100644 --- a/crates/rustmotion-studio/src/editor/topbar.rs +++ b/crates/rustmotion/src/studio/editor/topbar.rs @@ -8,8 +8,10 @@ use gpui_kit::{ Window, }; -use crate::app::state::{EditorState, StudioState}; -use crate::scenario::{baseline_slot, history_slot, redo, set_baseline, undo, Shared, View}; +use crate::studio::app::state::{EditorState, StudioState}; +use crate::studio::scenario::{ + baseline_slot, history_slot, redo, set_baseline, undo, Shared, View, +}; use super::export::{export_label, ExportStatus}; @@ -215,7 +217,7 @@ impl RenderOnce for TopBar { .xsmall() .on_click(move |_, window, cx| { let next = studio.read(cx).theme_pref.next(); - crate::theme::set(&studio, next, window, cx); + crate::studio::theme::set(&studio, next, window, cx); }), ) .child( @@ -321,8 +323,8 @@ impl RenderOnce for TopBar { } } -fn theme_pref_icon(pref: crate::app::state::ThemePref) -> Option { - use crate::app::state::ThemePref; +fn theme_pref_icon(pref: crate::studio::app::state::ThemePref) -> Option { + use crate::studio::app::state::ThemePref; match pref { ThemePref::Dark => Some(IconName::Moon), ThemePref::Light => Some(IconName::Sun), diff --git a/crates/rustmotion-studio/src/editor/view.rs b/crates/rustmotion/src/studio/editor/view.rs similarity index 99% rename from crates/rustmotion-studio/src/editor/view.rs rename to crates/rustmotion/src/studio/editor/view.rs index 12d9ddfb..8652a6f3 100644 --- a/crates/rustmotion-studio/src/editor/view.rs +++ b/crates/rustmotion/src/studio/editor/view.rs @@ -6,8 +6,8 @@ use gpui_kit::{ StatefulInteractiveElement, Styled, Subscription, Window, }; -use crate::app::state::{EditorState, StudioState}; -use crate::scenario::{ +use crate::studio::app::state::{EditorState, StudioState}; +use crate::studio::scenario::{ baseline_slot, diff_scenarios, get_baseline, history_slot, list_annotations, redo, undo, ChangeKind, Shared, }; @@ -610,7 +610,7 @@ mod tests { playing: true, muted: false, rev: 9, - selected: Some(crate::app::state::Selection { + selected: Some(crate::studio::app::state::Selection { node_id: 7, pointer: "/scenes/2/children/3".into(), kind: "text".into(), diff --git a/crates/rustmotion-studio/src/library/data.rs b/crates/rustmotion/src/studio/library/data.rs similarity index 100% rename from crates/rustmotion-studio/src/library/data.rs rename to crates/rustmotion/src/studio/library/data.rs diff --git a/crates/rustmotion-studio/src/library/mod.rs b/crates/rustmotion/src/studio/library/mod.rs similarity index 100% rename from crates/rustmotion-studio/src/library/mod.rs rename to crates/rustmotion/src/studio/library/mod.rs diff --git a/crates/rustmotion-studio/src/library/view.rs b/crates/rustmotion/src/studio/library/view.rs similarity index 97% rename from crates/rustmotion-studio/src/library/view.rs rename to crates/rustmotion/src/studio/library/view.rs index 44617c6e..e59490c7 100644 --- a/crates/rustmotion-studio/src/library/view.rs +++ b/crates/rustmotion/src/studio/library/view.rs @@ -11,11 +11,11 @@ use gpui_kit::{ Pixels, Render, RenderImage, StatefulInteractiveElement, Styled, Subscription, Window, }; -use crate::app::state::StudioState; -use crate::editor::frames::render_frame_rgba_deep; -use crate::editor::surface::{frame_from_rgba, frame_surface}; -use crate::library::{ScenarioEntry, SharedLibrary}; -use crate::scenario::{empty_scenario, StudioModel, View}; +use crate::studio::app::state::StudioState; +use crate::studio::editor::frames::render_frame_rgba_deep; +use crate::studio::editor::surface::{frame_from_rgba, frame_surface}; +use crate::studio::library::{ScenarioEntry, SharedLibrary}; +use crate::studio::scenario::{empty_scenario, StudioModel, View}; const SIDEBAR_WIDTH: Pixels = px(280.); const CARD_MIN_WIDTH: Pixels = px(240.); @@ -100,7 +100,7 @@ fn open_scenario(state: &Entity, path: PathBuf, cx: &mut App) { lib.note_opened(&path); lib.retarget_watch(&path); } - crate::app::spawn_scenario_warmup(shared.clone()); + crate::studio::app::spawn_scenario_warmup(shared.clone()); state.update(cx, |studio, cx| { studio.view = View::Editor; cx.notify(); @@ -407,7 +407,7 @@ mod tests { use super::*; use std::sync::Mutex; - use crate::library::LibraryState; + use crate::studio::library::LibraryState; fn examples_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples") diff --git a/crates/rustmotion-studio/src/lib.rs b/crates/rustmotion/src/studio/mod.rs similarity index 100% rename from crates/rustmotion-studio/src/lib.rs rename to crates/rustmotion/src/studio/mod.rs diff --git a/crates/rustmotion-studio/src/scenario/baseline.rs b/crates/rustmotion/src/studio/scenario/baseline.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/baseline.rs rename to crates/rustmotion/src/studio/scenario/baseline.rs diff --git a/crates/rustmotion-studio/src/scenario/diff.rs b/crates/rustmotion/src/studio/scenario/diff.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/diff.rs rename to crates/rustmotion/src/studio/scenario/diff.rs diff --git a/crates/rustmotion-studio/src/scenario/edit.rs b/crates/rustmotion/src/studio/scenario/edit.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/edit.rs rename to crates/rustmotion/src/studio/scenario/edit.rs diff --git a/crates/rustmotion-studio/src/scenario/history.rs b/crates/rustmotion/src/studio/scenario/history.rs similarity index 99% rename from crates/rustmotion-studio/src/scenario/history.rs rename to crates/rustmotion/src/studio/scenario/history.rs index a67de43b..fcd0cba4 100644 --- a/crates/rustmotion-studio/src/scenario/history.rs +++ b/crates/rustmotion/src/studio/scenario/history.rs @@ -149,7 +149,7 @@ fn step(shared: &Shared, slot: &SharedHistory, is_undo: bool) { #[cfg(test)] mod tests { use super::*; - use crate::scenario::{empty_scenario, StudioModel}; + use crate::studio::scenario::{empty_scenario, StudioModel}; #[test] fn record_caps_at_64_evicting_oldest() { diff --git a/crates/rustmotion-studio/src/scenario/mod.rs b/crates/rustmotion/src/studio/scenario/mod.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/mod.rs rename to crates/rustmotion/src/studio/scenario/mod.rs diff --git a/crates/rustmotion-studio/src/scenario/model.rs b/crates/rustmotion/src/studio/scenario/model.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/model.rs rename to crates/rustmotion/src/studio/scenario/model.rs diff --git a/crates/rustmotion-studio/src/scenario/optimistic.rs b/crates/rustmotion/src/studio/scenario/optimistic.rs similarity index 98% rename from crates/rustmotion-studio/src/scenario/optimistic.rs rename to crates/rustmotion/src/studio/scenario/optimistic.rs index 66b886a1..37641be8 100644 --- a/crates/rustmotion-studio/src/scenario/optimistic.rs +++ b/crates/rustmotion/src/studio/scenario/optimistic.rs @@ -223,7 +223,7 @@ fn rebuild_from_value(raw: &Value) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::scenario::{empty_scenario, StudioModel}; + use crate::studio::scenario::{empty_scenario, StudioModel}; use serde_json::json; fn temp_json(tag: &str, content: &str) -> PathBuf { @@ -464,10 +464,10 @@ mod tests { let after = DOC.replace("48", "72"); let path = temp_json("undo_flow", &after); let shared = model_for(&path); - let hist: crate::scenario::SharedHistory = Arc::new(Mutex::new(Default::default())); - crate::scenario::record_edit(&hist, &path, before.to_string()); + let hist: crate::studio::scenario::SharedHistory = Arc::new(Mutex::new(Default::default())); + crate::studio::scenario::record_edit(&hist, &path, before.to_string()); - crate::scenario::undo(&shared, &hist); + crate::studio::scenario::undo(&shared, &hist); let disk = std::fs::read_to_string(&path).unwrap(); assert_eq!(disk, before); diff --git a/crates/rustmotion-studio/src/scenario/sidecar.rs b/crates/rustmotion/src/studio/scenario/sidecar.rs similarity index 100% rename from crates/rustmotion-studio/src/scenario/sidecar.rs rename to crates/rustmotion/src/studio/scenario/sidecar.rs diff --git a/crates/rustmotion-studio/src/theme/mod.rs b/crates/rustmotion/src/studio/theme/mod.rs similarity index 94% rename from crates/rustmotion-studio/src/theme/mod.rs rename to crates/rustmotion/src/studio/theme/mod.rs index 3f04c619..0e43f38c 100644 --- a/crates/rustmotion-studio/src/theme/mod.rs +++ b/crates/rustmotion/src/studio/theme/mod.rs @@ -3,7 +3,7 @@ pub mod persist; use gpui_component::{Theme, ThemeMode}; use gpui_kit::{App, Entity, Window}; -use crate::app::state::{StudioState, ThemePref}; +use crate::studio::app::state::{StudioState, ThemePref}; pub fn apply(pref: ThemePref, window: Option<&mut Window>, cx: &mut App) { match pref { diff --git a/crates/rustmotion-studio/src/theme/persist.rs b/crates/rustmotion/src/studio/theme/persist.rs similarity index 97% rename from crates/rustmotion-studio/src/theme/persist.rs rename to crates/rustmotion/src/studio/theme/persist.rs index c79284f5..060aa173 100644 --- a/crates/rustmotion-studio/src/theme/persist.rs +++ b/crates/rustmotion/src/studio/theme/persist.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::app::state::ThemePref; +use crate::studio::app::state::ThemePref; fn theme_pref_path() -> Option { dirs::config_dir().map(|d| d.join("rustmotion").join("theme.json")) diff --git a/crates/rustmotion-studio/tests/audit_ws_g.rs b/crates/rustmotion/tests/studio_audit_ws_g.rs similarity index 98% rename from crates/rustmotion-studio/tests/audit_ws_g.rs rename to crates/rustmotion/tests/studio_audit_ws_g.rs index e0344873..74e265b4 100644 --- a/crates/rustmotion-studio/tests/audit_ws_g.rs +++ b/crates/rustmotion/tests/studio_audit_ws_g.rs @@ -1,10 +1,12 @@ +#![cfg(feature = "studio")] + use std::fs; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use rustmotion_studio::editor::audio::audio_fingerprint; -use rustmotion_studio::editor::frames::render_frame_deep; -use rustmotion_studio::scenario::{ +use rustmotion::studio::editor::audio::audio_fingerprint; +use rustmotion::studio::editor::frames::render_frame_deep; +use rustmotion::studio::scenario::{ apply_optimistic, empty_scenario, pending_write_slot, queue_mutation, record_edit, resolve_flush, take_pending, undo, Mutation, Shared, SharedHistory, StudioModel, }; diff --git a/crates/rustmotion-studio/DIOXUS_NOTES.md b/docs/studio-dioxus-notes.md similarity index 100% rename from crates/rustmotion-studio/DIOXUS_NOTES.md rename to docs/studio-dioxus-notes.md