diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
index 1edfca21f..f3f2e6ea6 100644
--- a/.gitea/workflows/ci.yml
+++ b/.gitea/workflows/ci.yml
@@ -49,8 +49,8 @@ jobs:
# # ------------------------------------------------------------------
# # Build environment
# # ------------------------------------------------------------------
-# # ocio-sys builds a stub bridge unless these are set; the oakcommon
-# # ocioutils tests need the real library (see crates/oakcommon/.cargo/
+# # ocio-sys builds a stub bridge unless these are set; the oak-common
+# # ocioutils tests need the real library (see crates/oak-common/.cargo/
# # config.toml, which only applies to builds run from that directory).
# # ocio-sys builds its vendored OpenColorIO from source on Linux (the
# # `bundled` feature; the distro package is too old for the bridge),
@@ -167,17 +167,17 @@ jobs:
# # machines have no system-wide OFX plugins, so the fixture is the
# # only discovery.
# - name: Build OFX fixture plugin
-# run: crates/oakplugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture
+# run: crates/oak-plugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture
#
# - name: Probe OFX plugin discovery
# run: |
# OFX_PLUGIN_PATH="$PWD/.cache/ofx-fixture" \
-# cargo run --locked -p oakplugin --example scan_probe > probe.log 2>&1
+# cargo run --locked -p oak-plugin --example scan_probe > probe.log 2>&1
# grep -q 'type_id=rs.oak.CiTestPlugin' probe.log
# # A project carrying a plugin node must survive save/load (the
# # serializer resolves plugin types via the dynamic factory).
# OAK_OFX_FIXTURE_DIR="$PWD/.cache/ofx-fixtre" \
-# cargo test --locked -p oakplugin --test ofx_roundtrip
+# cargo test --locked -p oak-plugin --test ofx_roundtrip
windows:
name: Build & test (Windows)
diff --git a/Cargo.lock b/Cargo.lock
index 334ac741d..3b0500ce5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4719,7 +4719,7 @@ dependencies = [
]
[[package]]
-name = "oak"
+name = "oak-app"
version = "0.5.0"
dependencies = [
"gpui",
@@ -4727,79 +4727,66 @@ dependencies = [
"gpui_platform",
"gpui_widgets",
"image",
- "oakaudio",
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oaknode",
- "oakplugin",
- "oakrender",
- "oakstorage",
- "oaktask",
- "oaktimeline",
- "oakundo",
+ "oak-audio",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-node",
+ "oak-plugin",
+ "oak-render",
+ "oak-storage",
+ "oak-task",
+ "oak-timeline",
+ "oak-undo",
"serde_yaml",
"smallvec",
]
[[package]]
-name = "oak-cli"
-version = "0.1.0"
-dependencies = [
- "clap",
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oaknode",
- "oakrender",
- "oaktask",
- "oaktimeline",
-]
-
-[[package]]
-name = "oak-worker"
-version = "0.1.0"
-dependencies = [
- "libc",
- "oakcore-rs",
- "oaknode",
- "oakplugin",
- "oakrender",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "oakaudio"
-version = "0.1.0"
+name = "oak-audio"
+version = "0.5.0"
dependencies = [
"cpal",
"ffmpeg-next",
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oakffmpeg-link",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-ffmpeg-link",
"thiserror 2.0.20",
]
[[package]]
-name = "oakcodec"
-version = "0.1.0"
+name = "oak-cli"
+version = "0.5.0"
+dependencies = [
+ "clap",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-node",
+ "oak-render",
+ "oak-task",
+ "oak-timeline",
+]
+
+[[package]]
+name = "oak-codec"
+version = "0.5.0"
dependencies = [
"ffmpeg-next",
- "oakcommon",
- "oakcore-rs",
- "oakffmpeg-link",
+ "oak-common",
+ "oak-core",
+ "oak-ffmpeg-link",
"thiserror 2.0.20",
]
[[package]]
-name = "oakcommon"
-version = "0.1.0"
+name = "oak-common"
+version = "0.5.0"
dependencies = [
"image",
"log",
- "oakcore-rs",
+ "oak-core",
"ocio-rs",
"quick-xml 0.41.0",
"thiserror 2.0.20",
@@ -4807,29 +4794,29 @@ dependencies = [
]
[[package]]
-name = "oakcore-rs"
-version = "0.1.0"
+name = "oak-core"
+version = "0.5.0"
[[package]]
-name = "oakffmpeg-link"
-version = "0.1.0"
+name = "oak-ffmpeg-link"
+version = "0.5.0"
[[package]]
-name = "oaknode"
-version = "0.1.0"
+name = "oak-node"
+version = "0.5.0"
dependencies = [
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oakundo",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-undo",
"thiserror 2.0.20",
]
[[package]]
-name = "oakotio"
-version = "0.1.0"
+name = "oak-otio"
+version = "0.5.0"
dependencies = [
- "oakcore-rs",
+ "oak-core",
"quick-xml 0.41.0",
"serde",
"serde_json",
@@ -4837,26 +4824,26 @@ dependencies = [
]
[[package]]
-name = "oakplugin"
-version = "0.1.0"
+name = "oak-plugin"
+version = "0.5.0"
dependencies = [
"cc",
- "oakcore-rs",
- "oaknode",
- "oakrender",
- "oakundo",
+ "oak-core",
+ "oak-node",
+ "oak-render",
+ "oak-undo",
"thiserror 2.0.20",
]
[[package]]
-name = "oakrender"
-version = "0.1.0"
+name = "oak-render"
+version = "0.5.0"
dependencies = [
"libc",
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oaknode",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-node",
"ocio-rs",
"serde",
"serde_json",
@@ -4865,15 +4852,15 @@ dependencies = [
]
[[package]]
-name = "oakstorage"
-version = "0.1.0"
+name = "oak-storage"
+version = "0.5.0"
dependencies = [
"chrono",
- "oakcommon",
- "oakcore-rs",
- "oaknode",
- "oakotio",
- "oakundo",
+ "oak-common",
+ "oak-core",
+ "oak-node",
+ "oak-otio",
+ "oak-undo",
"sea-orm",
"serde",
"serde_json",
@@ -4882,37 +4869,50 @@ dependencies = [
]
[[package]]
-name = "oaktask"
-version = "0.1.0"
+name = "oak-task"
+version = "0.5.0"
dependencies = [
- "oakcodec",
- "oakcommon",
- "oakcore-rs",
- "oaknode",
- "oakotio",
- "oakrender",
- "oakundo",
+ "oak-codec",
+ "oak-common",
+ "oak-core",
+ "oak-node",
+ "oak-otio",
+ "oak-render",
+ "oak-undo",
"thiserror 2.0.20",
]
[[package]]
-name = "oaktimeline"
-version = "0.1.0"
+name = "oak-timeline"
+version = "0.5.0"
dependencies = [
- "oakcommon",
- "oakcore-rs",
- "oaknode",
- "oakundo",
+ "oak-common",
+ "oak-core",
+ "oak-node",
+ "oak-undo",
"thiserror 2.0.20",
]
[[package]]
-name = "oakundo"
-version = "0.1.0"
+name = "oak-undo"
+version = "0.5.0"
dependencies = [
"thiserror 2.0.20",
]
+[[package]]
+name = "oak-worker"
+version = "0.5.0"
+dependencies = [
+ "libc",
+ "oak-core",
+ "oak-node",
+ "oak-plugin",
+ "oak-render",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "objc"
version = "0.2.7"
@@ -6861,6 +6861,16 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+[[package]]
+name = "simple_player"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+ "gpui_platform",
+ "image",
+ "oak-app",
+]
+
[[package]]
name = "simplecss"
version = "0.2.2"
diff --git a/Cargo.toml b/Cargo.toml
index 618049c02..bfd67f2b4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,34 +14,35 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-# Root manifest: the `oakapp` package (the gpui-based application) plus the
-# Cargo workspace over every crate under crates/.
+# Root manifest: the Cargo workspace only — every package lives under
+# crates/ (the GUI app in oak-app, the CLI in oak-cli, the render worker
+# in oak-worker, the module rlibs in oak-*) or examples/ (standalone
+# integration crates, each with its own Cargo.toml).
#
# gpui (the oak-gpui fork at gpui/) is excluded: it is a separate git
# repository with its own workspace (resolver 3, edition 2024,
# workspace.package/workspace.dependencies). Without the exclusion its
-# crates would be auto-included here via oakapp's path dependencies and
+# crates would be auto-included here via oak-app's path dependencies and
# would inherit from THIS workspace's [workspace.package] (which lacks the
# keys gpui expects). Excluded, each gpui crate resolves against gpui's own
# workspace root, exactly as before the monorepo workspace existed.
[workspace]
-members = ["crates/*"]
+members = ["crates/*", "examples/*"]
exclude = ["gpui", "crates/oakengine.bk"]
-# NOTE: oakstorage (crates/oakstorage) is a workspace member but NOT a
-# default member (it stays out of the default-members test matrix to keep
-# `cargo test` at the root fast; the app links it as a normal path
-# dependency, so it builds with the app). Build/test it explicitly with
-# `cargo test -p oakstorage`.
+# NOTE: oak-storage is a workspace member but NOT a default member (it
+# stays out of the default-members test matrix to keep `cargo test` at
+# the root fast; the app links it as a normal path dependency, so it
+# builds with the app). Build/test it explicitly with `cargo test -p oak-storage`.
# NOTE: `crates/oakengine` (the frozen C-ABI facade cdylib) is retired:
# every consumer (app/cli/worker/plugins) links the module rlibs
# directly, so nothing in the workspace referenced it. The sources are
# kept at crates/oakengine.bk (excluded from the workspace) as a
# reference snapshot; git history is the authoritative backup.
-default-members = [".", "crates/oak-cli", "crates/oak-worker"]
+default-members = ["crates/oak-app", "crates/oak-cli", "crates/oak-worker"]
resolver = "2"
[workspace.package]
-# Single source of truth for the release version: the root `oak` package
+# Single source of truth for the release version: the app package
# inherits it, and with it cargo-packager and the CD packaging scripts.
version = "0.5.0"
@@ -54,127 +55,6 @@ version = "0.5.0"
# ignores).
panic = "unwind"
-[package]
-name = "oak"
-version.workspace = true
-edition = "2021"
-description = "Oak Video Editor"
-license = "GPL-3.0-or-later"
-
-[lib]
-name = "oakapp"
-path = "src/lib.rs"
-# Doctests are disabled: the app links the oak* module crates (which carry
-# media/codec dependencies); the doc examples' assertions are covered by
-# unit tests instead (see `oakui/timecode`).
-doctest = false
-
-[[bin]]
-name = "oak-editor"
-path = "src/main.rs"
-
-[dependencies]
-# gpui: the GPU-accelerated UI framework (oak-gpui fork, git submodule at gpui/).
-gpui = { path = "gpui/crates/gpui" }
-# Convenience entry point: `gpui_platform::application()` picks the platform
-# backend. font-kit enables text shaping/rendering on macOS.
-gpui_platform = { path = "gpui/crates/gpui_platform", features = ["font-kit"] }
-# Oak's widget library: menus, viewer, form controls, project explorer.
-gpui_widgets = { path = "gpui/crates/gpui_widgets" }
-# The mock engine's synthetic viewer frames (`image::Frame` in a
-# `RenderImage`), matching the versions gpui itself uses.
-image = "0.25"
-smallvec = "1"
-# Editable-text widget (used by the file / export dialogs' path fields, the
-# same gpui-elements crate gpui_widgets builds on).
-gpui_elements = { path = "gpui/crates/gpui_elements" }
-# UI string tables: YAML language packs under assets/i18n/, loaded at
-# runtime (bundled with the app, user-extensible) with the compiled-in
-# English table as the fallback.
-serde_yaml = "0.9"
-
-# M14 R3: the app is a PURE module-crate consumer — every engine call is a
-# direct Rust call into the oak* rlibs (oaknode for the project graph,
-# oaktimeline/oakundo for the edit commands and the global undo stack,
-# oakrender for the ticket arena, oakcodec for the export formats/test
-# media, oaktask for the export/interchange tasks, oakaudio for the
-# manager/waveforms, oakcommon/oakcore-rs for the config store and shared
-# value types, oakstorage for the write-through library). No liboakengine
-# dylib, no C ABI, no build.rs link step, no host shims.
-oakaudio = { path = "crates/oakaudio" }
-oakcodec = { path = "crates/oakcodec" }
-oakcommon = { path = "crates/oakcommon" }
-oakcore-rs = { path = "crates/oakcore" }
-oaknode = { path = "crates/oaknode" }
-oakplugin = { path = "crates/oakplugin" }
-oakrender = { path = "crates/oakrender" }
-oakstorage = { path = "crates/oakstorage" }
-oaktask = { path = "crates/oaktask" }
-oaktimeline = { path = "crates/oaktimeline" }
-oakundo = { path = "crates/oakundo" }
-
-[features]
-default = []
-# Force the mock engine even though the real facade is linked. Off by
-# default: the app runs on the real engine unless `--mock` / `OAK_ENGINE=mock`
-# is given at runtime (or this feature is enabled at build time).
-mock-engine = []
-
-[dev-dependencies]
-# `#[gpui::test]` harness for engine-seam smoke tests (test-support feature).
-gpui = { path = "gpui/crates/gpui", features = ["test-support"] }
-# `test-support` also enables `gpui_macos/test-support`, which is what makes
-# `render_to_image` (the screenshot example) available.
-gpui_platform = { path = "gpui/crates/gpui_platform", features = ["test-support"] }
-# Screenshot capture: `examples/screenshot.rs` saves the rendered window PNG
-# (the `image` crate is already in the lockfile through gpui).
-image = "0.25"
-
-# ---------------------------------------------------------------------------
-# cargo-packager metadata (the CD workflow, .github/workflows/cd.yml, packages
-# the release binaries with `cargo packager`; the schema lives at
-# https://docs.crabnebula.dev/packager/configuration/).
-#
-# Binaries are resolved relative to `target/` (oak-editor is the
-# main binary; oak-cli / oak-worker are bundled alongside). They must exist
-# before packaging runs — `cargo packager` does NOT build the app itself, so
-# CD builds with `cargo build --release` first.
-#
-# `icons/icon.png` is NOT committed: it is generated from `Oak_Icon.svg`
-# (rsvg-convert) in CD right before packaging, because cargo-packager needs a
-# bitmap (it converts a square PNG into .icns / .ico / hicolor PNGs itself).
-# It must be 512x512: cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x
-# (and 1024x1024@2x) to an ICNS type — a plain 1024x1024 PNG aborts with
-# "No matching IconType".
-# Keep the path here in sync with the CD workflow.
-# ---------------------------------------------------------------------------
-[package.metadata.packager]
-name = "oak"
-productName = "Oak Video Editor"
-identifier = "org.oakvideoeditor.Oak"
-description = "Oak Video Editor"
-longDescription = "Oak Video Editor: a free, open-source non-linear video editor written in Rust."
-authors = ["Oak Team"]
-copyright = "Copyright (C) 2026 Oak Team"
-licenseFile = "LICENSE"
-category = "Video"
-icons = ["icons/icon.png"]
-# The UI string tables (language packs); users can drop extra
-# .yaml files into the installed i18n directory.
-# The Windows DLL staging dir is filled by
-# tooling/package/bundle-dylibs-windows.sh before `cargo packager` runs
-# (NSIS/WiX place resources next to the executable, which is exactly
-# where Windows resolves DLLs from). Empty locally = no bundled DLLs.
-resources = [
- "assets/i18n",
- { src = "target/pkg/win-dlls/*.dll", target = "." },
-]
-binaries = [
- { path = "oak-editor", main = true },
- { path = "oak-cli", main = false },
- { path = "oak-worker", main = false },
-]
-
[profile.dev.package."*"]
opt-level = 1
diff --git a/crates/oak-app/Cargo.toml b/crates/oak-app/Cargo.toml
new file mode 100644
index 000000000..01ad584f2
--- /dev/null
+++ b/crates/oak-app/Cargo.toml
@@ -0,0 +1,140 @@
+# Oak Video Editor - Non-Linear Video Editor
+# Copyright (C) 2026 Oak Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# The GUI application: the gpui-based `oakapp` library and the
+# `oak-editor` binary. Everything under src/ is app-specific; the engine
+# and module crates live in the workspace under crates/.
+
+[package]
+name = "oak-app"
+version.workspace = true
+edition = "2021"
+description = "Oak Video Editor"
+license = "GPL-3.0-or-later"
+
+[lib]
+name = "oakapp"
+path = "src/lib.rs"
+# Doctests are disabled: the app links the oak* module crates (which carry
+# media/codec dependencies); the doc examples' assertions are covered by
+# unit tests instead (see `oakui/timecode`).
+doctest = false
+
+[[bin]]
+name = "oak-editor"
+path = "src/main.rs"
+
+[dependencies]
+# gpui: the GPU-accelerated UI framework (oak-gpui fork, git submodule at gpui/).
+gpui = { path = "../../gpui/crates/gpui" }
+# Convenience entry point: `gpui_platform::application()` picks the platform
+# backend. font-kit enables text shaping/rendering on macOS.
+gpui_platform = { path = "../../gpui/crates/gpui_platform", features = ["font-kit"] }
+# Oak's widget library: menus, viewer, form controls, project explorer.
+gpui_widgets = { path = "../../gpui/crates/gpui_widgets" }
+# The mock engine's synthetic viewer frames (`image::Frame` in a
+# `RenderImage`), matching the versions gpui itself uses.
+image = "0.25"
+smallvec = "1"
+# Editable-text widget (used by the file / export dialogs' path fields, the
+# same gpui-elements crate gpui_widgets builds on).
+gpui_elements = { path = "../../gpui/crates/gpui_elements" }
+# UI string tables: YAML language packs under assets/i18n/, loaded at
+# runtime (bundled with the app, user-extensible) with the compiled-in
+# English table as the fallback.
+serde_yaml = "0.9"
+
+# M14 R3: the app is a PURE module-crate consumer — every engine call is a
+# direct Rust call into the oak* rlibs (oak-node for the project graph,
+# oak-timeline/oak-undo for the edit commands and the global undo stack,
+# oak-render for the ticket arena, oak-codec for the export formats/test
+# media, oak-task for the export/interchange tasks, oak-audio for the
+# manager/waveforms, oak-common/oak-core for the config store and shared
+# value types, oak-storage for the write-through library). No liboakengine
+# dylib, no C ABI, no build.rs link step, no host shims.
+oak-audio = { path = "../oak-audio" }
+oak-codec = { path = "../oak-codec" }
+oak-common = { path = "../oak-common" }
+oak-core = { path = "../oak-core" }
+oak-node = { path = "../oak-node" }
+oak-plugin = { path = "../oak-plugin" }
+oak-render = { path = "../oak-render" }
+oak-storage = { path = "../oak-storage" }
+oak-task = { path = "../oak-task" }
+oak-timeline = { path = "../oak-timeline" }
+oak-undo = { path = "../oak-undo" }
+
+[features]
+default = []
+# Force the mock engine even though the real facade is linked. Off by
+# default: the app runs on the real engine unless `--mock` / `OAK_ENGINE=mock`
+# is given at runtime (or this feature is enabled at build time).
+mock-engine = []
+
+[dev-dependencies]
+# `#[gpui::test]` harness for engine-seam smoke tests (test-support feature).
+gpui = { path = "../../gpui/crates/gpui", features = ["test-support"] }
+# `test-support` also enables `gpui_macos/test-support`, which is what makes
+# `render_to_image` (the screenshot example) available.
+gpui_platform = { path = "../../gpui/crates/gpui_platform", features = ["test-support"] }
+# Screenshot capture: `examples/screenshot.rs` saves the rendered window PNG
+# (the `image` crate is already in the lockfile through gpui).
+image = "0.25"
+
+# ---------------------------------------------------------------------------
+# cargo-packager metadata (the CD workflow packages the release binaries
+# with `cargo packager`; the schema lives at
+# https://docs.crabnebula.dev/packager/configuration/).
+#
+# Binaries are resolved relative to `target/` (oak-editor is the
+# main binary; oak-cli / oak-worker are bundled alongside). They must exist
+# before packaging runs — `cargo packager` does NOT build the app itself, so
+# CD builds with `cargo build --release` first.
+#
+# `icons/icon.png` is NOT committed: it is generated from `Oak_Icon.svg`
+# (rsvg-convert) in CD right before packaging, because cargo-packager needs a
+# bitmap (it converts a square PNG into .icns / .ico / hicolor PNGs itself).
+# It must be 512x512: cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x
+# (and 1024x1024@2x) to an ICNS type — a plain 1024x1024 PNG aborts with
+# "No matching IconType".
+# Keep the path here in sync with the CD workflow.
+# ---------------------------------------------------------------------------
+[package.metadata.packager]
+name = "oak"
+productName = "Oak Video Editor"
+identifier = "org.oakvideoeditor.Oak"
+description = "Oak Video Editor"
+longDescription = "Oak Video Editor: a free, open-source non-linear video editor written in Rust."
+authors = ["Oak Team"]
+copyright = "Copyright (C) 2026 Oak Team"
+licenseFile = "LICENSE"
+category = "Video"
+icons = ["../../icons/icon.png"]
+# The UI string tables (language packs); users can drop extra
+# .yaml files into the installed i18n directory.
+# The Windows DLL staging dir is filled by
+# tooling/package/bundle-dylibs-windows.sh before `cargo packager` runs
+# (NSIS/WiX place resources next to the executable, which is exactly
+# where Windows resolves DLLs from). Empty locally = no bundled DLLs.
+resources = [
+ "../../assets/i18n",
+ { src = "../../target/pkg/win-dlls/*.dll", target = "." },
+]
+binaries = [
+ { path = "oak-editor", main = true },
+ { path = "oak-cli", main = false },
+ { path = "oak-worker", main = false },
+]
diff --git a/build.rs b/crates/oak-app/build.rs
similarity index 92%
rename from build.rs
rename to crates/oak-app/build.rs
index 6aeabd549..336c9a620 100644
--- a/build.rs
+++ b/crates/oak-app/build.rs
@@ -39,12 +39,12 @@ fn main() {
}
// --- OFX interact end-to-end test plugin ---------------------------------
// The app-side interact tests (src/oakui/ofx.rs) drive the *real*
- // minimal test plugin (crates/oakplugin/cbits/oak_test_plugin.c) through
+ // minimal test plugin (../../crates/oak-plugin/cbits/oak_test_plugin.c) through
// the app's interact wiring. oakplugin compiles the same C file into its
// own OUT_DIR, but build-script env vars do not cross crates, so compile
// it here too — the test assembles a plugin bundle from the app's
// OUT_DIR.
- println!("cargo:rerun-if-changed=crates/oakplugin/cbits/oak_test_plugin.c");
+ println!("cargo:rerun-if-changed=../../crates/oak-plugin/cbits/oak_test_plugin.c");
build_test_plugin(&os);
}
@@ -63,8 +63,8 @@ fn build_test_plugin(os: &str) {
};
let mut args = vec![
"-fPIC".to_string(),
- "-Icrates/oakplugin/ofx".to_string(),
- "crates/oakplugin/cbits/oak_test_plugin.c".to_string(),
+ "-I../../crates/oak-plugin/ofx".to_string(),
+ "../../crates/oak-plugin/cbits/oak_test_plugin.c".to_string(),
link_flag.to_string(),
"-o".to_string(),
format!("{out}/oak_test_plugin.{ext}"),
diff --git a/src/actions.rs b/crates/oak-app/src/actions.rs
similarity index 99%
rename from src/actions.rs
rename to crates/oak-app/src/actions.rs
index 7a9b245e8..39faba81a 100644
--- a/src/actions.rs
+++ b/crates/oak-app/src/actions.rs
@@ -463,7 +463,7 @@ pub(crate) fn shortcuts_test_lock() -> &'static Mutex<()> {
/// The path of the custom-shortcuts file: `/shortcuts`, exactly like
/// the C++ `MainWindow::get_custom_shortcuts_file`.
pub fn custom_shortcuts_path() -> String {
- let dir = oakcommon::filefunctions::FileFunctions::new()
+ let dir = oak_common::filefunctions::FileFunctions::new()
.get_configuration_location()
.unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().into_owned());
format!("{}/shortcuts", dir.trim_end_matches('/'))
diff --git a/src/app.rs b/crates/oak-app/src/app.rs
similarity index 99%
rename from src/app.rs
rename to crates/oak-app/src/app.rs
index 68380d0a6..a66f0194b 100644
--- a/src/app.rs
+++ b/crates/oak-app/src/app.rs
@@ -2339,7 +2339,7 @@ impl MenuState {
loop_playback: false,
show_all: false,
full_screen: false,
- use_proxy_media: oakcommon::configstore::ConfigStore::instance()
+ use_proxy_media: oak_common::configstore::ConfigStore::instance()
.get_bool(None, "UseProxyMedia", 1)
!= 0,
open_panels: WINDOW_PANELS
diff --git a/src/dialogs.rs b/crates/oak-app/src/dialogs.rs
similarity index 100%
rename from src/dialogs.rs
rename to crates/oak-app/src/dialogs.rs
diff --git a/src/i18n.rs b/crates/oak-app/src/i18n.rs
similarity index 100%
rename from src/i18n.rs
rename to crates/oak-app/src/i18n.rs
diff --git a/src/lib.rs b/crates/oak-app/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to crates/oak-app/src/lib.rs
diff --git a/src/main.rs b/crates/oak-app/src/main.rs
similarity index 100%
rename from src/main.rs
rename to crates/oak-app/src/main.rs
diff --git a/src/manager.rs b/crates/oak-app/src/manager.rs
similarity index 100%
rename from src/manager.rs
rename to crates/oak-app/src/manager.rs
diff --git a/src/oakui/component/menu.rs b/crates/oak-app/src/oakui/component/menu.rs
similarity index 100%
rename from src/oakui/component/menu.rs
rename to crates/oak-app/src/oakui/component/menu.rs
diff --git a/src/oakui/component/mod.rs b/crates/oak-app/src/oakui/component/mod.rs
similarity index 100%
rename from src/oakui/component/mod.rs
rename to crates/oak-app/src/oakui/component/mod.rs
diff --git a/src/oakui/component/text_input.rs b/crates/oak-app/src/oakui/component/text_input.rs
similarity index 100%
rename from src/oakui/component/text_input.rs
rename to crates/oak-app/src/oakui/component/text_input.rs
diff --git a/src/oakui/displaycolor.rs b/crates/oak-app/src/oakui/displaycolor.rs
similarity index 97%
rename from src/oakui/displaycolor.rs
rename to crates/oak-app/src/oakui/displaycolor.rs
index 6eba3f02a..46de57556 100644
--- a/src/oakui/displaycolor.rs
+++ b/crates/oak-app/src/oakui/displaycolor.rs
@@ -32,8 +32,8 @@
use std::sync::{Arc, LazyLock, Mutex};
-use oakcommon::configstore::ConfigStore;
-use oakrender::color::ColorProcessor;
+use oak_common::configstore::ConfigStore;
+use oak_render::color::ColorProcessor;
/// Config key: the display color management mode ("icc" / "off").
pub const CONFIG_KEY_COLOR_MODE: &str = "DisplayColorMode";
@@ -118,7 +118,7 @@ fn current() -> Option {
}
// The custom override wins; empty = the platform's display profile.
let icc = if icc_path.is_empty() {
- oakcommon::displayicc::system_display_icc()
+ oak_common::displayicc::system_display_icc()
} else {
Some(icc_path.clone())
};
diff --git a/src/oakui/effectchain.rs b/crates/oak-app/src/oakui/effectchain.rs
similarity index 92%
rename from src/oakui/effectchain.rs
rename to crates/oak-app/src/oakui/effectchain.rs
index fa71a4581..03ca2668b 100644
--- a/src/oakui/effectchain.rs
+++ b/crates/oak-app/src/oakui/effectchain.rs
@@ -24,15 +24,15 @@
//! `oakengine_node_effect_remove` / `..._move` / `..._set_enabled`), rebuilt
//! over the direct graph API: chain nodes are `NodeId`s in the host's own
//! project, and the undoable edits are one stack entry each (a multi
-//! command or a closure command from [`oakundo::undocommand::UndoCommand`]).
+//! command or a closure command from [`oak_undo::undocommand::UndoCommand`]).
use std::sync::{Arc, Mutex};
-use oaknode::graph::{Graph, NodeEntry};
-use oaknode::id::NodeId;
-use oaknode::node::ENABLED_INPUT;
-use oaknode::value::NodeValue;
-use oakundo::undocommand::UndoCommand;
+use oak_node::graph::{Graph, NodeEntry};
+use oak_node::id::NodeId;
+use oak_node::node::ENABLED_INPUT;
+use oak_node::value::NodeValue;
+use oak_undo::undocommand::UndoCommand;
use super::graphops::{connect_command, disconnect_command, lock, push_command as push, ProjectRef};
@@ -100,9 +100,9 @@ pub fn is_enabled(g: &Graph, node: NodeId) -> bool {
/// General — the C++ `factorymenu.cpp` OpenFX branch).
pub fn addable_effects() -> Vec {
use super::engine::EffectEntry;
- use oaknode::node::flags as node_flags;
+ use oak_node::node::flags as node_flags;
let mut out = Vec::new();
- for meta in oaknode::factory::Factory::global().entries() {
+ for meta in oak_node::factory::Factory::global().entries() {
// A scratch instance per entry just to read its flags (the factory
// metadata carries no flag copy).
let (core, _behavior) = (meta.create)();
@@ -122,7 +122,7 @@ pub fn addable_effects() -> Vec {
});
}
}
- for meta in oaknode::factory::Factory::global().dynamic_entries() {
+ for meta in oak_node::factory::Factory::global().dynamic_entries() {
// OpenFX plugin entries: grouped by sub-category. The factory
// metadata carries no flags; plugin nodes are always video effects.
let name = if meta.name.is_empty() {
@@ -157,7 +157,7 @@ pub fn addable_effects() -> Vec {
/// persistent-message badge and to trigger push buttons.
pub fn plugin_instance_handle(g: &Graph, node: NodeId) -> Option {
let behavior = g.get(node)?.behavior.as_any()?;
- let plugin = behavior.downcast_ref::()?;
+ let plugin = behavior.downcast_ref::()?;
let handle = plugin.instance_handle();
(!handle.is_null()).then_some(handle.0)
}
@@ -172,8 +172,8 @@ pub fn effect_params(
g: &Graph,
node: NodeId,
) -> Option> {
- use oaknode::input::flags as input_flags;
- use oaknode::value::ValueType;
+ use oak_node::input::flags as input_flags;
+ use oak_node::value::ValueType;
let entry = g.get(node)?;
let mut out = Vec::new();
@@ -190,7 +190,7 @@ pub fn effect_params(
continue;
}
// The standard enabled input is structural, not a parameter.
- if input.id == oaknode::node::ENABLED_INPUT {
+ if input.id == oak_node::node::ENABLED_INPUT {
continue;
}
// Display name: the behavior's localized input name (C++
@@ -211,7 +211,7 @@ pub fn effect_params(
for option in entry.behavior.input_combo_strings(&input.id) {
properties.push((
"combo_option".to_string(),
- oaknode::value::NodeValue::Text(option.to_string()),
+ oak_node::value::NodeValue::Text(option.to_string()),
));
}
}
@@ -236,7 +236,7 @@ pub fn combo_options(p: &super::engine::EffectParam) -> Vec {
.iter()
.filter(|(k, _)| k == "combo_option")
.filter_map(|(_, v)| match v {
- oaknode::value::NodeValue::Text(s) => Some(s.clone()),
+ oak_node::value::NodeValue::Text(s) => Some(s.clone()),
_ => None,
})
.collect()
@@ -248,7 +248,7 @@ pub fn combo_values(p: &super::engine::EffectParam) -> Vec {
.iter()
.filter(|(k, _)| k == "combo_value")
.filter_map(|(_, v)| match v {
- oaknode::value::NodeValue::Text(s) => Some(s.clone()),
+ oak_node::value::NodeValue::Text(s) => Some(s.clone()),
_ => None,
})
.collect()
@@ -262,7 +262,7 @@ pub fn ui_section_of(p: &super::engine::EffectParam) -> Option<(String, String)>
.iter()
.find(|(k, _)| k == "ui_group")
.and_then(|(_, v)| match v {
- oaknode::value::NodeValue::Text(s) => Some(s.clone()),
+ oak_node::value::NodeValue::Text(s) => Some(s.clone()),
_ => None,
});
let page = p
@@ -270,7 +270,7 @@ pub fn ui_section_of(p: &super::engine::EffectParam) -> Option<(String, String)>
.iter()
.find(|(k, _)| k == "ui_page")
.and_then(|(_, v)| match v {
- oaknode::value::NodeValue::Text(s) => Some(s.clone()),
+ oak_node::value::NodeValue::Text(s) => Some(s.clone()),
_ => None,
});
match (group, page) {
@@ -335,14 +335,14 @@ fn add_node_command(p: &ProjectRef, entry: NodeEntry) -> (UndoCommand, AddNodeSt
/// ONE undo row on success / aborts (undoing the executed children) on
/// error.
fn grouped(name: &str, f: impl FnOnce() -> Result) -> Result {
- oakundo::global::group_begin(name).map_err(|e| e.to_string())?;
+ oak_undo::global::group_begin(name).map_err(|e| e.to_string())?;
match f() {
Ok(v) => {
- oakundo::global::group_end().map_err(|e| e.to_string())?;
+ oak_undo::global::group_end().map_err(|e| e.to_string())?;
Ok(v)
}
Err(e) => {
- let _ = oakundo::global::group_abort();
+ let _ = oak_undo::global::group_abort();
Err(e)
}
}
@@ -461,7 +461,7 @@ pub fn insert(p: &ProjectRef, host: NodeId, index: usize, type_id: &str) -> Resu
if effect_input_of(&g.graph, host).is_none() {
return Err("node cannot host effects (no effect input)".to_string());
}
- let Some((core, behavior)) = oaknode::factory::Factory::global().create_any(type_id) else {
+ let Some((core, behavior)) = oak_node::factory::Factory::global().create_any(type_id) else {
return Err(format!("unknown node type id \"{type_id}\""));
};
if core.effect_input.is_empty() {
@@ -680,7 +680,7 @@ mod tests {
let project = graphops::create_project();
let clip = {
let mut g = lock(&project);
- let (core, behavior) = oaknode::block::clip_create();
+ let (core, behavior) = oak_node::block::clip_create();
g.graph.add_node(core, behavior)
};
(project, clip)
@@ -698,7 +698,7 @@ mod tests {
#[test]
fn insert_walks_the_chain_and_undoes() {
let _g = stack_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, host) = project_with_clip();
let ty = some_effect_type();
@@ -710,19 +710,19 @@ mod tests {
assert_eq!(chain(&lock(&project).graph, host), vec![second, third, first]);
// One undo row per insert.
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(chain(&lock(&project).graph, host), vec![second, first]);
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(chain(&lock(&project).graph, host), vec![first]);
- oakundo::global::redo().unwrap();
+ oak_undo::global::redo().unwrap();
assert_eq!(chain(&lock(&project).graph, host), vec![second, first]);
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
#[test]
fn remove_bridges_the_gap() {
let _g = stack_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, host) = project_with_clip();
let ty = some_effect_type();
let a = insert(&project, host, 0, &ty).unwrap();
@@ -732,15 +732,15 @@ mod tests {
remove(&project, host, b).expect("remove the middle effect");
assert_eq!(chain(&lock(&project).graph, host), vec![a, c]);
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(chain(&lock(&project).graph, host), vec![a, b, c]);
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
#[test]
fn move_reorders_the_chain() {
let _g = stack_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, host) = project_with_clip();
let ty = some_effect_type();
let a = insert(&project, host, 0, &ty).unwrap();
@@ -749,15 +749,15 @@ mod tests {
move_effect(&project, host, c, 0).expect("move the last effect to the source side");
assert_eq!(chain(&lock(&project).graph, host), vec![c, a, b]);
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(chain(&lock(&project).graph, host), vec![a, b, c]);
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
#[test]
fn set_enabled_toggles_and_undoes() {
let _g = stack_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, host) = project_with_clip();
let ty = some_effect_type();
let eff = insert(&project, host, 0, &ty).unwrap();
@@ -765,9 +765,9 @@ mod tests {
set_enabled(&project, eff, false).unwrap();
assert!(!is_enabled(&lock(&project).graph, eff));
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert!(is_enabled(&lock(&project).graph, eff));
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
#[test]
@@ -800,7 +800,7 @@ mod tests {
#[test]
fn set_input_value_undoes() {
let _g = stack_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, host) = project_with_clip();
// Choose a built-in effect whose scratch core exposes a float input
@@ -808,12 +808,12 @@ mod tests {
let ty = addable_effects()
.into_iter()
.find(|entry| {
- let (core, _behavior) = oaknode::factory::Factory::global()
+ let (core, _behavior) = oak_node::factory::Factory::global()
.create_any(&entry.type_id)
.expect("the entry resolves");
core.inputs.iter().any(|i| {
- i.value_type == oaknode::value::ValueType::Float
- && i.flags & oaknode::input::flags::HIDDEN == 0
+ i.value_type == oak_node::value::ValueType::Float
+ && i.flags & oak_node::input::flags::HIDDEN == 0
})
})
.expect("at least one built-in effect exposes a float input")
@@ -831,8 +831,8 @@ mod tests {
.get(eff)
.and_then(|e| {
e.core.inputs.iter().find(|i| {
- i.value_type == oaknode::value::ValueType::Float
- && i.flags & oaknode::input::flags::HIDDEN == 0
+ i.value_type == oak_node::value::ValueType::Float
+ && i.flags & oak_node::input::flags::HIDDEN == 0
})
})
.map(|i| i.id.clone())
@@ -849,12 +849,12 @@ mod tests {
lock(&project).graph.get(eff).unwrap().core.standard_value(&input_id, -1),
NodeValue::Float(42.0)
);
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(
lock(&project).graph.get(eff).unwrap().core.standard_value(&input_id, -1),
before
);
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
/// The combo-option collector reads the repeated `("combo_option", _)`
@@ -862,7 +862,7 @@ mod tests {
#[test]
fn combo_option_collectors_read_repeated_properties() {
use crate::oakui::engine::EffectParam;
- use oaknode::value::{NodeValue, ValueType};
+ use oak_node::value::{NodeValue, ValueType};
let param = EffectParam {
input_id: "mode".into(),
display_name: "Mode".into(),
@@ -885,7 +885,7 @@ mod tests {
#[test]
fn ui_section_collects_group_and_page() {
use crate::oakui::engine::EffectParam;
- use oaknode::value::{NodeValue, ValueType};
+ use oak_node::value::{NodeValue, ValueType};
let plain = EffectParam {
input_id: "p".into(),
display_name: "P".into(),
diff --git a/src/oakui/engine.rs b/crates/oak-app/src/oakui/engine.rs
similarity index 97%
rename from src/oakui/engine.rs
rename to crates/oak-app/src/oakui/engine.rs
index b0fd7614a..88d1fe0b7 100644
--- a/src/oakui/engine.rs
+++ b/crates/oak-app/src/oakui/engine.rs
@@ -179,20 +179,20 @@ pub struct EffectParam {
/// The display name (the OFX param label).
pub display_name: String,
/// The value type.
- pub value_type: oaknode::value::ValueType,
+ pub value_type: oak_node::value::ValueType,
/// The current value.
- pub value: oaknode::value::NodeValue,
- /// The input flag bits (`oaknode::input::flags::*`).
+ pub value: oak_node::value::NodeValue,
+ /// The input flag bits (`oak_node::input::flags::*`).
pub flags: u32,
/// The input properties (`combo_option` / `combo_value` / `ui_group` /
/// `ui_page` / `min` / `max` / ...).
- pub properties: Vec<(String, oaknode::value::NodeValue)>,
+ pub properties: Vec<(String, oak_node::value::NodeValue)>,
}
/// The i18n key of a node category submenu, or `None` for categories that
/// never appear in the node editor's Add menu (timeline-structural nodes).
-pub fn node_category_key(category: oaknode::node::Category) -> Option<&'static str> {
- use oaknode::node::Category as C;
+pub fn node_category_key(category: oak_node::node::Category) -> Option<&'static str> {
+ use oak_node::node::Category as C;
Some(match category {
C::Output => "node.category.output",
C::Effect => "node.category.effect",
@@ -374,7 +374,7 @@ pub trait AppEngine:
&mut self,
_effect: EffectId,
_input_id: &str,
- _value: oaknode::value::NodeValue,
+ _value: oak_node::value::NodeValue,
_cx: &mut Context,
) -> Result<(), String> {
Err("effect params not supported".into())
@@ -431,12 +431,12 @@ pub trait AppEngine:
/// included.
fn node_library(&self) -> Vec {
let mut out = Vec::new();
- let factory = oaknode::factory::Factory::global();
+ let factory = oak_node::factory::Factory::global();
for meta in factory.entries() {
// A scratch instance per entry just to read its flags (the
// factory metadata carries no flag copy).
let (core, _behavior) = (meta.create)();
- if core.flags & oaknode::node::flags::DONT_SHOW_IN_CREATE_MENU != 0 {
+ if core.flags & oak_node::node::flags::DONT_SHOW_IN_CREATE_MENU != 0 {
continue;
}
let Some(category_key) = meta.categories.first().copied().and_then(node_category_key)
@@ -757,7 +757,7 @@ pub trait AppEngine:
/// The global "Use Proxy Media" switch (the C++ `UseProxyMedia`
/// config; preview-only — exports always decode the original media).
fn use_proxy_media(&self) -> bool {
- oakcommon::configstore::ConfigStore::instance()
+ oak_common::configstore::ConfigStore::instance()
.get_bool(None, "UseProxyMedia", 1)
!= 0
}
@@ -766,7 +766,7 @@ pub trait AppEngine:
/// footage's rendered frames (the C++ toggles the config and
/// re-renders; the preview path reads the switch on every montage).
fn set_use_proxy_media(&mut self, enabled: bool, cx: &mut Context) {
- oakcommon::configstore::ConfigStore::instance().set(
+ oak_common::configstore::ConfigStore::instance().set(
None,
"UseProxyMedia",
if enabled { "true" } else { "false" },
@@ -779,7 +779,7 @@ pub trait AppEngine:
/// size, 2/4/8 = progressively smaller preview renders for machines
/// that cannot keep up. Preview-only; exports always render native.
fn playback_divider(&self) -> i64 {
- oakcommon::configstore::ConfigStore::instance()
+ oak_common::configstore::ConfigStore::instance()
.get_int(None, "PlaybackDivider", 1)
.clamp(1, 8) as i64
}
@@ -787,7 +787,7 @@ pub trait AppEngine:
/// Sets the playback resolution divider and invalidates the rendered
/// frames so the next pull re-renders at the new geometry.
fn set_playback_divider(&mut self, divider: i64, cx: &mut Context) {
- oakcommon::configstore::ConfigStore::instance().set(
+ oak_common::configstore::ConfigStore::instance().set(
None,
"PlaybackDivider",
÷r.clamp(1, 8).to_string(),
@@ -817,7 +817,7 @@ pub trait AppEngine:
}
/// Starts generating the proxy of footage `id` (a background ffmpeg
- /// transcode through `oaktask::ProxyTask`). Progress is reported
+ /// transcode through `oak_task::ProxyTask`). Progress is reported
/// through [`proxy_task_progress`](Self::proxy_task_progress) and
/// drained on the engine tick; completion invalidates the footage's
/// rendered frames.
@@ -1005,7 +1005,7 @@ pub struct MulticamState {
}
/// The lifecycle state of one footage's proxy (the UI mirror of
-/// `oakcodec::proxymanager::ProxyState`).
+/// `oak_codec::proxymanager::ProxyState`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyMediaState {
/// No proxy on disk (the menu offers "Generate").
@@ -1019,7 +1019,7 @@ pub enum ProxyMediaState {
}
/// The proxy generation parameters the proxy dialog edits (the app-side
-/// mirror of `oakcodec::proxymanager::ProxyParams`, minus the fixed
+/// mirror of `oak_codec::proxymanager::ProxyParams`, minus the fixed
/// version / extension).
#[derive(Debug, Clone, PartialEq)]
pub struct ProxyParamsUi {
@@ -1041,7 +1041,7 @@ pub struct ProxyParamsUi {
/// default values): `ProxyWidth`/`ProxyHeight`/`ProxyDivider`/`ProxyCRF`/
/// `ProxyPreset`/`ProxyIncludeAudio`.
pub fn proxy_params_from_config() -> ProxyParamsUi {
- let codec = oakcodec::proxymanager::ProxyManager::proxy_params_from_config();
+ let codec = oak_codec::proxymanager::ProxyManager::proxy_params_from_config();
let end = |a: &[u8; 32]| a.iter().position(|&b| b == 0).unwrap_or(a.len());
let preset = std::str::from_utf8(&codec.preset[..end(&codec.preset)])
.unwrap_or("")
@@ -1058,8 +1058,8 @@ pub fn proxy_params_from_config() -> ProxyParamsUi {
impl ProxyParamsUi {
/// The codec-side params these UI params stand for.
- pub fn to_codec(&self) -> oakcodec::proxymanager::ProxyParams {
- let mut p = oakcodec::proxymanager::ProxyParams::default();
+ pub fn to_codec(&self) -> oak_codec::proxymanager::ProxyParams {
+ let mut p = oak_codec::proxymanager::ProxyParams::default();
p.width = self.width;
p.height = self.height;
p.divider = self.divider;
@@ -1072,7 +1072,7 @@ impl ProxyParamsUi {
}
/// UI params from codec-side params.
- pub fn from_codec(codec: &oakcodec::proxymanager::ProxyParams) -> ProxyParamsUi {
+ pub fn from_codec(codec: &oak_codec::proxymanager::ProxyParams) -> ProxyParamsUi {
let end = |a: &[u8; 32]| a.iter().position(|&b| b == 0).unwrap_or(a.len());
ProxyParamsUi {
width: codec.width,
diff --git a/src/oakui/frames.rs b/crates/oak-app/src/oakui/frames.rs
similarity index 100%
rename from src/oakui/frames.rs
rename to crates/oak-app/src/oakui/frames.rs
diff --git a/src/oakui/graphops.rs b/crates/oak-app/src/oakui/graphops.rs
similarity index 90%
rename from src/oakui/graphops.rs
rename to crates/oak-app/src/oakui/graphops.rs
index 3aba85eb0..86166daf9 100644
--- a/src/oakui/graphops.rs
+++ b/crates/oak-app/src/oakui/graphops.rs
@@ -32,18 +32,18 @@
use std::path::Path;
use std::sync::{Arc, Mutex, MutexGuard};
-use oakcore_rs::{Rational, TimeRange};
-use oaknode::block::ClipBlockBehavior;
-use oaknode::footage::FootageBehavior;
-use oaknode::graph::Graph;
-use oaknode::id::NodeId;
-use oaknode::project::Project;
-use oaknode::sequence::SequenceBehavior;
-use oaknode::track::{TrackBehavior, TrackListBehavior, TrackType};
-use oaktimeline::handle::CHandle;
-use oaktimeline::util::NodeRef;
+use oak_core::{Rational, TimeRange};
+use oak_node::block::ClipBlockBehavior;
+use oak_node::footage::FootageBehavior;
+use oak_node::graph::Graph;
+use oak_node::id::NodeId;
+use oak_node::project::Project;
+use oak_node::sequence::SequenceBehavior;
+use oak_node::track::{TrackBehavior, TrackListBehavior, TrackType};
+use oak_timeline::handle::CHandle;
+use oak_timeline::util::NodeRef;
-use oakstorage::backend::StorageBackend;
+use oak_storage::backend::StorageBackend;
use super::engine::LibraryProject;
@@ -116,7 +116,7 @@ pub fn footage_behavior(g: &Graph, id: NodeId) -> Option<&FootageBehavior> {
pub fn is_folder(g: &Graph, id: NodeId) -> bool {
g.get(id)
.and_then(|e| e.behavior.as_any())
- .and_then(|a| a.downcast_ref::())
+ .and_then(|a| a.downcast_ref::())
.is_some()
}
@@ -141,7 +141,7 @@ pub fn load_ove(path: &Path) -> Result {
let xml = std::fs::read_to_string(path)
.map_err(|e| format!("failed to read \"{}\": {e}", path.display()))?;
let project =
- oaknode::serializer::load(&xml).map_err(|e| format!("failed to parse: {e}"))?;
+ oak_node::serializer::load(&xml).map_err(|e| format!("failed to parse: {e}"))?;
let abs = if path.is_absolute() {
path.to_path_buf()
} else {
@@ -162,7 +162,7 @@ pub fn load_ove(path: &Path) -> Result {
pub fn save_ove(project: &ProjectRef, path: &Path) -> Result<(), String> {
let xml = {
let guard = lock(project);
- oaknode::serializer::save(&guard).map_err(|e| format!("failed to serialize: {e}"))?
+ oak_node::serializer::save(&guard).map_err(|e| format!("failed to serialize: {e}"))?
};
std::fs::write(path, &xml).map_err(|e| format!("failed to write \"{}\": {e}", path.display()))?;
let mut guard = lock(project);
@@ -217,7 +217,7 @@ pub fn create_sequence(project: &ProjectRef, name: &str) -> NodeId {
let Some(list) = find_or_create_track_list(project, seq, kind) else {
continue;
};
- oaktimeline::undogeneral::TimelineAddTrackCommand::new(node_ref(project, list)).redo();
+ oak_timeline::undogeneral::TimelineAddTrackCommand::new(node_ref(project, list)).redo();
}
seq
}
@@ -287,15 +287,15 @@ pub fn sequence_length(g: &Graph, seq: NodeId) -> Rational {
}
/// Borrow a block's core (any block kind).
-fn block_core(entry: &oaknode::graph::NodeEntry) -> Option<&oaknode::block::BlockCore> {
+fn block_core(entry: &oak_node::graph::NodeEntry) -> Option<&oak_node::block::BlockCore> {
let any = entry.behavior.as_any()?;
if let Some(c) = any.downcast_ref::() {
return Some(&c.core);
}
- if let Some(g) = any.downcast_ref::() {
+ if let Some(g) = any.downcast_ref::() {
return Some(&g.core);
}
- any.downcast_ref::()
+ any.downcast_ref::()
.map(|t| &t.core)
}
@@ -560,7 +560,7 @@ pub fn import_footage(project: &ProjectRef, path: &Path) -> Result())
@@ -582,11 +582,11 @@ pub fn import_footage(project: &ProjectRef, path: &Path) -> Result Result CHandle {
- oaktimeline::handle::make_owned(oaktimeline::marker::TimelineMarkerList::new())
+ oak_timeline::handle::make_owned(oak_timeline::marker::TimelineMarkerList::new())
}
/// Create a workarea handle (owned, refcount 1).
pub fn workarea_create() -> CHandle {
- oaktimeline::handle::make_owned(oaktimeline::workarea::TimelineWorkArea::new())
+ oak_timeline::handle::make_owned(oak_timeline::workarea::TimelineWorkArea::new())
}
/// Release an owned marker-list / workarea handle (NULL-safe; the handle
@@ -629,7 +629,7 @@ pub fn markers_of(list: &CHandle) -> Vec<(Rational, String, i32)> {
// SAFETY: `list` boxes a `TimelineMarkerList` (created by
// `marker_list_create`); the read is shared and brief.
let Some(l) = (unsafe {
- oaktimeline::handle::get::>>(
+ oak_timeline::handle::get::>>(
list,
)
}) else {
@@ -649,7 +649,7 @@ pub fn marker_index_at(list: &CHandle, time: Rational) -> Option {
}
// SAFETY: as `markers_of`.
let l = unsafe {
- oaktimeline::handle::get::>>(
+ oak_timeline::handle::get::>>(
list,
)
}?;
@@ -665,7 +665,7 @@ pub fn workarea_state(wa: &CHandle) -> Option<(bool, TimeRange)> {
// SAFETY: `wa` boxes a `TimelineWorkArea` (created by
// `workarea_create`); the read is shared and brief.
let w = unsafe {
- oaktimeline::handle::get::>>(
+ oak_timeline::handle::get::>>(
wa,
)
}?;
@@ -681,7 +681,7 @@ pub fn workarea_set(wa: &CHandle, enabled: bool, range: TimeRange) {
// SAFETY: `wa` boxes a `TimelineWorkArea`; the engine writes it only
// from the UI thread.
if let Some(w) = unsafe {
- oaktimeline::handle::get_mut::>>(
+ oak_timeline::handle::get_mut::>>(
wa,
)
} {
@@ -700,19 +700,19 @@ pub fn workarea_set(wa: &CHandle, enabled: bool, range: TimeRange) {
/// The configured default library as a parsed URI; an error when the
/// write-through backend is disabled or the path does not resolve.
-fn library() -> Result {
- if !oakstorage::writethrough::storage_enabled() {
+fn library() -> Result {
+ if !oak_storage::writethrough::storage_enabled() {
return Err("the project library is not configured".to_string());
}
- let uri = oakstorage::writethrough::library_uri()
+ let uri = oak_storage::writethrough::library_uri()
.ok_or_else(|| "the project library path does not resolve".to_string())?;
- oakstorage::uri::StorageUri::parse(&uri).map_err(|e| e.to_string())
+ oak_storage::uri::StorageUri::parse(&uri).map_err(|e| e.to_string())
}
/// The library URI selecting one row (`…?project=`).
-fn project_uri(uuid: &str) -> Result {
+fn project_uri(uuid: &str) -> Result {
let uri = library()?;
- oakstorage::uri::StorageUri::parse(&format!("{}?project={uuid}", uri.to_uri_string()))
+ oak_storage::uri::StorageUri::parse(&format!("{}?project={uuid}", uri.to_uri_string()))
.map_err(|e| e.to_string())
}
@@ -720,11 +720,11 @@ fn project_uri(uuid: &str) -> Result {
/// data source). With storage disabled the result is the empty list, not
/// an error (the facade contract).
pub fn library_list() -> Result, String> {
- if !oakstorage::writethrough::storage_enabled() {
+ if !oak_storage::writethrough::storage_enabled() {
return Ok(Vec::new());
}
let uri = library()?;
- let backend = oakstorage::writethrough::backend();
+ let backend = oak_storage::writethrough::backend();
let infos = backend.list_projects(&uri).map_err(|e| e.to_string())?;
Ok(infos
.into_iter()
@@ -760,7 +760,7 @@ pub fn library_create(name: &str) -> Result {
.insert("projectname".to_string(), name.to_string());
guard.uuid.clone()
};
- let result = oakstorage::writethrough::backend()
+ let result = oak_storage::writethrough::backend()
.save_project(&project, &uri, 0)
.map_err(|e| e.to_string());
result?;
@@ -773,7 +773,7 @@ pub fn library_delete(uuid: &str) -> Result<(), String> {
if uuid.is_empty() {
return Err("invalid uuid".to_string());
}
- oakstorage::writethrough::backend()
+ oak_storage::writethrough::backend()
.delete_project(&library()?, uuid)
.map_err(|e| e.to_string())
}
@@ -783,7 +783,7 @@ pub fn library_rename(uuid: &str, name: &str) -> Result<(), String> {
if uuid.is_empty() || name.trim().is_empty() {
return Err("invalid uuid or name".to_string());
}
- oakstorage::writethrough::backend()
+ oak_storage::writethrough::backend()
.rename_project(&library()?, uuid, name.trim())
.map_err(|e| e.to_string())
}
@@ -795,7 +795,7 @@ pub fn library_duplicate(uuid: &str) -> Result {
if uuid.is_empty() {
return Err("invalid uuid".to_string());
}
- let info = oakstorage::writethrough::backend()
+ let info = oak_storage::writethrough::backend()
.duplicate_project(&library()?, uuid, None)
.map_err(|e| e.to_string())?;
Ok(info.uuid)
@@ -804,9 +804,9 @@ pub fn library_duplicate(uuid: &str) -> Result {
/// Import a `.ove` / `.otio` / `.fcpxml` project file as a new library
/// row; returns the new row's uuid.
pub fn library_import(path: &Path) -> Result {
- let file_uri = oakstorage::uri::StorageUri::parse(&path.to_string_lossy())
+ let file_uri = oak_storage::uri::StorageUri::parse(&path.to_string_lossy())
.map_err(|e| e.to_string())?;
- oakstorage::writethrough::backend()
+ oak_storage::writethrough::backend()
.import_from_file(&library()?, &file_uri)
.map_err(|e| e.to_string())
}
@@ -817,9 +817,9 @@ pub fn library_export(uuid: &str, path: &Path) -> Result<(), String> {
if uuid.is_empty() {
return Err("invalid uuid".to_string());
}
- let file_uri = oakstorage::uri::StorageUri::parse(&path.to_string_lossy())
+ let file_uri = oak_storage::uri::StorageUri::parse(&path.to_string_lossy())
.map_err(|e| e.to_string())?;
- oakstorage::writethrough::backend()
+ oak_storage::writethrough::backend()
.export_to_file(&library()?, uuid, &file_uri)
.map_err(|e| e.to_string())
}
@@ -831,7 +831,7 @@ pub fn library_open(uuid: &str) -> Result {
if uuid.is_empty() {
return Err("invalid uuid".to_string());
}
- let result = oakstorage::writethrough::backend()
+ let result = oak_storage::writethrough::backend()
.load(&project_uri(uuid)?)
.map_err(|e| e.to_string())?;
if result.project.is_null() {
@@ -841,7 +841,7 @@ pub fn library_open(uuid: &str) -> Result {
));
}
let handle = result.project;
- let project = unsafe { oakstorage::nodeutil::project_arc(&handle) }
+ let project = unsafe { oak_storage::nodeutil::project_arc(&handle) }
.map_err(|e| e.to_string())?;
lock(&project).set_modified(false);
Ok(project)
@@ -858,33 +858,33 @@ pub fn library_open(uuid: &str) -> Result {
/// facade: an unconfigured library leaves the project unbound but the
/// handle is still usable for queries.
pub fn storage_bind(project: &ProjectRef) -> CHandle {
- let handle = oakstorage::nodeutil::make_project_owned(project.clone());
- oakstorage::writethrough::bind_project(handle);
+ let handle = oak_storage::nodeutil::make_project_owned(project.clone());
+ oak_storage::writethrough::bind_project(handle);
handle
}
/// Whether the project behind `handle` is bound to the write-through
/// session (the status bar's write state).
pub fn storage_bound(handle: &CHandle) -> bool {
- oakstorage::writethrough::is_bound(*handle)
+ oak_storage::writethrough::is_bound(*handle)
}
/// The last write-through / snapshot error of the project behind
/// `handle`, if any.
pub fn storage_last_error(handle: &CHandle) -> Option {
- oakstorage::writethrough::last_error(*handle)
+ oak_storage::writethrough::last_error(*handle)
}
/// Flush the project's pending writes, drop its binding and release the
/// query handle (the engine's project-close path).
pub fn storage_unbind(handle: CHandle) {
- oakstorage::writethrough::unbind_project(handle);
- oakstorage::nodeutil::release_project(handle);
+ oak_storage::writethrough::unbind_project(handle);
+ oak_storage::nodeutil::release_project(handle);
}
/// Flush every bound project and stop the snapshot thread (app exit).
pub fn storage_flush() {
- oakstorage::writethrough::flush_all();
+ oak_storage::writethrough::flush_all();
}
// ---------------------------------------------------------------------------
@@ -892,25 +892,25 @@ pub fn storage_flush() {
//
// The timeline edits the facade's `oakengine_sequence_*` / `oakengine_clip_*`
// exports performed, rebuilt over the oaktimeline command structs and the
-// oakundo global stack's safe [`oakundo::global::push`]. Every entry point
+// oakundo global stack's safe [`oak_undo::global::push`]. Every entry point
// pushes exactly one undo row (composite edits assemble a multi command).
// ---------------------------------------------------------------------------
/// Push one command onto the global undo stack (redo then record).
-pub fn push_command(cmd: oakundo::undocommand::UndoCommand, name: &str) -> Result<(), String> {
- oakundo::global::push(cmd, name).map_err(|e| e.to_string())
+pub fn push_command(cmd: oak_undo::undocommand::UndoCommand, name: &str) -> Result<(), String> {
+ oak_undo::global::push(cmd, name).map_err(|e| e.to_string())
}
/// Assemble `children` into ONE multi command and push it (an empty set
/// is a no-op, mirroring the stack's empty-multi discard).
pub fn push_multi_command(
- children: Vec,
+ children: Vec,
name: &str,
) -> Result<(), String> {
if children.is_empty() {
return Ok(());
}
- let mut multi = oakundo::undocommand::UndoCommand::multi();
+ let mut multi = oak_undo::undocommand::UndoCommand::multi();
for child in children {
multi.multi_add_child(child);
}
@@ -918,12 +918,12 @@ pub fn push_multi_command(
}
/// Push one command (private alias).
-fn push(cmd: oakundo::undocommand::UndoCommand, name: &str) -> Result<(), String> {
+fn push(cmd: oak_undo::undocommand::UndoCommand, name: &str) -> Result<(), String> {
push_command(cmd, name)
}
/// Assemble and push a multi command (private alias).
-fn push_multi(children: Vec, name: &str) -> Result<(), String> {
+fn push_multi(children: Vec, name: &str) -> Result<(), String> {
push_multi_command(children, name)
}
@@ -935,7 +935,7 @@ pub fn connect_command(
from: NodeId,
to: NodeId,
input_id: &str,
-) -> Result {
+) -> Result {
{
let g = lock(p);
if !g.graph.is_valid(from) || !g.graph.is_valid(to) {
@@ -955,7 +955,7 @@ pub fn connect_command(
}
let (p1, p2) = (p.clone(), p.clone());
let (id1, id2) = (input_id.to_string(), input_id.to_string());
- Ok(oakundo::undocommand::UndoCommand::from_closures(
+ Ok(oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
let _ = g.graph.connect(from, to, &id1, -1);
@@ -977,7 +977,7 @@ pub fn disconnect_command(
p: &ProjectRef,
to: NodeId,
input_id: &str,
-) -> Result {
+) -> Result {
let source = {
let g = lock(p);
let has_input = g
@@ -992,7 +992,7 @@ pub fn disconnect_command(
};
let (p1, p2) = (p.clone(), p.clone());
let (id1, id2) = (input_id.to_string(), input_id.to_string());
- Ok(oakundo::undocommand::UndoCommand::from_closures(
+ Ok(oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
g.graph.disconnect_input(to, &id1, -1);
@@ -1016,7 +1016,7 @@ pub fn set_context_position_command(
context: NodeId,
x: f64,
y: f64,
-) -> Result {
+) -> Result {
let old = {
let g = lock(p);
if !g.graph.is_valid(node) || !g.graph.is_valid(context) {
@@ -1033,7 +1033,7 @@ pub fn set_context_position_command(
})
};
let (p1, p2) = (p.clone(), p.clone());
- Ok(oakundo::undocommand::UndoCommand::from_closures(
+ Ok(oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
if let Some(e) = g.graph.get_mut(node) {
@@ -1068,7 +1068,7 @@ pub fn add_track(p: &ProjectRef, seq: NodeId, kind: TrackType) -> Result Result Result<(), String> {
push(
- oaktimeline::undogeneral::TimelineRemoveTrackCommand::new(node_ref(p, track)).to_command(),
+ oak_timeline::undogeneral::TimelineRemoveTrackCommand::new(node_ref(p, track)).to_command(),
"Remove Track",
)
}
@@ -1134,7 +1134,7 @@ fn set_track_flag(
}
let (p1, p2) = (p.clone(), p.clone());
push(
- oakundo::undocommand::UndoCommand::from_closures(
+ oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
if let Some(t) = g
@@ -1265,7 +1265,7 @@ pub fn place_footage_clip(
// `oaknode_clip_set_media_in` + `oaknode_block_set_length_and_media_in`).
let clip = {
let mut g = lock(p);
- let (core, behavior) = oaknode::block::clip_create();
+ let (core, behavior) = oak_node::block::clip_create();
let id = g.graph.add_node(core, behavior);
if let Some(c) = g
.graph
@@ -1279,14 +1279,14 @@ pub fn place_footage_clip(
id
};
- let place = oaktimeline::undopointer::TrackPlaceBlockCommand::new(
+ let place = oak_timeline::undopointer::TrackPlaceBlockCommand::new(
node_ref(p, list),
track_index as i32,
node_ref(p, clip),
in_r,
)
.to_command();
- let edge = connect_command(p, footage, clip, oaknode::block::clip_input::TEXTURE_INPUT)?;
+ let edge = connect_command(p, footage, clip, oak_node::block::clip_input::TEXTURE_INPUT)?;
push_multi(vec![place, edge], "Add Clip")?;
Ok(clip)
}
@@ -1328,7 +1328,7 @@ pub fn place_footage_clips_linked(
let mut clips = Vec::with_capacity(placements.len());
for _ in placements {
let mut g = lock(p);
- let (core, behavior) = oaknode::block::clip_create();
+ let (core, behavior) = oak_node::block::clip_create();
let id = g.graph.add_node(core, behavior);
if let Some(c) = g
.graph
@@ -1350,7 +1350,7 @@ pub fn place_footage_clips_linked(
.ok_or_else(|| "sequence has no track list for this type".to_string())?
};
commands.push(
- oaktimeline::undopointer::TrackPlaceBlockCommand::new(
+ oak_timeline::undopointer::TrackPlaceBlockCommand::new(
node_ref(p, list),
track_index as i32,
node_ref(p, clip),
@@ -1358,7 +1358,7 @@ pub fn place_footage_clips_linked(
)
.to_command(),
);
- commands.push(connect_command(p, footage, clip, oaknode::block::clip_input::TEXTURE_INPUT)?);
+ commands.push(connect_command(p, footage, clip, oak_node::block::clip_input::TEXTURE_INPUT)?);
}
// Link the group both ways (closure commands capture the current
// links for undo).
@@ -1372,7 +1372,7 @@ pub fn place_footage_clips_linked(
g.graph.links_of(a)
};
let (p1, p2) = (p.clone(), p.clone());
- commands.push(oakundo::undocommand::UndoCommand::from_closures(
+ commands.push(oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
if let Some(entry) = g.graph.get_mut(a) {
@@ -1416,7 +1416,7 @@ pub fn split_clip(p: &ProjectRef, clip: NodeId, time_ts: i64) -> Result<(), Stri
return Err(format!("split time {time_ts} is not strictly inside the clip"));
}
push(
- oaktimeline::undosplit::BlockSplitCommand::new(node_ref(p, clip), point).to_command(),
+ oak_timeline::undosplit::BlockSplitCommand::new(node_ref(p, clip), point).to_command(),
"Split Clip",
)
}
@@ -1427,7 +1427,7 @@ pub fn split_clip(p: &ProjectRef, clip: NodeId, time_ts: i64) -> Result<(), Stri
/// module's own `BlockTrimCommand` applies its length setters with
/// inverted semantics, so the closures carry the correct mapping).
pub fn trim_clip(p: &ProjectRef, clip: NodeId, new_in_ts: i64, new_out_ts: i64) -> Result<(), String> {
- use oaknode::block::BlockCore;
+ use oak_node::block::BlockCore;
if new_in_ts < 0 || new_out_ts <= new_in_ts {
return Err("invalid trim range (need 0 <= new_in < new_out)".to_string());
}
@@ -1459,9 +1459,9 @@ pub fn trim_clip(p: &ProjectRef, clip: NodeId, new_in_ts: i64, new_out_ts: i64)
out_anchored: bool,
old: Rational,
new: Rational,
- ) -> oakundo::undocommand::UndoCommand {
+ ) -> oak_undo::undocommand::UndoCommand {
let (p1, p2) = (p.clone(), p.clone());
- oakundo::undocommand::UndoCommand::from_closures(
+ oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
if let Some(c) = g
@@ -1517,7 +1517,7 @@ fn move_clip_command(
p: &ProjectRef,
clip: NodeId,
new_in_ts: i64,
-) -> Result {
+) -> Result {
let (tb, list, track_index) = {
let g = lock(p);
let track = clip_track(&g.graph, clip).ok_or_else(|| "the clip is not on a track".to_string())?;
@@ -1531,7 +1531,7 @@ fn move_clip_command(
.ok_or_else(|| "the sequence has no valid frame rate".to_string())?;
(tb, list, track_index)
};
- Ok(oaktimeline::undopointer::TrackMoveBlockCommand::new(
+ Ok(oak_timeline::undopointer::TrackMoveBlockCommand::new(
node_ref(p, list),
track_index,
node_ref(p, clip),
@@ -1557,7 +1557,7 @@ fn move_clip_to_track_commands(
clip: NodeId,
dest_track: NodeId,
new_in_ts: i64,
-) -> Result, String> {
+) -> Result, String> {
let (tb, list, dest_index, source_track) = {
let g = lock(p);
let source_track =
@@ -1576,7 +1576,7 @@ fn move_clip_to_track_commands(
};
let in_r = ts_to_rational(new_in_ts, tb);
- let gap = oaktimeline::undogeneral::TrackReplaceBlockWithGapCommand::new(
+ let gap = oak_timeline::undogeneral::TrackReplaceBlockWithGapCommand::new(
node_ref(p, source_track),
node_ref(p, clip),
true,
@@ -1592,7 +1592,7 @@ fn move_clip_to_track_commands(
.ok_or_else(|| "the node is not a clip".to_string())?
};
let (p1, p2) = (p.clone(), p.clone());
- let rehome = oakundo::undocommand::UndoCommand::from_closures(
+ let rehome = oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
if let Some(c) = g
@@ -1616,7 +1616,7 @@ fn move_clip_to_track_commands(
}
},
);
- let place = oaktimeline::undopointer::TrackPlaceBlockCommand::new(
+ let place = oak_timeline::undopointer::TrackPlaceBlockCommand::new(
node_ref(p, list),
dest_index,
node_ref(p, clip),
@@ -1720,13 +1720,13 @@ pub fn delete_clip(p: &ProjectRef, clip: NodeId) -> Result<(), String> {
let g = lock(p);
clip_track(&g.graph, clip).ok_or_else(|| "the clip is not on a track".to_string())?
};
- let gap = oaktimeline::undogeneral::TrackReplaceBlockWithGapCommand::new(
+ let gap = oak_timeline::undogeneral::TrackReplaceBlockWithGapCommand::new(
node_ref(p, source_track),
node_ref(p, clip),
true,
)
.to_command();
- let remove = oaktask::nodeops::remove_node_command(p.clone(), clip);
+ let remove = oak_task::nodeops::remove_node_command(p.clone(), clip);
push_multi(vec![gap, remove], "Delete Clips")
}
@@ -1743,7 +1743,7 @@ pub fn ripple_delete_clip(p: &ProjectRef, clip: NodeId) -> Result<(), String> {
(track, TimeRange::new(in_r, out_r))
};
push(
- oaktimeline::undoripple::TrackRippleRemoveAreaCommand::new(node_ref(p, track), range)
+ oak_timeline::undoripple::TrackRippleRemoveAreaCommand::new(node_ref(p, track), range)
.to_command(),
"Ripple Delete Clip",
)
@@ -1757,7 +1757,7 @@ pub fn marker_add(markers: &CHandle, time: Rational, name: &str, color: i32) ->
return Err("a marker already exists at that time".to_string());
}
push(
- oaktimeline::marker::MarkerAddCommand::new(
+ oak_timeline::marker::MarkerAddCommand::new(
*markers,
TimeRange::new(time, time),
name,
@@ -1775,7 +1775,7 @@ pub fn marker_remove(markers: &CHandle, time: Rational) -> Result<(), String> {
return Err("no marker at that time".to_string());
};
push(
- oaktimeline::marker::MarkerRemoveCommand::new(*markers, index).to_command(),
+ oak_timeline::marker::MarkerRemoveCommand::new(*markers, index).to_command(),
"Remove Marker",
)
}
@@ -1793,8 +1793,8 @@ pub fn workarea_set_undoable(
) -> Result<(), String> {
push_multi(
vec![
- oaktimeline::workarea::WorkareaSetEnabledCommand::new(*wa, enabled).to_command(),
- oaktimeline::workarea::WorkareaSetRangeCommand::new_with_old(*wa, range, old_range)
+ oak_timeline::workarea::WorkareaSetEnabledCommand::new(*wa, enabled).to_command(),
+ oak_timeline::workarea::WorkareaSetRangeCommand::new_with_old(*wa, range, old_range)
.to_command(),
],
"Set Workarea",
@@ -1807,7 +1807,7 @@ pub fn workarea_set_undoable(
/// removed.
pub fn remove_node(p: &ProjectRef, node: NodeId) -> Result<(), String> {
push(
- oaktimeline::undocommon::create_remove_command(&node_ref(p, node)),
+ oak_timeline::undocommon::create_remove_command(&node_ref(p, node)),
"Remove Node",
)
}
@@ -1954,7 +1954,7 @@ pub fn paste_clips(
let clip = {
let mut g = lock(p);
- let (core, behavior) = oaknode::block::clip_create();
+ let (core, behavior) = oak_node::block::clip_create();
let id = g.graph.add_node(core, behavior);
if let Some(c) = g
.graph
@@ -1970,7 +1970,7 @@ pub fn paste_clips(
};
new_ids.push(clip);
commands.push(
- oaktimeline::undopointer::TrackPlaceBlockCommand::new(
+ oak_timeline::undopointer::TrackPlaceBlockCommand::new(
node_ref(p, list),
track_index as i32,
node_ref(p, clip),
@@ -1982,7 +1982,7 @@ pub fn paste_clips(
p,
item.footage,
clip,
- oaknode::block::clip_input::TEXTURE_INPUT,
+ oak_node::block::clip_input::TEXTURE_INPUT,
)?);
}
// Link the pasted group both ways (the C++ pastes linked selections as
@@ -1991,7 +1991,7 @@ pub fn paste_clips(
let first = new_ids[0];
for &other in &new_ids[1..] {
let (p1, p2) = (p.clone(), p.clone());
- commands.push(oakundo::undocommand::UndoCommand::from_closures(
+ commands.push(oak_undo::undocommand::UndoCommand::from_closures(
move || {
let mut g = lock(&p1);
for (a, b) in [(first, other), (other, first)] {
@@ -2057,7 +2057,7 @@ mod tests {
#[test]
fn track_flag_setters_toggle_and_undo() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, _seq, track) = project_with_track(TrackType::Video);
assert_eq!(track_muted(&project, track), Some(false));
@@ -2068,27 +2068,27 @@ mod tests {
set_track_locked(&project, track, true).expect("lock the track");
assert_eq!(track_locked(&project, track), Some(true));
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(track_locked(&project, track), Some(false));
- oakundo::global::undo().unwrap();
+ oak_undo::global::undo().unwrap();
assert_eq!(track_muted(&project, track), Some(false));
- oakundo::global::redo().unwrap();
+ oak_undo::global::redo().unwrap();
assert_eq!(track_muted(&project, track), Some(true));
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
/// Setting a flag to its current value pushes no undo row.
#[test]
fn track_flag_setter_noop_when_unchanged() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, _seq, track) = project_with_track(TrackType::Audio);
- let before = oakundo::global::count().unwrap();
+ let before = oak_undo::global::count().unwrap();
set_track_muted(&project, track, false).expect("mute already false");
set_track_locked(&project, track, false).expect("lock already false");
- assert_eq!(oakundo::global::count().unwrap(), before);
- oakundo::global::clear().unwrap();
+ assert_eq!(oak_undo::global::count().unwrap(), before);
+ oak_undo::global::clear().unwrap();
}
/// Undo/redo stability: undo → redo → undo → redo must converge to the
@@ -2098,11 +2098,11 @@ mod tests {
#[test]
fn undo_redo_cycles_converge_to_the_same_state() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let project = create_project();
let seq = create_sequence(&project, "Undo Cycles");
let media = std::env::temp_dir().join(format!("oak_undo_cycle_{}.mp4", std::process::id()));
- oakcodec::testmedia::write_test_clip(&media, 64, 64, 10, 10).expect("generate test media");
+ oak_codec::testmedia::write_test_clip(&media, 64, 64, 10, 10).expect("generate test media");
let footage = import_footage(&project, &media).expect("import");
place_footage_clips_linked(
&project,
@@ -2133,7 +2133,7 @@ mod tests {
// Two full undo/redo cycles; the state must be identical after
// every redo and match the pre-undo state after every undo.
for cycle in 0..2 {
- oakundo::global::undo().expect("undo");
+ oak_undo::global::undo().expect("undo");
let g = lock(&project);
let video_blocks: usize = track_ids(&g.graph, seq, TrackType::Video)
.iter()
@@ -2141,11 +2141,11 @@ mod tests {
.sum();
assert_eq!(video_blocks, 0, "cycle {cycle}: undo removes the clips");
drop(g);
- oakundo::global::redo().expect("redo");
+ oak_undo::global::redo().expect("redo");
let state = snapshot(&project);
assert_eq!(state, before, "cycle {cycle}: redo must restore the exact state");
}
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let _ = std::fs::remove_file(&media);
}
@@ -2153,11 +2153,11 @@ mod tests {
#[test]
fn track_flag_setters_reject_non_tracks() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let (project, seq, _track) = project_with_track(TrackType::Video);
assert!(set_track_muted(&project, seq, true).is_err());
assert!(set_track_locked(&project, seq, true).is_err());
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
}
@@ -2171,7 +2171,7 @@ mod undo_cycle_track_tests {
#[test]
fn add_track_undo_redo_cycles_converge() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let project = create_project();
let seq = create_sequence(&project, "Add Track Cycles");
let count_of = |p: &ProjectRef, kind: TrackType| {
@@ -2188,13 +2188,13 @@ mod undo_cycle_track_tests {
};
for cycle in 0..3 {
- oakundo::global::undo().expect("undo");
+ oak_undo::global::undo().expect("undo");
assert_eq!(
count_of(&project, TrackType::Video),
2,
"cycle {cycle}: undo removes only the added track"
);
- oakundo::global::redo().expect("redo");
+ oak_undo::global::redo().expect("redo");
assert_eq!(
count_of(&project, TrackType::Video),
3,
@@ -2212,7 +2212,7 @@ mod undo_cycle_track_tests {
);
drop(g);
}
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
}
}
@@ -2250,8 +2250,8 @@ mod undo_cycle_ops_tests {
fn cycle_assert(p: &ProjectRef, seq: NodeId, post: &(usize, Vec<(NodeId, Vec<(NodeId, i128, i128)>)>), what: &str) {
for cycle in 0..3 {
- oakundo::global::undo().unwrap_or_else(|e| panic!("{what}: undo failed: {e:?}"));
- oakundo::global::redo().unwrap_or_else(|e| panic!("{what}: redo failed: {e:?}"));
+ oak_undo::global::undo().unwrap_or_else(|e| panic!("{what}: undo failed: {e:?}"));
+ oak_undo::global::redo().unwrap_or_else(|e| panic!("{what}: redo failed: {e:?}"));
let state = snapshot(p, seq);
assert_eq!(&state, post, "{what}: cycle {cycle} diverged");
}
@@ -2281,9 +2281,9 @@ mod undo_cycle_ops_tests {
#[test]
fn move_trim_delete_split_cycles_converge() {
let _g = test_lock();
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let media = std::env::temp_dir().join(format!("oak_cycle_ops_{}.mp4", std::process::id()));
- oakcodec::testmedia::write_test_clip(&media, 64, 64, 10, 10).expect("generate");
+ oak_codec::testmedia::write_test_clip(&media, 64, 64, 10, 10).expect("generate");
// --- move ---
let (project, seq, _footage) = project_with_two_clips(&media);
@@ -2297,25 +2297,25 @@ mod undo_cycle_ops_tests {
move_clip(&project, clip, 20).expect("move");
let post = snapshot(&project, seq);
cycle_assert(&project, seq, &post, "move");
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
// --- trim ---
trim_clip(&project, clip, 22, 28).expect("trim");
let post = snapshot(&project, seq);
cycle_assert(&project, seq, &post, "trim");
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
// --- split ---
split_clip(&project, clip, 25).expect("split");
let post = snapshot(&project, seq);
cycle_assert(&project, seq, &post, "split");
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
// --- delete ---
delete_clip(&project, clip).expect("delete");
let post = snapshot(&project, seq);
cycle_assert(&project, seq, &post, "delete");
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
// --- ripple delete (on a fresh project, then check) ---
let (project, seq, _footage) = project_with_two_clips(&media);
@@ -2329,7 +2329,7 @@ mod undo_cycle_ops_tests {
ripple_delete_clip(&project, clip).expect("ripple delete");
let post = snapshot(&project, seq);
cycle_assert(&project, seq, &post, "ripple delete");
- oakundo::global::clear().unwrap();
+ oak_undo::global::clear().unwrap();
let _ = std::fs::remove_file(&media);
}
diff --git a/src/oakui/icons.rs b/crates/oak-app/src/oakui/icons.rs
similarity index 98%
rename from src/oakui/icons.rs
rename to crates/oak-app/src/oakui/icons.rs
index ac3aa09b1..40f1dd984 100644
--- a/src/oakui/icons.rs
+++ b/crates/oak-app/src/oakui/icons.rs
@@ -59,7 +59,7 @@ pub fn icon_path(name: &str, cx: &App) -> PathBuf {
"light"
};
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
- .join("assets/icons")
+ .join("../../assets/icons")
.join(family)
.join(format!("{name}.png"))
}
@@ -103,7 +103,7 @@ mod tests {
] {
for family in ["dark", "light"] {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
- .join("assets/icons")
+ .join("../../assets/icons")
.join(family)
.join(format!("{name}.png"));
assert!(
diff --git a/src/oakui/mock.rs b/crates/oak-app/src/oakui/mock.rs
similarity index 98%
rename from src/oakui/mock.rs
rename to crates/oak-app/src/oakui/mock.rs
index c37a84fc1..881886f81 100644
--- a/src/oakui/mock.rs
+++ b/crates/oak-app/src/oakui/mock.rs
@@ -61,10 +61,10 @@ use gpui_widgets::audio_meter::AudioMeterDataSource;
use gpui_widgets::project_explorer::{ProjectDataSource, ProjectEntry};
use gpui_widgets::viewer::PlaybackClock;
-use oakcore_rs::Rational;
-use oaknode::block::clip_input;
-use oaknode::track::TrackType;
-use oaktimeline::util::{block_clip_create, track_append_block};
+use oak_core::Rational;
+use oak_node::block::clip_input;
+use oak_node::track::TrackType;
+use oak_timeline::util::{block_clip_create, track_append_block};
use super::engine::{
AppEngine, EngineGateway, ExportEvent, ExportSession, LibraryProject, Monitor, MulticamState,
@@ -543,7 +543,7 @@ pub struct MockEngine {
/// sequence's video tracks are the angles. Created lazily so the demo
/// panel shows a genuine graph behind its synthetic frames — and the
/// switch / enable / disable commands run on the real command path
- /// (`oaktimeline::multicam` + the global undo stack).
+ /// (`oak_timeline::multicam` + the global undo stack).
multicam_graph: Mutex