3 Commits

Author SHA1 Message Date
Marcos Paulo 09172264e9 feat(ui): Catppuccin themes
Adds Latte, Frappé, Macchiato and Mocha to the Appearance setting (ui.theme
ids catppuccinLatte|catppuccinFrappe|catppuccinMacchiato|catppuccinMocha),
listed in Setup > Appearance.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 03:13:29 -03:00
Marcos Paulo 097fbd0c8a build: use the masi/craft-libs fork
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 02:57:33 -03:00
Marcos Paulo 30944cd0ab chore: debrand ArtCraft and Discord references
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 02:57:21 -03:00
29 changed files with 221 additions and 187 deletions
+12
View File
@@ -0,0 +1,12 @@
# Strip upstream (ArtCraft / Discord) branding. Run: perl tools/craft-skills/skills/debrand/debrand.pl . (--check in CI)
delete docs/brand
# MIT/Apache texts stay verbatim. The copyright holder line stays everywhere (legal requirement).
keep ^(LICENSE-(MIT|APACHE)|\.debrand)$
dropline ^ATTRIBUTION\.md$ :: \|\s*`docs/brand/
sub (?s)\nThe ArtCraft name, wordmark and logos in docs/brand/.*?LICENSE-brand\.txt\.\n =>
sub \n- The ArtCraft trademarks in `docs/brand/` are the only exception, under `docs/brand/LICENSE-brand\.txt`\. =>
sub until the owner draws SoundCraft's icon in ArtCraft like => until the owner draws SoundCraft's icon like
sub https://getartcraft\.com/apps/soundcraft => https://github.com/storytold/soundcraft
sub https://discord\.gg/artcraft => https://github.com/storytold/soundcraft/issues
forbid (?i)artcraft(?! Team and the SoundCraft contributors)
forbid (?i)discord
+3
View File
@@ -0,0 +1,3 @@
[submodule "tools/craft-skills"]
path = tools/craft-skills
url = https://git.marcospaulo.dev.br/storytold/craft-skills.git
-1
View File
@@ -16,7 +16,6 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
- **Every bundled asset gets a row in [`ATTRIBUTION.md`](ATTRIBUTION.md)** (path, author, source, license) in the same commit that adds it. `cargo xtask assets` enforces this; CI fails otherwise.
- Never copy files out of the Pro Tools bundle (`/Applications/Pro Tools.app`): no plugins, presets, icons, sounds, session templates or text. Observing the running app (screenshots kept under `plan/`, menu names) is fine; copying its resources is not.
- Demo audio is synthesised in code (`crates/engine/src/demo.rs`). Demo and README media must be ours or public domain.
- The ArtCraft trademarks in `docs/brand/` are the only exception, under `docs/brand/LICENSE-brand.txt`.
## Clean-room
Pro Tools is installed on the dev machine and may be *observed* black-box: run it, use its UI with synthetic sessions, take screenshots by window id (`screencapture -l <id>`), stored only under `plan/protools/screenshots/`. Never read, disassemble or copy anything inside the Pro Tools bundle; never copy Avid icons, artwork, plugin names, presets, or wording beyond feature names. Never copy GPL/AGPL/LGPL code (Ardour, Audacity, LMMS, Freeverb sources…). Our plugins have our own names.
-8
View File
@@ -29,14 +29,6 @@ and, at runtime, the operating system's own UI fonts when available (never redis
| Path | Author | Source | License |
|---|---|---|---|
| `docs/brand/artcraft-logo.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms (`docs/brand/LICENSE-brand.txt`) |
| `docs/brand/artcraft-logo.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-logo-white.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-logo-white.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `assets/app-icon/icon.svg` | SoundCraft contributors | original (hand-placed SVG paths; placeholder app icon) | MIT OR Apache-2.0 |
| `packaging/macos/dmg/` (all files) | @XusBadia | original (macOS DMG window background and layout, from the app icon; see `packaging/macos/dmg/README.md`) | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft-1024.png` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
Generated
+1 -1
View File
@@ -785,7 +785,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=5c874d51eeb3de70e735c8ce87a59a7dedd47675#5c874d51eeb3de70e735c8ce87a59a7dedd47675"
source = "git+https://git.marcospaulo.dev.br/masi/craft-libs?rev=22544b6d619d4f16779b8bdfa3f0b642a86fc065#22544b6d619d4f16779b8bdfa3f0b642a86fc065"
dependencies = [
"egui",
"serde",
+1 -1
View File
@@ -41,7 +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"] }
craft-ui = { version = "0.1.0", git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "22544b6d619d4f16779b8bdfa3f0b642a86fc065", features = ["serde"] }
egui = "0.36"
eframe = { version = "0.36", default-features = false }
egui_kittest = "0.36"
-4
View File
@@ -3,10 +3,6 @@ Copyright (c) 2026 ArtCraft Team and the SoundCraft contributors.
Licensed under the MIT License or the Apache License, Version 2.0, at your option
(LICENSE-MIT, LICENSE-APACHE).
The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are
not open source and are not covered by the license above; their terms are in
docs/brand/LICENSE-brand.txt.
Third-party material
This product bundles third-party material under its own licenses, listed below. The per-file
+12 -68
View File
@@ -1,12 +1,3 @@
<p align="center">
<a href="https://getartcraft.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/artcraft-logo-white.svg">
<img alt="ArtCraft" src="docs/brand/artcraft-logo.svg" width="200">
</picture>
</a>
</p>
<h1 align="center">SoundCraft</h1>
<p align="center">
@@ -27,27 +18,12 @@
<img alt="Status: pre-alpha" src="https://img.shields.io/badge/status-pre--alpha-orange?style=flat-square">
</p>
<p align="center">
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
</p>
<p align="center">
<a href="https://getartcraft.com/apps/soundcraft"><b>SoundCraft on getartcraft.com</b></a> ·
<a href="https://getartcraft.com/">ArtCraft</a> ·
<a href="https://getartcraft.com/apps">All Crafting Apps</a>
</p>
<p align="center">
<img alt="SoundCraft's Edit window with a nine-track session: drums, bass, pad, keys and lead, a reverb aux and a master fader" src="docs/images/edit.png" width="100%">
<br>
<sub>The Edit window: tracks, waveforms, MIDI, rulers, markers and the transport, playing the built-in demo song (every sound in it is synthesised by SoundCraft itself).</sub>
</p>
> [!NOTE]
> **ArtCraft is a community of artists from all walks of life.** Painters, photographers,
> filmmakers, illustrators, designers, animators, hobbyists, and people who picked up a pencil
> last week. If you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**
<p align="center">
<a href="#what-it-does">What it does</a> ·
<a href="#getting-started">Getting started</a> ·
@@ -246,7 +222,7 @@ estimates are in [`ROADMAP.md`](ROADMAP.md); the full assessment is in
[`docs/target-app-parity.md`](docs/target-app-parity.md), the ranked list of what's missing in
[`docs/gaps.md`](docs/gaps.md), and the menu-by-menu comparison in
[`docs/parity-checklist.md`](docs/parity-checklist.md). Bug reports and wish lists are
very welcome, in the issues or on Discord.
very welcome, in the issues.
## Downloads
@@ -311,44 +287,22 @@ RISC-V (riscv64): `soundcraft-<ver>-linux-riscv64.tar.gz`, a tarball only (glibc
## The Crafting Apps
SoundCraft is one of the **Crafting Apps**: free, open-source creative tools from the
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
[storytold](https://github.com/storytold) team, each written from scratch in Rust and each able to
stand on its own.
| | App | What it's for | Code | Learn more |
|:-:|---|---|---|---|
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | **Recording, editing and mixing audio · you are here** | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
| | App | What it's for | Code |
|:-:|---|---|---|
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) |
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) |
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) |
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) |
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) |
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) |
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) |
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | **Recording, editing and mixing audio · you are here** | [GitHub](https://github.com/storytold/soundcraft) |
<br>
<p align="center">
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
</p>
<h3 align="center">Come make things with us</h3>
<p align="center">
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film,
set type, and people still figuring out what they like to make. Share what you're working on,
ask for help, tell us what's broken, or tell us what you wish these tools could do.
Whatever your medium and however long you've been at it, you're welcome here.
</p>
<p align="center">
<a href="https://discord.gg/artcraft"><b>discord.gg/artcraft</b></a> ·
<a href="https://getartcraft.com/">getartcraft.com</a> ·
<a href="https://getartcraft.com/apps">The Crafting Apps</a> ·
<a href="https://getartcraft.com/apps/soundcraft">SoundCraft</a>
</p>
## License and credits
SoundCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
@@ -361,18 +315,8 @@ Every icon in the interface is drawn in code, the app icon is original, and the
screenshots' audio are synthesised by SoundCraft itself, so there are no samples or artwork from
anyone else in this repository.
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
part of this repository and SoundCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
Forks and modified versions must remove them.
<sub>Avid and Pro Tools are trademarks or registered trademarks of Avid Technology, Inc. in the United States and/or other countries. SoundCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Avid Technology, Inc.; these names are used only to describe the workflows it is compatible with.</sub>
<sub>Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. SoundCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Adobe Inc.; these names are used only to describe the workflows it is compatible with.</sub>
<p align="center">
<a href="https://getartcraft.com/"><img alt="ArtCraft" src="docs/brand/artcraft-mark.svg" width="28"></a><br>
<sub>Made by the <a href="https://getartcraft.com/">ArtCraft</a> team and community.</sub>
</p>
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.
+1 -1
View File
@@ -5,7 +5,7 @@ owl-template framing (craftrules `standards/icon-design.md`): a full-bleed colou
in three-quarter view with its bill open and its eye on the viewer, its breast and wing running off
the bottom and right edges, three song arcs leaving the bill.
This is a **placeholder** until the owner draws SoundCraft's icon in ArtCraft like the other apps'
This is a **placeholder** until the owner draws SoundCraft's icon like the other apps'
icons. Replace `icon.svg` and run `packaging/icons.sh`; nothing else needs to change.
## Palette
+6 -6
View File
@@ -99,7 +99,7 @@ pub struct UiState {
pub video_burn_in: bool,
pub workspace_dir: String,
pub configurations: Vec<(String, Value)>,
/// Appearance (`ui.theme`): dark by default, light or follow the system on request.
/// Appearance (`ui.theme`): dark by default; light, a Catppuccin flavour or the system on request.
pub theme: theme::ThemeMode,
}
@@ -234,7 +234,7 @@ pub struct SoundApp {
fonts_ready: bool,
fonts_installed: bool,
/// The palette last applied to the context (light?), so visuals are set only on a change.
theme_applied: Option<bool>,
theme_applied: Option<theme::ThemeMode>,
/// Last panel host context, used to cancel renderer-owned gestures on explicit replacement.
pub(crate) docking_context: Option<egui::Context>,
/// Transport simulation when there is no player (tests, offscreen renders).
@@ -769,10 +769,10 @@ impl SoundApp {
ctx.request_repaint();
}
}
let light = theme::wants_light(ctx, self.ui.theme);
if self.theme_applied != Some(light) || theme::is_light() != light {
theme::apply(ctx, light);
self.theme_applied = Some(light);
let mode = theme::resolve(ctx, self.ui.theme);
if self.theme_applied != Some(mode) || theme::active_mode() != mode {
theme::apply(ctx, mode);
self.theme_applied = Some(mode);
}
let now = ctx.input(|i| i.time);
let dt = self.last_frame.map_or(1.0 / 60.0, |t| (now - t) as f32).clamp(0.0, 0.25);
+1 -1
View File
@@ -571,7 +571,7 @@ pub fn run_ui_command(app: &mut SoundApp, id: &str, p: &Value) -> Option<Result<
return Some(Ok(json!({"mode": app.ui.theme.id()})));
};
let Some(mode) = crate::theme::ThemeMode::parse(mode) else {
return Some(Err(format!("unknown theme `{mode}` (system, light or dark)")));
return Some(Err(format!("unknown theme `{mode}` ({})", crate::theme::ThemeMode::id_list())));
};
app.ui.theme = mode;
json!({"mode": mode.id()})
+2 -3
View File
@@ -501,10 +501,9 @@ fn about(app: &mut SoundApp, ctx: &egui::Context) {
_ => {
ui.label(egui::RichText::new("SoundCraft").font(bold(22.0)));
ui.label(format!("Version {}", env!("CARGO_PKG_VERSION")));
ui.label("An open-source digital audio workstation from the ArtCraft team.");
ui.label("An open-source digital audio workstation from the storytold team.");
ui.label("Dual-licensed MIT OR Apache-2.0. Made with Rust and egui.");
ui.hyperlink_to("getartcraft.com/apps/soundcraft", "https://getartcraft.com/apps/soundcraft");
ui.hyperlink_to("Join us on Discord", "https://discord.gg/artcraft");
ui.hyperlink_to("github.com/storytold/soundcraft", "https://github.com/storytold/soundcraft");
}
}
});
+168 -16
View File
@@ -1,10 +1,11 @@
//! Design tokens. Colours were chosen by eye to give a dark, studio-style look; they are ours.
use craft_ui::theme::catppuccin::Flavor;
use egui::{Color32, FontFamily, FontId};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicU8, Ordering};
/// Whether the Light palette is active. Dark is the default (the incumbent is dark-only).
static LIGHT_THEME: AtomicBool = AtomicBool::new(false);
/// Index into `ThemeMode::ALL` of the active palette. Dark (0) is the default (the incumbent is dark-only).
static ACTIVE_THEME: AtomicU8 = AtomicU8::new(0);
/// Unit tests that change the process-wide palette must own it until their assertions finish.
#[cfg(test)]
@@ -13,7 +14,7 @@ pub(crate) fn lock_test_palette() -> std::sync::MutexGuard<'static, ()> {
PALETTE.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
/// The Appearance setting (`ui.theme {mode}`). Dark by default; System and Light are opt-in.
/// The Appearance setting (`ui.theme {mode}`). Dark by default; System, Light and the Catppuccin flavours are opt-in.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ThemeMode {
@@ -21,16 +22,45 @@ pub enum ThemeMode {
Light,
#[default]
Dark,
#[serde(rename = "catppuccinLatte")]
CatppuccinLatte,
#[serde(rename = "catppuccinFrappe")]
CatppuccinFrappe,
#[serde(rename = "catppuccinMacchiato")]
CatppuccinMacchiato,
#[serde(rename = "catppuccinMocha")]
CatppuccinMocha,
}
impl ThemeMode {
pub const ALL: [Self; 3] = [Self::Dark, Self::Light, Self::System];
pub const ALL: [Self; 7] =
[Self::Dark, Self::Light, Self::System, Self::CatppuccinLatte, Self::CatppuccinFrappe, Self::CatppuccinMacchiato, Self::CatppuccinMocha];
/// The Catppuccin flavour behind this mode, if it is one.
pub const fn flavor(self) -> Option<Flavor> {
match self {
Self::CatppuccinLatte => Some(Flavor::Latte),
Self::CatppuccinFrappe => Some(Flavor::Frappe),
Self::CatppuccinMacchiato => Some(Flavor::Macchiato),
Self::CatppuccinMocha => Some(Flavor::Mocha),
Self::System | Self::Light | Self::Dark => None,
}
}
/// Every id, `|`-separated: for error messages.
pub fn id_list() -> String {
Self::ALL.map(Self::id).join("|")
}
pub const fn label(self) -> &'static str {
match self {
Self::System => "System",
Self::Light => "Light",
Self::Dark => "Dark",
Self::CatppuccinLatte => "Catppuccin Latte",
Self::CatppuccinFrappe => "Catppuccin Frappé",
Self::CatppuccinMacchiato => "Catppuccin Macchiato",
Self::CatppuccinMocha => "Catppuccin Mocha",
}
}
@@ -40,6 +70,10 @@ impl ThemeMode {
Self::System => "system",
Self::Light => "light",
Self::Dark => "dark",
Self::CatppuccinLatte => "catppuccinLatte",
Self::CatppuccinFrappe => "catppuccinFrappe",
Self::CatppuccinMacchiato => "catppuccinMacchiato",
Self::CatppuccinMocha => "catppuccinMocha",
}
}
@@ -326,21 +360,126 @@ impl Tokens {
};
pub fn current() -> Tokens {
if is_light() { Self::LIGHT } else { Self::DARK }
Self::for_mode(active_mode())
}
/// The tokens of a resolved mode (`System` reads as Dark: resolve it with `resolve` first).
pub fn for_mode(mode: ThemeMode) -> Tokens {
match mode.flavor() {
Some(f) => catppuccin(f),
None if mode == ThemeMode::Light => Self::LIGHT,
None => Self::DARK,
}
}
}
/// Whether the Light palette is the active one.
/// The palette in use: never `System`.
pub fn active_mode() -> ThemeMode {
ThemeMode::ALL.get(usize::from(ACTIVE_THEME.load(Ordering::Relaxed))).copied().unwrap_or_default()
}
/// Whether the active palette is a light one (Light or Catppuccin Latte).
pub fn is_light() -> bool {
LIGHT_THEME.load(Ordering::Relaxed)
let mode = active_mode();
mode.flavor().map_or(mode == ThemeMode::Light, |f| !f.is_dark())
}
/// Resolve each frame from the integration's current OS appearance. Unknown uses Dark.
pub fn wants_light(ctx: &egui::Context, mode: ThemeMode) -> bool {
pub fn resolve(ctx: &egui::Context, mode: ThemeMode) -> ThemeMode {
match mode {
ThemeMode::Light => true,
ThemeMode::Dark => false,
ThemeMode::System => ctx.system_theme() == Some(egui::Theme::Light),
ThemeMode::System if ctx.system_theme() == Some(egui::Theme::Light) => ThemeMode::Light,
ThemeMode::System => ThemeMode::Dark,
_ => mode,
}
}
/// A Catppuccin flavour on SoundCraft's token set (accent Mauve, selection Blue).
fn catppuccin(f: Flavor) -> Tokens {
let p = f.palette();
let dark = f.is_dark();
let a = |c: Color32, alpha: u8| Color32::from_rgba_unmultiplied(c.r(), c.g(), c.b(), alpha);
let tint = |c: Color32, t: f32| p.base.lerp_to_gamma(c, t);
Tokens {
window_bg: p.base,
panel_bg: p.base,
panel_bg2: p.surface0,
toolbar_bg: p.mantle,
toolbar_group: p.crust,
border: if dark { p.crust } else { p.surface1 },
border_light: p.surface1,
text: p.text,
text_dim: p.subtext0,
text_dark: if dark { p.crust } else { p.text },
header_text: p.subtext1,
counter_bg: p.crust,
counter_text: p.green,
counter_label: p.subtext1,
accent: p.mauve,
accent_dark: tint(p.mauve, 0.6),
mode_on: p.green,
mode_on_text: p.crust,
mode_off_text: p.green,
button: p.surface0,
button_hi: p.surface1,
button_border: p.surface2,
name_field: if dark { p.text } else { p.base },
name_field_sel: p.rosewater,
ruler_bg: p.mantle,
ruler_label_bg: p.mantle,
ruler_text: p.subtext0,
ruler_tick: p.overlay0,
tempo_ruler: tint(p.green, 0.5),
meter_ruler: tint(p.sapphire, 0.5),
marker_ruler: p.surface0,
playlist_bg: p.base,
playlist_alt: p.mantle,
grid_line: p.surface0,
bar_line: p.surface1,
selection: a(p.blue, 110),
playhead: p.red,
insertion: p.text,
solo: p.yellow,
mute: p.peach,
rec: p.red,
input: p.green,
auto_read: p.green,
auto_write: p.red,
auto_touch: p.peach,
auto_latch: p.mauve,
meter_green: p.green,
meter_yellow: p.yellow,
meter_red: p.red,
fader_track: p.crust,
strip_bg: p.mantle,
strip_section: p.base,
slot_bg: p.crust,
automation_line: p.text,
menu_bar: p.mantle,
group_border: if dark { p.crust } else { p.surface1 },
mode_off: p.surface0,
counter_off: p.surface1,
counter_border: p.surface1,
counter_rule: p.surface0,
tool_bracket_off: p.surface1,
row_selected: tint(p.blue, 0.35),
visible_dot: p.overlay1,
big_counter_bg: p.crust,
header_selected: p.surface0,
strip_selected: p.surface0,
marker_text: p.text,
playlist_lane: p.mantle,
universe_bg: p.crust,
universe_view: p.text,
meter_bg: p.crust,
meter_clip_off: tint(p.red, 0.3),
knob_bg: p.surface0,
knob_ring: p.overlay0,
knob_pointer: p.text,
insert_on: tint(p.blue, 0.3),
insert_bypass: tint(p.yellow, 0.3),
send_on: tint(p.green, 0.3),
slot_border: p.surface1,
section_label: p.subtext0,
}
}
@@ -379,11 +518,12 @@ pub fn mono(size: f32) -> FontId {
FontId::new(size, FontFamily::Monospace)
}
/// Make `light` (or dark) the active palette and apply its visuals. Call only when it changes.
pub fn apply(ctx: &egui::Context, light: bool) {
LIGHT_THEME.store(light, Ordering::Relaxed);
/// Make `mode` (already resolved, not `System`) the active palette and apply its visuals. Call only when it changes.
pub fn apply(ctx: &egui::Context, mode: ThemeMode) {
let index = ThemeMode::ALL.iter().position(|m| *m == mode).unwrap_or_default();
ACTIVE_THEME.store(u8::try_from(index).unwrap_or_default(), Ordering::Relaxed);
let t = Tokens::current();
let mut v = if light { egui::Visuals::light() } else { egui::Visuals::dark() };
let mut v = if is_light() { egui::Visuals::light() } else { egui::Visuals::dark() };
v.panel_fill = t.panel_bg;
v.window_fill = t.panel_bg2;
v.extreme_bg_color = t.slot_bg;
@@ -436,6 +576,18 @@ mod tests {
assert_eq!(ThemeMode::parse("sepia"), None);
}
#[test]
fn catppuccin_themes_use_the_flavour_palette() {
for mode in ThemeMode::ALL {
let Some(f) = mode.flavor() else { continue };
let (t, p) = (Tokens::for_mode(mode), f.palette());
assert_eq!((t.window_bg, t.panel_bg, t.text, t.accent), (p.base, p.base, p.text, p.mauve), "{}", mode.id());
}
assert_eq!(ThemeMode::ALL.iter().filter(|m| m.flavor().is_some()).count(), Flavor::ALL.len());
assert_eq!(ThemeMode::parse("catppuccinMocha"), Some(ThemeMode::CatppuccinMocha));
assert_eq!(ThemeMode::id_list().split('|').count(), ThemeMode::ALL.len());
}
#[test]
fn theme_command_sets_and_reports_the_mode() {
let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default());
+4 -4
View File
@@ -405,7 +405,7 @@ mod selector_visual_regressions {
move |ui, regions: &mut serde_json::Map<String, serde_json::Value>| {
if !ready {
crate::fonts::install(ui.ctx());
crate::theme::apply(ui.ctx(), false);
crate::theme::apply(ui.ctx(), crate::theme::ThemeMode::Dark);
ready = true;
return;
}
@@ -458,7 +458,7 @@ mod selector_visual_regressions {
for scale in [1.0, 1.5, 2.0] {
let ctx = egui::Context::default();
crate::fonts::install(&ctx);
crate::theme::apply(&ctx, false);
crate::theme::apply(&ctx, crate::theme::ThemeMode::Dark);
let mut app = mixer_app(narrow, mode);
let mut output = ctx.run_ui(scaled_input(vec2(320.0, 640.0), scale), |ui| crate::mix_window::show(&mut app, ui));
output.textures_delta.clear();
@@ -505,7 +505,7 @@ mod selector_visual_regressions {
move |ui, app: &mut crate::SoundApp| {
if !ready {
crate::fonts::install(ui.ctx());
crate::theme::apply(ui.ctx(), false);
crate::theme::apply(ui.ctx(), crate::theme::ThemeMode::Dark);
ready = true;
return;
}
@@ -553,7 +553,7 @@ mod selector_visual_regressions {
for scale in [1.0, 1.5, 2.0] {
let ctx = egui::Context::default();
crate::fonts::install(&ctx);
crate::theme::apply(&ctx, false);
crate::theme::apply(&ctx, crate::theme::ThemeMode::Dark);
let mut rect = Rect::NOTHING;
let mut output = ctx.run_ui(scaled_input(vec2(320.0, 120.0), scale), |ui| {
rect = selector_box(ui, width, 16.0, label, Tokens::current().text).rect;
+1 -1
View File
@@ -180,7 +180,7 @@ fn shared_controls_visuals() {
}
let mut controls = Harness::builder().with_size(vec2(340.0, 150.0)).with_pixels_per_point(scale).wgpu().build_ui_state(
|ui, rects: &mut Vec<egui::Rect>| {
theme::apply(ui.ctx(), false);
theme::apply(ui.ctx(), theme::ThemeMode::Dark);
rects.clear();
for enabled in [true, false] {
ui.add_enabled_ui(enabled, |ui| {
+2 -2
View File
@@ -43,7 +43,7 @@ fn shared_panels_and_controls_follow_manual_and_live_system_appearance() {
}
}
let ctx = egui::Context::default();
theme::apply(&ctx, light);
theme::apply(&ctx, if light { theme::ThemeMode::Light } else { theme::ThemeMode::Dark });
let tokens = theme::Tokens::current();
let mut output = ctx.run_ui(egui::RawInput::default(), |ui| {
widgets::icon_button(ui, vec2(38.0, 28.0), "loop", true, "Loop playback");
@@ -58,7 +58,7 @@ fn shared_panels_and_controls_follow_manual_and_live_system_appearance() {
if let Some(directory) = std::env::var_os("CRAFT_UI_THEME_SHOTS").map(std::path::PathBuf::from) {
let mut controls = Harness::builder().with_size(vec2(340.0, 150.0)).with_pixels_per_point(scale).wgpu().build_ui_state(
move |ui, _: &mut ()| {
theme::apply(ui.ctx(), light);
theme::apply(ui.ctx(), if light { theme::ThemeMode::Light } else { theme::ThemeMode::Dark });
for enabled in [true, false] {
ui.add_enabled_ui(enabled, |ui| {
ui.horizontal(|ui| {
-42
View File
@@ -1,42 +0,0 @@
ArtCraft brand assets: license
The ArtCraft name, wordmark and mark (the "ArtCraft Marks"), including every file in this
directory in every color, size and format, are owned by the ArtCraft Team. They are trademarks
and are NOT open source. They are not covered by SoundCraft's MIT OR Apache-2.0 license, and nothing in
that license, in NOTICE or in any other file of this repository grants any right to them.
Permission
The ArtCraft Team grants a limited, non-exclusive, non-transferable, revocable, royalty-free
permission to use the ArtCraft Marks only:
1. as part of copies of this repository; and
2. within SoundCraft itself, its documentation and its official builds, to identify SoundCraft as made
by the ArtCraft team and community and to link to getartcraft.com and its community.
Conditions
The permission holds only while every one of these is true:
- The marks are used exactly as supplied in this directory. Do not redraw, recolor, crop,
stretch, rotate, animate, outline or add effects to them, extract parts of them, or combine
them with any other name, mark or artwork.
- They are used only in the context of SoundCraft, and only to say that the ArtCraft team makes it.
They must never suggest that the ArtCraft Team made, sponsors, endorses, certifies or supports
any other product, service, fork, modified version, plug-in, organization or person.
- Neither the marks nor the ArtCraft name appear in the name, logo, icon, domain, social media
handle, app-store listing, advertising or marketing of anything other than SoundCraft.
Forks and modified versions
If you distribute or publish a modified version of SoundCraft, or any work derived from it, you must
remove the ArtCraft Marks from it (or replace them with your own) and must not call it ArtCraft or
present it as an ArtCraft product. You may say, in plain text, that your work is based on SoundCraft by
the ArtCraft team. A fork kept only to propose changes back to this repository may keep the marks
unchanged.
Everything else
Any use not expressly permitted here needs prior written permission from the ArtCraft Team
(https://getartcraft.com/). The permission ends automatically for anyone who breaks these terms.
All other rights are reserved.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -19.42 136.34 136.34" role="img" aria-label="ArtCraft">
<title>ArtCraft</title>
<path fill="#0b0b0c" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -19.42 136.34 136.34" role="img" aria-label="ArtCraft">
<title>ArtCraft</title>
<path fill="#4e7bfb" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 341 B

@@ -26,10 +26,10 @@
<launchable type="desktop-id">ai.storyteller.soundcraft.desktop</launchable>
<icon type="stock">ai.storyteller.soundcraft</icon>
<!-- Only url types known to appstream 0.15 (Ubuntu 22.04, where CI validates): no vcs-browser. -->
<url type="homepage">https://getartcraft.com/apps/soundcraft</url>
<url type="homepage">https://github.com/storytold/soundcraft</url>
<url type="bugtracker">https://github.com/storytold/soundcraft/issues</url>
<url type="help">https://github.com/storytold/soundcraft</url>
<url type="contact">https://discord.gg/artcraft</url>
<url type="contact">https://github.com/storytold/soundcraft/issues</url>
<categories>
<category>AudioVideo</category>
<category>Audio</category>
+1 -1
View File
@@ -14,7 +14,7 @@ section: sound
priority: optional
maintainer: ${SOUNDCRAFT_MAINTAINER}
vendor: Learning Machines LLC
homepage: https://getartcraft.com/apps/soundcraft
homepage: https://github.com/storytold/soundcraft
license: MIT OR Apache-2.0
description: |-
Open-source, native digital audio workstation. Record, edit and mix
+3 -3
View File
@@ -26,9 +26,9 @@
<Icon Id="SoundcraftIcon.ico" SourceFile="$(var.IconPath)" />
<Property Id="ARPPRODUCTICON" Value="SoundcraftIcon.ico" />
<Property Id="ARPURLINFOABOUT" Value="https://getartcraft.com/apps/soundcraft" />
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/soundcraft" />
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/storytold/soundcraft" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/storytold/soundcraft" />
<Property Id="ARPHELPLINK" Value="https://github.com/storytold/soundcraft/issues" />
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="INSTALLFOLDER" Name="SoundCraft" />
Submodule tools/craft-skills added at 26c1c201a7