From 63b487589c7b931ac5e8a5e312d383d76a1af3f0 Mon Sep 17 00:00:00 2001
From: skylar taylor-barrick
Date: Sat, 10 Oct 2026 15:37:36 -0600
Subject: [PATCH] UI.9: preserve SoundCraft workflows with shared docking
---
Cargo.lock | 10 +
Cargo.toml | 1 +
README.md | 5 +-
ROADMAP.md | 6 +
apps/soundcraft-cli/src/main.rs | 2 +-
apps/soundcraft/src/config.rs | 192 +++++++++
apps/soundcraft/src/main.rs | 40 +-
crates/mix/src/lib.rs | 2 +-
crates/ui-egui/Cargo.toml | 3 +-
crates/ui-egui/src/docking.rs | 475 ++++++++++++++++++++++
crates/ui-egui/src/edit_window.rs | 33 +-
crates/ui-egui/src/lib.rs | 9 +
crates/ui-egui/src/menus.rs | 24 +-
crates/ui-egui/src/mix_window.rs | 54 +--
crates/ui-egui/src/panels.rs | 140 +++----
crates/ui-egui/src/widgets.rs | 260 ++++++++++--
crates/ui-egui/src/windows.rs | 13 +-
crates/ui-egui/tests/docking_workflows.rs | 159 ++++++++
crates/ui-egui/tests/midi_panel.rs | 79 +++-
crates/ui-egui/tests/shared_controls.rs | 217 ++++++++++
crates/ui-egui/tests/theme_docking.rs | 83 ++++
docs/architecture.md | 7 +
docs/control-protocol.md | 31 ++
xtask/src/main.rs | 2 +-
24 files changed, 1659 insertions(+), 188 deletions(-)
create mode 100644 apps/soundcraft/src/config.rs
create mode 100644 crates/ui-egui/src/docking.rs
create mode 100644 crates/ui-egui/tests/docking_workflows.rs
create mode 100644 crates/ui-egui/tests/shared_controls.rs
create mode 100644 crates/ui-egui/tests/theme_docking.rs
diff --git a/Cargo.lock b/Cargo.lock
index 8b26758..cb38648 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -782,6 +782,15 @@ dependencies = [
"windows 0.54.0",
]
+[[package]]
+name = "craft-ui"
+version = "0.1.0"
+source = "git+https://github.com/storytold/craft-libs?rev=5c874d51eeb3de70e735c8ce87a59a7dedd47675#5c874d51eeb3de70e735c8ce87a59a7dedd47675"
+dependencies = [
+ "egui",
+ "serde",
+]
+
[[package]]
name = "crc32fast"
version = "1.5.2"
@@ -3570,6 +3579,7 @@ dependencies = [
name = "soundcraft-ui-egui"
version = "0.4.0"
dependencies = [
+ "craft-ui",
"egui",
"egui_kittest",
"image",
diff --git a/Cargo.toml b/Cargo.toml
index 95b9a8e..083ceba 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,6 +41,7 @@ rustfft = "6"
cpal = "0.16"
# ui
+craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
egui = "0.36"
eframe = { version = "0.36", default-features = false }
egui_kittest = "0.36"
diff --git a/README.md b/README.md
index 89ec40e..26101e1 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,8 @@ where things are.
## Getting started
Download a build from the [releases page](https://github.com/storytold/soundcraft/releases), or build
-from source with a recent stable Rust:
+from source with Rust 1.95 or later for the desktop/web UI (the core workspace's declared
+minimum remains Rust 1.90):
```sh
git clone https://github.com/storytold/soundcraft
@@ -366,3 +367,5 @@ Forks and modified versions must remove them.

Made by the ArtCraft team and community.
+
+Tracks and Groups, Clips, MIDI Editor, Memory Locations, and Undo History use movable panels. Edit and Mix retain separate arrangements; Clips and MIDI Editor belong to Edit. Transport, plug-in, and specialist utility windows retain their existing hosts. Drag a panel header to a target edge or another header. Its context menu offers Float panel, Move to group, and Close panel. Existing panel toggles reopen panels at their saved location; Reset Panel Layout restores the app layout. Floating panels remain inside the current app window.
diff --git a/ROADMAP.md b/ROADMAP.md
index a89da3f..14ef908 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -61,6 +61,10 @@ mutated and truncated movies.
## Current focus
+Common toolbar icon buttons and track record-enable buttons use shared `craft-ui` interaction,
+accessible labels and keyboard focus, retaining SoundCraft's blue tool-selection and red record-arm
+states. The specialized transport buttons, faders, meters and saved session model remain local.
+
1. The alpha list above.
2. Surround-pan automation; AU editor views.
3. Built-in plugin depth (more processors, better UIs).
@@ -76,3 +80,5 @@ mutated and truncated movies.
- **M9 (in progress):** AudioSuite breadth (done), Elastic Audio, Beat Detective.
- **M10 (next):** alpha release on all platforms.
- **M11 (mostly done):** third-party plugin hosting (CLAP, VST3, AU), video, surround.
+
+- **2026-10-10 (UI docking):** Tracks and Groups, Clips, MIDI Editor, Memory Locations, and Undo History use movable panels. Edit and Mix retain separate arrangements; Clips and MIDI Editor belong to Edit. Transport, plug-in, and specialist utility windows retain their existing hosts. Panel moves and reset are available as UI commands, and saved UI preferences retain arrangements. No milestone percentage change.
diff --git a/apps/soundcraft-cli/src/main.rs b/apps/soundcraft-cli/src/main.rs
index 2ba9112..b127f19 100644
--- a/apps/soundcraft-cli/src/main.rs
+++ b/apps/soundcraft-cli/src/main.rs
@@ -299,7 +299,7 @@ fn app(args: &[String]) -> ExitCode {
let positional: Vec<&String> = args
.iter()
.enumerate()
- .filter(|(i, a)| !a.starts_with("--") && !(*i > 0 && args.get(i - 1).is_some_and(|p| p == "--port")))
+ .filter(|(i, a)| !(a.starts_with("--") || (*i > 0 && args.get(i - 1).is_some_and(|p| p == "--port"))))
.map(|(_, a)| a)
.collect();
let Some(method) = positional.first() else { return fail("app [--port P] METHOD [JSON]") };
diff --git a/apps/soundcraft/src/config.rs b/apps/soundcraft/src/config.rs
new file mode 100644
index 0000000..71abd34
--- /dev/null
+++ b/apps/soundcraft/src/config.rs
@@ -0,0 +1,192 @@
+//! Per-user settings and automation profile directories.
+
+use std::ffi::OsString;
+use std::path::PathBuf;
+
+#[derive(Clone, Copy)]
+enum Platform {
+ Mac,
+ Windows,
+ Other,
+}
+
+/// SoundCraft's settings folder. A nonempty `$SOUNDCRAFT_CONFIG_DIR` overrides the platform default.
+/// The override is the complete directory; no application-name suffix is appended. An explicitly
+/// empty value disables the settings directory instead of falling back to a personal profile.
+pub fn config_dir() -> Option {
+ let platform = if cfg!(target_os = "macos") {
+ Platform::Mac
+ } else if cfg!(windows) {
+ Platform::Windows
+ } else {
+ Platform::Other
+ };
+ config_dir_with(platform, |name| std::env::var_os(name))
+}
+
+fn config_dir_with(platform: Platform, env: impl Fn(&str) -> Option) -> Option {
+ if let Some(dir) = env("SOUNDCRAFT_CONFIG_DIR") {
+ return (!dir.is_empty()).then(|| PathBuf::from(dir));
+ }
+ match platform {
+ Platform::Mac => env("HOME").map(|h| PathBuf::from(h).join("Library/Application Support/SoundCraft")),
+ Platform::Windows => env("APPDATA").map(|a| PathBuf::from(a).join("SoundCraft")),
+ Platform::Other => {
+ env("XDG_CONFIG_HOME").map(PathBuf::from).or_else(|| env("HOME").map(|h| PathBuf::from(h).join(".config"))).map(|c| c.join("soundcraft"))
+ }
+ }
+}
+
+/// Read a bounded native settings file and reject invalid docking trees before using them.
+pub fn load_ui(path: &std::path::Path) -> Result