Compare commits
151 Commits
main
...
5385bf675a
| Author | SHA1 | Date | |
|---|---|---|---|
| 5385bf675a | |||
| 211e71f5a9 | |||
| 23c7abb145 | |||
| 670ae0b6b6 | |||
| 10ca567ac5 | |||
| b2871ac251 | |||
| 8ba89f91a0 | |||
| 7d670dacb9 | |||
| 1de8c068f6 | |||
| d792f011ee | |||
| 897f1a776e | |||
| 869d3af244 | |||
| a5f17687f1 | |||
| 5b851751e5 | |||
| bc5d12e53a | |||
| d6bbae173b | |||
| 1f339f1503 | |||
| 8ffe2c22c7 | |||
| 20c32ce0d8 | |||
| a326d58d30 | |||
| c72733bac8 | |||
| 5758b18d58 | |||
| 52cc890a67 | |||
| 0f9d750069 | |||
| 66ee2e28ff | |||
| 6ec3a86568 | |||
| 51f52be4ce | |||
| 2c98a915fa | |||
| e42476dd4d | |||
| 3e364a6622 | |||
| 1248f74b25 | |||
| fc2ab0757b | |||
| 10ed5a629a | |||
| 88c2b51720 | |||
| 5cda1a8f95 | |||
| 200832f230 | |||
| 91bc9011b2 | |||
| de56598fca | |||
| abafea8ddf | |||
| e6f776bdf4 | |||
| d40d713649 | |||
| 767575b25d | |||
| 82b0668bcf | |||
| 6cf9d2eec1 | |||
| 2097997372 | |||
| 5579708f69 | |||
| 1b01491e87 | |||
| 5581ba1881 | |||
| 8983b3f21c | |||
| 4a7ae83019 | |||
| 61a6d7aad0 | |||
| 1b01e3b805 | |||
| 2a57cc415b | |||
| 7c76bdb8d6 | |||
| 1facc72a67 | |||
| 726ea16e92 | |||
| 154cac6547 | |||
| 3380e454df | |||
| 660f48216a | |||
| fb1f73ebd6 | |||
| cd223592a7 | |||
| af81c94207 | |||
| b53e4a76ab | |||
| 8c31ed4196 | |||
| 8024c18bb0 | |||
| 194030d953 | |||
| e4799c1f42 | |||
| 636129688d | |||
| a2ee0e5a50 | |||
| 96ed74c6fe | |||
| a67d982fcd | |||
| c9ab7a4f0b | |||
| 772d21a8ed | |||
| 4396147a8b | |||
| c396c39b6b | |||
| f6b43cb021 | |||
| 60d1d7ca74 | |||
| 9864cc6d61 | |||
| 985ab687d7 | |||
| 9b925d881e | |||
| 71146c7cea | |||
| 6b95f31afd | |||
| adee8d0d57 | |||
| f9c284effd | |||
| 57fd51be3e | |||
| ce70251057 | |||
| b47c789612 | |||
| dd853b8e1b | |||
| a0585b0814 | |||
| 2100b82dad | |||
| 15a4300db5 | |||
| fed39c01e8 | |||
| 0a4f1419eb | |||
| 793c83e18c | |||
| 20bc0ffcb4 | |||
| 8e09fd106e | |||
| 73ca0ff096 | |||
| 425f1c8627 | |||
| 730332cfb0 | |||
| 1d70a83759 | |||
| 0299012725 | |||
| 08029ec604 | |||
| 4f9b1f39f9 | |||
| 4772b02f77 | |||
| 4049c7787c | |||
| 4c635500dd | |||
| d0e37e13e6 | |||
| 7658cf9d51 | |||
| 584dbb6aad | |||
| 2731eea037 | |||
| 22ee5f97e6 | |||
| 5fb059ea20 | |||
| 705d93702b | |||
| 77a6aa9eb7 | |||
| d25b1317fc | |||
| 2851785e0d | |||
| a72772c8cc | |||
| 4d22bd5d2b | |||
| 495bfb3bdc | |||
| 73db616139 | |||
| 8efafffaff | |||
| 48f5920fed | |||
| d106711708 | |||
| 2be15d11f4 | |||
| 5952807240 | |||
| 0beed16c31 | |||
| c6860105a6 | |||
| f4eafdf5b2 | |||
| 935df84920 | |||
| a3a39ea28e | |||
| 574625735b | |||
| 40c509e295 | |||
| 61daa9d79d | |||
| 9e597258e4 | |||
| 223679acf8 | |||
| 2235a4b0a1 | |||
| 2453b78237 | |||
| fcb6adb6af | |||
| ce98acacd0 | |||
| d2d6ef5b06 | |||
| 6efcabd32d | |||
| 250e359fc5 | |||
| cf5994e604 | |||
| e1aff189cd | |||
| b3c56bc56c | |||
| 3bb19cbda8 | |||
| 42ad77d9ae | |||
| e853e67492 | |||
| f7e6f96cbf | |||
| 8af64fc4e2 | |||
| 183dd5b516 |
@@ -1,5 +0,0 @@
|
||||
[env]
|
||||
MACOSX_DEPLOYMENT_TARGET = "12.0"
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --release --"
|
||||
@@ -1,39 +0,0 @@
|
||||
name: Deploy Website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'website/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: website
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
working-directory: website
|
||||
run: pnpm build
|
||||
|
||||
- name: Deploy to host volume
|
||||
run: |
|
||||
rm -rf /home/debian/my-services/data/cagire-website/*
|
||||
cp -r website/dist/* /home/debian/my-services/data/cagire-website/
|
||||
|
||||
240
.github/workflows/ci.yml
vendored
Normal file
240
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact: cagire-linux-x86_64
|
||||
- os: macos-15-intel
|
||||
target: x86_64-apple-darwin
|
||||
artifact: cagire-macos-x86_64
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
artifact: cagire-macos-aarch64
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact: cagire-windows-x86_64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake pkg-config libasound2-dev libclang-dev libjack-dev \
|
||||
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgl1-mesa-dev
|
||||
cargo install cargo-bundle
|
||||
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew list cmake &>/dev/null || brew install cmake
|
||||
cargo install cargo-bundle
|
||||
|
||||
- name: Install dependencies (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System'
|
||||
echo "C:\Program Files\CMake\bin" >> $env:GITHUB_PATH
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Build desktop
|
||||
run: cargo build --release --features desktop --bin cagire-desktop --target ${{ matrix.target }}
|
||||
|
||||
- name: Bundle desktop app
|
||||
if: runner.os != 'Windows'
|
||||
run: cargo bundle --release --features desktop --bin cagire-desktop --target ${{ matrix.target }}
|
||||
|
||||
- name: Zip macOS app bundle
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release/bundle/osx
|
||||
zip -r Cagire.app.zip Cagire.app
|
||||
|
||||
- name: Upload artifact (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: target/${{ matrix.target }}/release/cagire
|
||||
|
||||
- name: Upload artifact (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: target/${{ matrix.target }}/release/cagire.exe
|
||||
|
||||
- name: Upload desktop artifact (Linux deb)
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
- name: Upload desktop artifact (macOS app bundle)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/bundle/osx/Cagire.app.zip
|
||||
|
||||
- name: Upload desktop artifact (Windows exe)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/cagire-desktop.exe
|
||||
|
||||
universal-macos:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Download macOS artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: cagire-macos-*
|
||||
path: artifacts
|
||||
|
||||
- name: Create universal CLI binary
|
||||
run: |
|
||||
lipo -create \
|
||||
artifacts/cagire-macos-x86_64/cagire \
|
||||
artifacts/cagire-macos-aarch64/cagire \
|
||||
-output cagire
|
||||
chmod +x cagire
|
||||
lipo -info cagire
|
||||
|
||||
- name: Create universal app bundle
|
||||
run: |
|
||||
cd artifacts/cagire-macos-aarch64-desktop
|
||||
unzip Cagire.app.zip
|
||||
cd ../cagire-macos-x86_64-desktop
|
||||
unzip Cagire.app.zip
|
||||
cd ../..
|
||||
cp -R artifacts/cagire-macos-aarch64-desktop/Cagire.app Cagire.app
|
||||
lipo -create \
|
||||
artifacts/cagire-macos-x86_64-desktop/Cagire.app/Contents/MacOS/cagire-desktop \
|
||||
artifacts/cagire-macos-aarch64-desktop/Cagire.app/Contents/MacOS/cagire-desktop \
|
||||
-output Cagire.app/Contents/MacOS/cagire-desktop
|
||||
lipo -info Cagire.app/Contents/MacOS/cagire-desktop
|
||||
zip -r Cagire.app.zip Cagire.app
|
||||
|
||||
- name: Build .pkg installer
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
mkdir -p pkg-root/Applications pkg-root/usr/local/bin
|
||||
cp -R Cagire.app pkg-root/Applications/
|
||||
cp cagire pkg-root/usr/local/bin/
|
||||
pkgbuild --analyze --root pkg-root component.plist
|
||||
plutil -replace BundleIsRelocatable -bool NO component.plist
|
||||
pkgbuild --root pkg-root --identifier com.sova.cagire \
|
||||
--version "$VERSION" --install-location / \
|
||||
--component-plist component.plist \
|
||||
"Cagire-${VERSION}-universal.pkg"
|
||||
|
||||
- name: Upload universal CLI
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cagire-macos-universal
|
||||
path: cagire
|
||||
|
||||
- name: Upload universal app bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cagire-macos-universal-desktop
|
||||
path: Cagire.app.zip
|
||||
|
||||
- name: Upload .pkg installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cagire-macos-universal-pkg
|
||||
path: Cagire-*-universal.pkg
|
||||
|
||||
release:
|
||||
needs: [build, universal-macos]
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Prepare release files
|
||||
run: |
|
||||
mkdir -p release
|
||||
for dir in artifacts/*/; do
|
||||
name=$(basename "$dir")
|
||||
if [[ "$name" == "cagire-macos-universal-pkg" ]]; then
|
||||
cp "$dir"/*.pkg release/
|
||||
elif [[ "$name" == "cagire-macos-universal-desktop" ]]; then
|
||||
cp "$dir/Cagire.app.zip" "release/cagire-macos-universal-desktop.app.zip"
|
||||
elif [[ "$name" == "cagire-macos-universal" ]]; then
|
||||
cp "$dir/cagire" "release/cagire-macos-universal"
|
||||
elif [[ "$name" == *-desktop ]]; then
|
||||
base="${name%-desktop}"
|
||||
if ls "$dir"/*.deb 1>/dev/null 2>&1; then
|
||||
cp "$dir"/*.deb "release/${base}-desktop.deb"
|
||||
elif [ -f "$dir/Cagire.app.zip" ]; then
|
||||
cp "$dir/Cagire.app.zip" "release/${base}-desktop.app.zip"
|
||||
elif [ -f "$dir/cagire-desktop.exe" ]; then
|
||||
cp "$dir/cagire-desktop.exe" "release/${base}-desktop.exe"
|
||||
fi
|
||||
else
|
||||
if [ -f "$dir/cagire.exe" ]; then
|
||||
cp "$dir/cagire.exe" "release/${name}.exe"
|
||||
elif [ -f "$dir/cagire" ]; then
|
||||
cp "$dir/cagire" "release/${name}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: release/*
|
||||
generate_release_notes: true
|
||||
58
.github/workflows/pages.yml
vendored
Normal file
58
.github/workflows/pages.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Deploy Website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: website/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
working-directory: website
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
working-directory: website
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: website/dist
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,11 +1,7 @@
|
||||
/target
|
||||
/.cache
|
||||
Cargo.lock
|
||||
*.prof
|
||||
.DS_Store
|
||||
releases/
|
||||
|
||||
# Local cargo overrides (doux path patch)
|
||||
.cargo/config.local.toml
|
||||
|
||||
# Claude
|
||||
.claude/
|
||||
|
||||
172
BUILDING.md
172
BUILDING.md
@@ -1,172 +0,0 @@
|
||||
# Building Cagire
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://git.raphaelforment.fr/BuboBubo/cagire
|
||||
cd cagire
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The `doux` audio engine is fetched automatically from git. No local path setup needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Rust** (stable toolchain): https://rustup.rs
|
||||
|
||||
## System Dependencies
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
cmake is required by `rusty_link` (Ableton Link C++ bindings). Xcode Command Line Tools provide the C++ compiler. CoreAudio and CoreMIDI are built-in. The desktop build needs no additional dependencies on macOS (Cocoa/Metal are provided by the system).
|
||||
|
||||
### Linux (Debian/Ubuntu)
|
||||
|
||||
```bash
|
||||
sudo apt install cmake g++ pkg-config libasound2-dev libjack-jackd2-dev
|
||||
```
|
||||
|
||||
For the desktop build (egui/eframe), also install:
|
||||
|
||||
```bash
|
||||
sudo apt install libgl-dev libxkbcommon-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev
|
||||
```
|
||||
|
||||
### Linux (Arch)
|
||||
|
||||
```bash
|
||||
sudo pacman -S cmake gcc pkgconf alsa-lib jack2
|
||||
```
|
||||
|
||||
For the desktop build:
|
||||
|
||||
```bash
|
||||
sudo pacman -S libxkbcommon libx11 libxcursor libxrandr libxi wayland mesa
|
||||
```
|
||||
|
||||
### Linux (Fedora)
|
||||
|
||||
```bash
|
||||
sudo dnf install cmake gcc-c++ pkgconf-pkg-config alsa-lib-devel jack-audio-connection-kit-devel
|
||||
```
|
||||
|
||||
For the desktop build:
|
||||
|
||||
```bash
|
||||
sudo dnf install libxkbcommon-devel libX11-devel libXcursor-devel libXrandr-devel libXi-devel wayland-devel mesa-libGL-devel
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Install Visual Studio Build Tools (MSVC) and CMake. Everything else is provided by the Windows SDK.
|
||||
|
||||
## Build
|
||||
|
||||
Terminal (default):
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Desktop (egui window):
|
||||
|
||||
```bash
|
||||
cargo build --release --features desktop --bin cagire-desktop
|
||||
```
|
||||
|
||||
Plugins (CLAP/VST3):
|
||||
|
||||
```bash
|
||||
cargo xtask bundle cagire-plugins --release
|
||||
```
|
||||
|
||||
The xtask alias is defined in `.cargo/config.toml` (committed). Plugin bundles are output to `target/bundled/`.
|
||||
|
||||
## Run
|
||||
|
||||
Terminal (default):
|
||||
|
||||
```bash
|
||||
cargo run --release -- [OPTIONS]
|
||||
```
|
||||
|
||||
Desktop (egui window):
|
||||
|
||||
```bash
|
||||
cargo run --release --features desktop --bin cagire-desktop
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-s, --samples <path>` | Sample directory (repeatable) |
|
||||
| `-o, --output <device>` | Output audio device |
|
||||
| `-i, --input <device>` | Input audio device |
|
||||
| `-c, --channels <n>` | Output channel count |
|
||||
| `-b, --buffer <size>` | Audio buffer size |
|
||||
|
||||
## Cross-Compilation
|
||||
|
||||
### Targets
|
||||
|
||||
| Target | Method | Binaries |
|
||||
|--------|--------|----------|
|
||||
| aarch64-apple-darwin | Native (macOS ARM only) | `cagire`, `cagire-desktop` |
|
||||
| x86_64-apple-darwin | Native (macOS only) | `cagire`, `cagire-desktop` |
|
||||
| x86_64-unknown-linux-gnu | `cross build` (Docker) | `cagire`, `cagire-desktop` |
|
||||
| aarch64-unknown-linux-gnu (RPi 64-bit) | `cross build` (Docker) | `cagire`, `cagire-desktop` |
|
||||
| x86_64-pc-windows-msvc | `cargo xwin build` (native) | `cagire`, `cagire-desktop` |
|
||||
|
||||
macOS targets can only be built on macOS. Linux targets are cross-compiled via Docker (`cross`). Windows targets are cross-compiled natively via `cargo-xwin` (downloads Windows SDK + MSVC CRT headers, no Docker needed).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Docker** + **cross** (Linux targets only): `cargo install cross --git https://github.com/cross-rs/cross`
|
||||
2. **cargo-xwin** (Windows target): `cargo install cargo-xwin` and `rustup target add x86_64-pc-windows-msvc`
|
||||
3. On macOS, add the Intel target: `rustup target add x86_64-apple-darwin`
|
||||
|
||||
### Building Individual Targets
|
||||
|
||||
```bash
|
||||
# Linux x86_64 (Docker)
|
||||
cross build --release --target x86_64-unknown-linux-gnu
|
||||
cross build --release --features desktop --bin cagire-desktop --target x86_64-unknown-linux-gnu
|
||||
|
||||
# Linux aarch64 (Docker)
|
||||
cross build --release --target aarch64-unknown-linux-gnu
|
||||
cross build --release --features desktop --bin cagire-desktop --target aarch64-unknown-linux-gnu
|
||||
|
||||
# Windows x86_64 (native, no Docker)
|
||||
cargo xwin build --release --target x86_64-pc-windows-msvc
|
||||
cargo xwin build --release --features desktop --bin cagire-desktop --target x86_64-pc-windows-msvc
|
||||
```
|
||||
|
||||
### Building All Targets
|
||||
|
||||
```bash
|
||||
# Interactive (prompts for platform/target selection):
|
||||
uv run scripts/build.py
|
||||
|
||||
# Non-interactive:
|
||||
uv run scripts/build.py --platforms macos-arm64,linux-x86_64 --targets cli,desktop
|
||||
uv run scripts/build.py --all
|
||||
```
|
||||
|
||||
Builds selected targets, producing binaries in `releases/`.
|
||||
|
||||
Platform aliases: `macos-arm64`, `macos-x86_64`, `linux-x86_64`, `linux-aarch64`, `windows-x86_64`.
|
||||
Target aliases: `cli`, `desktop`, `plugins`.
|
||||
|
||||
### Linux AppImage Packaging
|
||||
|
||||
Linux releases ship as AppImages — self-contained executables that bundle all shared library dependencies (ALSA, JACK, X11, OpenGL). No runtime dependencies required. `build.py` handles AppImage creation automatically for Linux targets.
|
||||
|
||||
### Notes
|
||||
|
||||
- Custom Dockerfiles in `scripts/cross/` install the native libraries for Linux cross-compilation (ALSA, JACK, X11, cmake, libclang, etc.). `Cross.toml` maps each Linux target to its Dockerfile.
|
||||
- The first Linux cross-build per target downloads Docker base images and installs packages. Subsequent builds use cached layers.
|
||||
- Cross-architecture Docker builds (e.g. aarch64 on x86_64) run under QEMU emulation and are significantly slower.
|
||||
- Windows cross-compilation via `cargo-xwin` runs natively on the host (no Docker) and uses real Windows SDK headers, ensuring correct ABI and struct layouts.
|
||||
232
CHANGELOG.md
232
CHANGELOG.md
@@ -2,238 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.1.5]
|
||||
|
||||
### Forth Language
|
||||
- **`at` reworked as a looping block**: `at` now captures all stack values as deltas, then re-executes its body once per delta. Closed by `.` (audio emit), `m.` (MIDI emit), or `done` (no emit). Each iteration gets independent nondeterministic rolls (e.g., `0 0.5 at kick snd 1 2 rand freq .` re-evaluates `kick snd 1 2 rand freq` at delta 0 and 0.5).
|
||||
- Removed `ArpList` type and `arp` word — arpeggio spreading is now handled by at-loops directly.
|
||||
|
||||
### Added
|
||||
|
||||
- Support i32/i16 sample formats at cpal boundary for ASIO compatibility
|
||||
|
||||
### Fixed
|
||||
- Resolved value annotations deduplicated: nondeterministic ops inside at-loops now show only the last resolved value per span, instead of one annotation per iteration.
|
||||
- Audio input device name matching.
|
||||
|
||||
## [0.1.4]
|
||||
|
||||
### Breaking
|
||||
- **Doux v0.0.12**: removed Mutable Instruments Plaits modes (`modal`, `va`, `analog`, `waveshape`, `grain`, `chord`, `swarm`, `pnoise`, etc.). Native percussion models retained; new models added: `tom`, `cowbell`, `cymbal`.
|
||||
- Simplified effects/filter API: removed per-filter envelope parameters in favor of the universal `env` word.
|
||||
- Recording commands simplified: removed `/sound/` path segment from `rec`, `overdub`, `orec`, `odub`.
|
||||
|
||||
### Forth Language
|
||||
- New modulation transition words: `islide` (swell), `oslide` (pluck), `pslide` (stair/stepped).
|
||||
- New `lpg` word (Low Pass Gate): pairs amplitude envelope with lowpass filter modulation.
|
||||
- New `inchan` word: select audio input channel by index.
|
||||
- New EQ frequency words: `eqlofreq`, `eqmidfreq`, `eqhifreq`.
|
||||
|
||||
### UI / UX
|
||||
- Redesigned top bar: consolidated transport, tempo, bar:beat display with visual beat segments.
|
||||
- CPU meter with color-coded fill bar (green/yellow/red).
|
||||
|
||||
### Engine
|
||||
- Audio input channel selection support.
|
||||
- Audio buffer sizing improved for multi-channel input.
|
||||
- MIDI output sends directly from dispatcher thread, bypassing UI-thread polling (~30x less jitter).
|
||||
|
||||
### Packaging
|
||||
- CI migrated from GitHub Actions to Gitea Actions.
|
||||
- Removed WIX installer; Windows now distributed via zip and NSIS only.
|
||||
- Gitea Actions workflow for automatic website deployment.
|
||||
- Added LICENSE file.
|
||||
|
||||
### Documentation
|
||||
- Extensive documentation updates reflecting doux v0.0.12 API changes across sources, filters, modulation, wavetable, and audio modulation docs.
|
||||
|
||||
## [0.1.3]
|
||||
|
||||
### Forth Language
|
||||
- New `stretch` word: pitch-independent time stretching via phase vocoder (e.g., `kick sound 2 stretch .` plays at half speed, same pitch).
|
||||
- Automatic default release time on sounds when none is explicitly set.
|
||||
|
||||
### Engine
|
||||
- Sample-accurate timing: delta computation switched from float seconds to integer sample ticks, fixing precision issues.
|
||||
- Lock-free audio input buffer: replaced `Arc<Mutex<VecDeque>>` with `HeapRb` ring buffer.
|
||||
- Theme access optimized: `Rc<ThemeColors>` replaces deep cloning on every `get()`.
|
||||
- Dictionary keys cached in `App` to avoid repeated lock acquisitions during rendering.
|
||||
|
||||
### Fixed
|
||||
- Realtime priority diagnostics: dedicated `warn_no_rt()` on Linux, lookahead widened from 20ms to 40ms when RT priority unavailable.
|
||||
- Float epsilon precision in delta/nudge zero-comparisons.
|
||||
- Windows build fixes for standalone and plugin targets.
|
||||
|
||||
### Documentation
|
||||
- Time stretching usage guide added to `docs/engine/samples.md`.
|
||||
|
||||
## [0.1.2]
|
||||
|
||||
### Forth Language
|
||||
- Single-letter envelope aliases: `a` (attack), `d` (decay), `s` (sustain), `r` (release).
|
||||
- `sound` alias changed from `s` to `snd` (frees `s` for sustain).
|
||||
- New `partials` word: set number of active harmonics for additive oscillator.
|
||||
- Velocity parameter normalized to 0–1 float range (was 0–127 integer).
|
||||
|
||||
### UI / UX
|
||||
- **Sample Explorer as dedicated page**: the side panel is now a full page (Tab key), with keyboard navigation (j/k, search with `/`, preview with Enter), replacing the old collapsible side panel.
|
||||
- **Pulsing armed-changes bar** on patterns page: staged play/stop/mute/solo changes shown in a launch bar with animated feedback ("c to launch").
|
||||
- Pulsing highlight on banks and patterns with staged changes.
|
||||
- Sample browser shows child count on collapsed folders and uses `+`/`-` tree icons.
|
||||
- File browser modal: shows audio file counts per directory, colored path segments, and hint bar.
|
||||
- Audio devices refreshed automatically when entering the Engine page.
|
||||
- Bank prelude field added to data model (foundation for bank-level Forth scripts).
|
||||
|
||||
### Engine
|
||||
- Audio timing switched from float seconds to integer tick-based scheduling, improving timing precision.
|
||||
- Stream error handling refined: only `DeviceNotAvailable` and `StreamInvalidated` trigger device-lost recovery (non-fatal errors no longer restart the stream).
|
||||
- Step traces use `Arc` for cheaper cloning between threads.
|
||||
|
||||
### Packaging
|
||||
- **Windows: NSIS installer** replaces cargo-wix MSI. Includes optional PATH registration, Start Menu shortcut, and proper Add/Remove Programs entry with uninstaller.
|
||||
- Improved Windows cross-compilation from Unix hosts (MinGW toolchain detection).
|
||||
- CI build timeouts increased to 60 minutes across all platforms.
|
||||
- Website download matrix updated.
|
||||
|
||||
## [0.1.1]
|
||||
|
||||
### Forth Language
|
||||
- `map` word: apply a quotation to each stack element (`1 2 3 ( 10 * ) map => 10 20 30`).
|
||||
- `loop` fix: now operates in steps instead of beats, uses `step_duration()` for correct timing.
|
||||
|
||||
### Fixed
|
||||
- Crash on missing sample directories: sample path scanning now validates directories exist before scanning.
|
||||
- Audio channel minimum enforced to 2, preventing crash on devices reporting fewer channels.
|
||||
- Audio device disconnect: automatic stream restart when device is lost (terminal and desktop).
|
||||
- Live keys (e.g. `f` for fill) no longer trigger while searching in dictionary or help views.
|
||||
- Side panel always uses horizontal layout (removed broken vertical fallback for narrow terminals).
|
||||
|
||||
### Changed
|
||||
- Runtime highlight enabled by default.
|
||||
|
||||
### Packaging
|
||||
- Modular CI: split monolithic release workflow into per-platform builds (Linux, macOS, Windows, cross-compilation).
|
||||
- Separate CI workflows for CLAP/VST plugin builds (Linux, macOS, Windows, Raspberry Pi).
|
||||
- Windows MSI installer workflow fixes.
|
||||
- Website download matrix updated.
|
||||
|
||||
## [0.1.0]
|
||||
|
||||
### Breaking
|
||||
- **Quotation syntax changed from `{ }` to `( )`** — all deferred code blocks now use parentheses.
|
||||
|
||||
### Forth Language
|
||||
|
||||
**Syntax:**
|
||||
- `[ v1 v2 v3 ]` bracket lists with implicit count.
|
||||
- `( ... )` quotation syntax (replaces `{ }`).
|
||||
- `,varname` assignment syntax (SetKeep): assign without consuming.
|
||||
- `case/of/endof/endcase` control flow.
|
||||
- `print` — debug word, outputs top-of-stack as text.
|
||||
- Arithmetic and unary ops now lift over ArpList and CycleList element-wise.
|
||||
|
||||
**New words:**
|
||||
- `index` — select item at explicit index (wraps with modulo).
|
||||
- `slice` / `pick` — sample slicing: divide a sample into N equal parts and select which slice to play.
|
||||
- `wave` / `waveform` — set drum synthesis waveform (0=sine, 0.5=triangle, 1=saw).
|
||||
- `pbounce` — ping-pong cycle keyed by pattern iteration.
|
||||
- `except` — inverse of `every`.
|
||||
- `every+` / `except+` — phase-offset variants.
|
||||
- `bjork` / `pbjork` — euclidean rhythm gates using quotations.
|
||||
- `arp` — arpeggio list type (spreads notes across time).
|
||||
- `all` / `noall` — apply params globally to all emitted sounds.
|
||||
- `linmap` / `expmap` — linear and exponential range mapping.
|
||||
- `rec` / `overdub` (`dub`) — record/overdub master audio to a named sample.
|
||||
- `orec` / `odub` — record/overdub a single orbit.
|
||||
|
||||
**Harmony and voicing:**
|
||||
- `key!` — set tonal center.
|
||||
- `triad` / `seventh` — diatonic chord from scale degree.
|
||||
- `inv` / `dinv` — chord inversion / down inversion.
|
||||
- `drop2` / `drop3` — drop voicings.
|
||||
- `tp` — transpose all ints on stack by N semitones.
|
||||
|
||||
**New chord types:**
|
||||
- `pwr`, `augmaj7`, `7sus4`, `9sus4`, `maj69`, `min69`, `maj11`, `maj13`, `min13`, `dom7s11`.
|
||||
|
||||
**Effect parameters:**
|
||||
- Ducking compressor: `comp`, `compattack`/`cattack`, `comprelease`/`crelease`, `comporbit`/`corbit`.
|
||||
- Smear effect: `smear`, `smearfreq`, `smearfb`.
|
||||
- Reverb: `verbtype`, `verbchorus`, `verbchorusfreq`, `verbprelow`, `verbprehigh`, `verblowcut`, `verbhighcut`, `verblowgain`.
|
||||
|
||||
**Behavior changes:**
|
||||
- All parameter words now accept varargs (100+ words updated to consume the full stack).
|
||||
- `every` reworked to accept quotations.
|
||||
- Removed `chain` word (replaced by pattern-level Follow Up setting).
|
||||
|
||||
### Engine
|
||||
- SF2 soundfont support: auto-scans sample directories for `.sf2` files.
|
||||
- Follow-up actions: patterns have configurable follow-up (Loop, Stop, Chain). Replaces the `chain` word with a declarative UI setting (`e` key).
|
||||
- Delta-time MIDI scheduling for tighter timing.
|
||||
- Audio stream errors surfaced as flash messages.
|
||||
- Prelude script evaluated on application startup (not only on play).
|
||||
- Global periodic script: a hidden script page runs alongside all patterns at its own speed/length.
|
||||
- RestartAll command: reset all active patterns to step 0 and clear state.
|
||||
- Tempo and current beat exposed in sequencer snapshot.
|
||||
- Spectrum analyzer rescaling.
|
||||
|
||||
### UI / UX
|
||||
- **Engine page redesign**: responsive narrow/wide layout, Link/MIDI/device settings moved here from Options.
|
||||
- **Patterns view redesign**: banks column with pattern counts, expandable detail rows, bottom preview strip with mini step grid.
|
||||
- **Mouse support**: click navigation on header/grid/panels/modals, text selection in code editor (click+drag), double-click on scope/spectrum/lissajous to cycle display modes.
|
||||
- Smooth playback progress bar interpolated between steps.
|
||||
- Dynamic step grid sizing adapts to terminal height.
|
||||
- Lissajous XY scope with Braille rendering and thermal trail mode.
|
||||
- Gain boost (1x–16x) and normalize toggle for scope/lissajous/spectrum.
|
||||
- Pattern description field: editable via `d`, shown in pattern list and properties.
|
||||
- Bank/pattern import and export via clipboard (base64 serialization for sharing).
|
||||
- Mute/solo on main page now apply immediately (no staging).
|
||||
- Step name automatically cleared when deleting a step.
|
||||
- F1–F6 page navigation across the 3×2 page grid.
|
||||
- Collapsible help sections with code block copy.
|
||||
- Onboarding system for first-time users.
|
||||
- Show/hide preview pane toggle and zoom factor setting.
|
||||
- Reduced UI lag: sequencer snapshot moved after render call.
|
||||
- 10 bundled demo projects loaded on fresh startup (togglable in Options).
|
||||
- Options page: each option shows a description line below when focused.
|
||||
- Dictionary page: word list uses full page height (removed description box).
|
||||
|
||||
### Themes
|
||||
- 5 new themes: Iceberg, Everforest, Fauve, Tropicalia, Jaipur.
|
||||
- Palette-based generation: all 18 themes derived from a 14-field Palette via Oklab color space (definitions reduced from ~300 to ~20 lines each).
|
||||
|
||||
### Desktop (egui)
|
||||
- Fixed Alt/Option key on macOS (dead-key composition now works).
|
||||
- Fixed multi-character text paste.
|
||||
- Extended function key support (F13–F20).
|
||||
- No console window on Windows desktop build.
|
||||
|
||||
### Packaging
|
||||
- macOS: `.dmg` disk image with `.app` bundle (Intel + Apple Silicon fat binaries via `lipo`).
|
||||
- Windows: `.msi` installer via WiX.
|
||||
- Linux: improved AppImage build scripts and Docker cross-compilation.
|
||||
|
||||
### CLAP Plugin (experimental)
|
||||
- Early CLAP plugin support via nih-plug, baseview, and egui. Feature-gated builds separate CLI from plugin targets.
|
||||
|
||||
### Documentation
|
||||
- Complete reorganization into `docs/` subdirectories.
|
||||
- 10 getting-started guides, 5 interactive tutorials.
|
||||
- New tutorials: Recording, Soundfonts, Sharing (import/export).
|
||||
- New topics: control flow, generators, harmony, randomness, variables, timing, bracket syntax.
|
||||
- Crate-level READMEs for forth, markdown, project, ratatui.
|
||||
|
||||
### Fixed
|
||||
- CycleList + ArpList index collision: arp uses timing index, cycle uses polyphony slot.
|
||||
- Scope widget not drawing completely in some terminal sizes.
|
||||
|
||||
### Codebase
|
||||
- `src/app.rs` split into 10 focused modules.
|
||||
- `src/input.rs` split into 8 page-specific handlers.
|
||||
- Undo/redo system with scope-based tracking.
|
||||
- Feature-gated CLI vs plugin builds.
|
||||
- New reusable widgets: CategoryList, HintBar, PropsForm, ScrollIndicators, SearchBar, SectionHeader.
|
||||
|
||||
## [0.0.9]
|
||||
|
||||
### Website
|
||||
|
||||
@@ -10,7 +10,6 @@ Contributions are welcome. There are many ways to contribute beyond code:
|
||||
## Prerequisites
|
||||
|
||||
- **Rust** (stable toolchain) - [rustup.rs](https://rustup.rs/)
|
||||
- **System libraries** - See [BUILDING.md](BUILDING.md) for platform-specific packages (cmake, ALSA, etc.)
|
||||
|
||||
## Quick start
|
||||
|
||||
|
||||
7542
Cargo.lock
generated
7542
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
48
Cargo.toml
48
Cargo.toml
@@ -1,12 +1,12 @@
|
||||
[workspace]
|
||||
members = ["crates/forth", "crates/markdown", "crates/project", "crates/ratatui", "plugins/cagire-plugins", "plugins/baseview", "plugins/egui-baseview", "plugins/nih-plug-egui", "xtask"]
|
||||
members = ["crates/forth", "crates/markdown", "crates/project", "crates/ratatui"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.4"
|
||||
version = "0.0.9"
|
||||
edition = "2021"
|
||||
authors = ["Raphaël Forment <raphael.forment@gmail.com>"]
|
||||
license = "AGPL-3.0"
|
||||
repository = "https://git.raphaelforment.fr/BuboBubo/cagire"
|
||||
repository = "https://github.com/Bubobubobubobubo/cagire"
|
||||
homepage = "https://cagire.raphaelforment.fr"
|
||||
description = "Forth-based live coding music sequencer"
|
||||
|
||||
@@ -34,29 +34,28 @@ path = "src/bin/desktop/main.rs"
|
||||
required-features = ["desktop"]
|
||||
|
||||
[features]
|
||||
default = ["cli"]
|
||||
cli = ["dep:cpal", "dep:midir", "dep:confy", "dep:clap", "dep:thread-priority"]
|
||||
block-renderer = ["dep:soft_ratatui", "dep:rustc-hash", "dep:egui", "dep:egui_ratatui"]
|
||||
default = []
|
||||
desktop = [
|
||||
"cli",
|
||||
"block-renderer",
|
||||
"cagire-forth/desktop",
|
||||
"dep:egui",
|
||||
"dep:eframe",
|
||||
"dep:egui_ratatui",
|
||||
"dep:soft_ratatui",
|
||||
"dep:rustc-hash",
|
||||
"dep:image",
|
||||
]
|
||||
asio = ["doux/asio", "cpal/asio"]
|
||||
|
||||
[dependencies]
|
||||
cagire-forth = { path = "crates/forth" }
|
||||
cagire-markdown = { path = "crates/markdown" }
|
||||
cagire-project = { path = "crates/project" }
|
||||
cagire-ratatui = { path = "crates/ratatui" }
|
||||
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.19", features = ["native", "soundfont"] }
|
||||
doux = { path = "/Users/bubo/doux", features = ["native"] }
|
||||
rusty_link = "0.4"
|
||||
ratatui = "0.30"
|
||||
crossterm = "0.29"
|
||||
cpal = { version = "0.17", optional = true }
|
||||
clap = { version = "4", features = ["derive"], optional = true }
|
||||
cpal = { version = "0.17", features = ["jack"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
@@ -65,12 +64,12 @@ tui-big-text = "0.8"
|
||||
arboard = "3"
|
||||
minimad = "0.13"
|
||||
crossbeam-channel = "0.5"
|
||||
confy = { version = "2", optional = true }
|
||||
confy = "2"
|
||||
rustfft = "6"
|
||||
thread-priority = { version = "1", optional = true }
|
||||
thread-priority = "1"
|
||||
ringbuf = "0.4"
|
||||
arc-swap = "1"
|
||||
midir = { version = "0.10", optional = true }
|
||||
midir = "0.10"
|
||||
parking_lot = "0.12"
|
||||
libc = "0.2"
|
||||
|
||||
@@ -83,28 +82,16 @@ rustc-hash = { version = "2", optional = true }
|
||||
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
cpal = { version = "0.17", optional = true, features = ["jack"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[patch."https://github.com/robbert-vdh/nih-plug"]
|
||||
nih_plug_egui = { path = "plugins/nih-plug-egui" }
|
||||
|
||||
[patch."https://github.com/BillyDM/egui-baseview.git"]
|
||||
egui-baseview = { path = "plugins/egui-baseview" }
|
||||
|
||||
[patch."https://github.com/RustAudio/baseview.git"]
|
||||
baseview = { path = "plugins/baseview" }
|
||||
|
||||
[package.metadata.bundle.bin.cagire-desktop]
|
||||
name = "Cagire"
|
||||
identifier = "com.sova.cagire"
|
||||
@@ -112,4 +99,3 @@ icon = ["assets/Cagire.icns", "assets/Cagire.ico", "assets/Cagire.png"]
|
||||
copyright = "Copyright (c) 2025 Raphaël Forment"
|
||||
category = "Music"
|
||||
short_description = "Forth-based music sequencer"
|
||||
minimum_system_version = "12.0"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
dockerfile = "./scripts/cross/aarch64-linux.Dockerfile"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
dockerfile = "./scripts/cross/x86_64-linux.Dockerfile"
|
||||
88
README.md
88
README.md
@@ -1,83 +1,37 @@
|
||||
<h1 align="center">Cagire</h1>
|
||||
|
||||
<p align="center"><em>A Forth-based live coding sequencer</em></p>
|
||||
<p align="center"><em>A Forth Music Sequencer</em></p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/Cagire.png" alt="Cagire" width="256">
|
||||
<img src="cagire_pixel.png" alt="Cagire" width="256">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cagire.raphaelforment.fr">Website</a> ·
|
||||
<a href="https://git.raphaelforment.fr/BuboBubo/cagire">Gitea</a> ·
|
||||
AGPL-3.0
|
||||
</p>
|
||||
Cagire is a terminal-based step sequencer for live coding music. Each step in a pattern contains a **Forth** script that produces sound and create events.
|
||||
|
||||
Cagire is a terminal based step sequencer and live coding platform. Each step in a sequence is represented by a **Forth** script. It ships with a self-contained audio engine. No external software is needed, Cagire is a fully autonomous musical instrument that provides everything you need to perform.
|
||||
## Build
|
||||
|
||||
### Examples
|
||||
|
||||
A filtered sawtooth with reverb:
|
||||
|
||||
```forth
|
||||
saw sound
|
||||
200 199 freq
|
||||
400 lpf
|
||||
.8 lpq .3 verb
|
||||
.
|
||||
Terminal version:
|
||||
```
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
A generative pattern using randomness, scales, and effects:
|
||||
|
||||
```forth
|
||||
sine sound 2 fm 0.5 fmh
|
||||
0 7 rand minor 50 + note
|
||||
.1 .8 rand lpf
|
||||
1 4 rand 10 * delay .5 delayfeedback
|
||||
.
|
||||
Desktop version (with egui window):
|
||||
```
|
||||
cargo build --release --features desktop --bin cagire-desktop
|
||||
```
|
||||
|
||||
### Features
|
||||
## Run
|
||||
|
||||
- **Cagire's Forth**: a stack-based language made for live coding
|
||||
- Forth has almost no syntax, only words, numbers and spaces. Very easy to learn for beginners, quite deep for experienced programmers.
|
||||
- Nondeterminism and generative: randomness, probabilities, patterns thought as first-class features.
|
||||
- Quotations: code blocks `( ... )` that compose with probability, cycling, euclidean, and conditional words.
|
||||
- User-defined words: extend (or redefine) the language on the fly with `:name ... ;` definitions.
|
||||
- Interactive documentation: built-in tutorials with runnable examples.
|
||||
- **Audio engine** (powered by [Doux](https://doux.livecoding.fr)):
|
||||
- Synthesis: classic waveforms (saw, pulse, tri, sine), additive (up to 32 partials), FM (2-op, 3 algorithms), wavetables, 7-voice spread.
|
||||
- Drum models: seven drum models with timbral morphing.
|
||||
- Sampling: disk-loaded samples with slicing, looping, pitch tracking, wavetable mode, and live recording from engine output or line input.
|
||||
- Filters: biquad LP/HP/BP and ladder filters. Filters can be modulated, stacked, etc.
|
||||
- Effects: phaser, flanger, chorus, smear, distortion, wavefolder, wavewrapper, bitcrusher, sample-rate reduction, 3-band EQ, tilt EQ, Haas stereo.
|
||||
- Bus effects: delay (standard, ping-pong, tape, multitap), two reverb engines (Dattorro plate, Vital Space), comb filter, feedback delay with LFO, sidechain compressor.
|
||||
- Modulation: vibrato, AM, ring mod, audio-rate LFO, transitions, DAHDSR envelope modulation — all applicable to any parameter.
|
||||
- **Sequencing**: probabilities, patterns, euclidean structures, sub-step timing, pattern chaining and a lot more.
|
||||
- **MIDI**: receive or send MIDI messages across up to 4 inputs and 4 outputs.
|
||||
- **Ableton Link**: tempo and phase sync with any Link-enabled software or hardware.
|
||||
- **Cross-platform**: terminal and desktop interfaces on macOS, Linux, and Windows.
|
||||
- **Plugins**: run Cagire as a CLAP or VST3 plugin inside your DAW (separate version).
|
||||
Terminal version:
|
||||
```
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
### Getting started
|
||||
Desktop version:
|
||||
```
|
||||
cargo run --release --features desktop --bin cagire-desktop
|
||||
```
|
||||
|
||||
Download the latest release for your platform from the [website](https://cagire.raphaelforment.fr).
|
||||
## License
|
||||
|
||||
To build from source instead, see [BUILDING.md](BUILDING.md).
|
||||
|
||||
### Documentation
|
||||
|
||||
Cagire includes interactive documentation with runnable code examples. Press **F4** in the application to open it.
|
||||
|
||||
- [Website](https://cagire.raphaelforment.fr)
|
||||
- [BUILDING.md](BUILDING.md) — build instructions and CLI flags
|
||||
- [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
### Credits
|
||||
|
||||
Cagire is developed by [BuboBubo](https://raphaelforment.fr) (Raphael Forment).
|
||||
|
||||
- **[Doux](https://doux.livecoding.fr)** (audio engine) — Rust port of Dough, originally written in C by Felix Roos
|
||||
|
||||
### License
|
||||
|
||||
[AGPL-3.0](LICENSE)
|
||||
AGPL-3.0
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 26 KiB |
@@ -1,18 +0,0 @@
|
||||
# Cagire - A Forth-based music sequencer
|
||||
|
||||
## Installation
|
||||
|
||||
Drag Cagire.app into the Applications folder.
|
||||
|
||||
## Unquarantine
|
||||
|
||||
Since this app is not signed with an Apple Developer certificate,
|
||||
macOS will block it from running. Thanks Apple! To fix this, open
|
||||
Terminal and run:
|
||||
|
||||
xattr -cr /Applications/Cagire.app
|
||||
|
||||
## Support
|
||||
|
||||
If you enjoy this software, consider supporting development:
|
||||
https://ko-fi.com/raphaelbubo
|
||||
@@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Cagire
|
||||
Comment=Forth-based music sequencer
|
||||
Exec=cagire
|
||||
Icon=cagire
|
||||
Categories=Audio;Music;AudioVideo;
|
||||
26
build.rs
26
build.rs
@@ -1,25 +1,11 @@
|
||||
//! Build script — embeds Windows application resources (icon, metadata).
|
||||
|
||||
fn main() {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
|
||||
if target_os == "windows" {
|
||||
println!("cargo:rustc-link-lib=ws2_32");
|
||||
println!("cargo:rustc-link-lib=iphlpapi");
|
||||
println!("cargo:rustc-link-lib=winmm");
|
||||
println!("cargo:rustc-link-lib=ole32");
|
||||
println!("cargo:rustc-link-lib=oleaut32");
|
||||
}
|
||||
|
||||
if target_os == "windows" {
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let icon = format!("{manifest_dir}/assets/Cagire.ico");
|
||||
winresource::WindowsResource::new()
|
||||
.set_icon(&icon)
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("assets/Cagire.ico")
|
||||
.set("ProductName", "Cagire")
|
||||
.set("FileDescription", "Forth-based music sequencer")
|
||||
.set("LegalCopyright", "Copyright (c) 2025 Raphaël Forment")
|
||||
.compile()
|
||||
.expect("Failed to compile Windows resources");
|
||||
.set("LegalCopyright", "Copyright (c) 2025 Raphaël Forment");
|
||||
res.compile().expect("Failed to compile Windows resources");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# cagire-forth
|
||||
|
||||
Stack-based Forth VM for the Cagire sequencer. Tokenizes, compiles, and executes step scripts to produce audio and MIDI commands.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `vm` | Interpreter loop, `Forth::evaluate()` entry point |
|
||||
| `compiler` | Tokenization (with source spans) and single-pass compilation to ops |
|
||||
| `ops` | `Op` enum (~90 variants) |
|
||||
| `types` | `Value`, `StepContext`, shared state types |
|
||||
| `words/` | Built-in word definitions: `core`, `sound`, `music`, `midi`, `effects`, `sequencing`, `compile` |
|
||||
| `theory/` | Music theory lookups: `scales` (~200 patterns), `chords` (interval arrays) |
|
||||
|
||||
## Key Types
|
||||
|
||||
- **`Forth`** — VM instance, holds stacks and compilation state
|
||||
- **`Value`** — Stack value (int, float, string, list, quotation, ...)
|
||||
- **`StepContext`** — Per-step evaluation context (step index, tempo, variables, ...)
|
||||
- **`Op`** — Compiled operation; nondeterministic variants carry `Option<SourceSpan>` for tracing
|
||||
- **`ExecutionTrace`** — Records executed/selected spans and resolved values during evaluation
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Single-pass compiler from Forth source text to Op sequences.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -15,7 +13,6 @@ enum Token {
|
||||
Word(String, SourceSpan),
|
||||
}
|
||||
|
||||
/// Compile Forth source text into an executable Op sequence.
|
||||
pub(super) fn compile_script(input: &str, dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
let tokens = tokenize(input);
|
||||
compile(&tokens, dict)
|
||||
@@ -31,7 +28,7 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '{' || c == '}' {
|
||||
if c == '(' || c == ')' {
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
@@ -133,7 +130,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))),
|
||||
Token::Word(w, span) => {
|
||||
let word = w.as_str();
|
||||
if word == "(" {
|
||||
if word == "{" {
|
||||
let (quote_ops, consumed, end_span) =
|
||||
compile_quotation(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
@@ -142,21 +139,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
end: end_span.end,
|
||||
};
|
||||
ops.push(Op::Quotation(Arc::from(quote_ops), Some(body_span)));
|
||||
} else if word == ")" {
|
||||
return Err("unexpected )".into());
|
||||
} else if word == "[" {
|
||||
let (bracket_ops, consumed, end_span) =
|
||||
compile_bracket(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
ops.push(Op::Mark);
|
||||
ops.extend(bracket_ops);
|
||||
let count_span = SourceSpan {
|
||||
start: span.start,
|
||||
end: end_span.end,
|
||||
};
|
||||
ops.push(Op::Count(Some(count_span)));
|
||||
} else if word == "]" {
|
||||
return Err("unexpected ]".into());
|
||||
} else if word == "}" {
|
||||
return Err("unexpected }".into());
|
||||
} else if word == ":" {
|
||||
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
@@ -176,13 +160,6 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
ops.push(Op::Branch(else_ops.len()));
|
||||
ops.extend(else_ops);
|
||||
}
|
||||
} else if word == "at" {
|
||||
if let Some((body_ops, consumed)) = compile_at(&tokens[i + 1..], dict)? {
|
||||
i += consumed;
|
||||
ops.push(Op::AtLoop(Arc::from(body_ops)));
|
||||
} else if !compile_word(word, Some(*span), &mut ops, dict) {
|
||||
return Err(format!("unknown word: {word}"));
|
||||
}
|
||||
} else if word == "case" {
|
||||
let (case_ops, consumed) = compile_case(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
@@ -210,8 +187,8 @@ fn compile_quotation(
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
if let Token::Word(w, _) = tok {
|
||||
match w.as_str() {
|
||||
"(" => depth += 1,
|
||||
")" => {
|
||||
"{" => depth += 1,
|
||||
"}" => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end_idx = Some(i);
|
||||
@@ -223,7 +200,7 @@ fn compile_quotation(
|
||||
}
|
||||
}
|
||||
|
||||
let end_idx = end_idx.ok_or("missing )")?;
|
||||
let end_idx = end_idx.ok_or("missing }")?;
|
||||
let end_span = match &tokens[end_idx] {
|
||||
Token::Word(_, span) => *span,
|
||||
_ => unreachable!(),
|
||||
@@ -232,38 +209,6 @@ fn compile_quotation(
|
||||
Ok((quote_ops, end_idx + 1, end_span))
|
||||
}
|
||||
|
||||
fn compile_bracket(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
let mut depth = 1;
|
||||
let mut end_idx = None;
|
||||
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
if let Token::Word(w, _) = tok {
|
||||
match w.as_str() {
|
||||
"[" => depth += 1,
|
||||
"]" => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end_idx = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let end_idx = end_idx.ok_or("missing ]")?;
|
||||
let end_span = match &tokens[end_idx] {
|
||||
Token::Word(_, span) => *span,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let body_ops = compile(&tokens[..end_idx], dict)?;
|
||||
Ok((body_ops, end_idx + 1, end_span))
|
||||
}
|
||||
|
||||
fn token_span(tok: &Token) -> Option<SourceSpan> {
|
||||
match tok {
|
||||
Token::Int(_, s) | Token::Float(_, s) | Token::Str(_, s) | Token::Word(_, s) => Some(*s),
|
||||
@@ -362,37 +307,6 @@ fn compile_if(
|
||||
Ok((then_ops, else_ops, then_pos + 1, then_span, else_span))
|
||||
}
|
||||
|
||||
fn compile_at(tokens: &[Token], dict: &Dictionary) -> Result<Option<(Vec<Op>, usize)>, String> {
|
||||
let mut depth = 1;
|
||||
|
||||
enum AtCloser { Dot, MidiDot, Done }
|
||||
let mut found: Option<(usize, AtCloser)> = None;
|
||||
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
if let Token::Word(w, _) = tok {
|
||||
match w.as_str() {
|
||||
"at" => depth += 1,
|
||||
"." if depth == 1 => { found = Some((i, AtCloser::Dot)); break; }
|
||||
"m." if depth == 1 => { found = Some((i, AtCloser::MidiDot)); break; }
|
||||
"done" if depth == 1 => { found = Some((i, AtCloser::Done)); break; }
|
||||
"." | "m." | "done" => depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some((pos, closer)) = found else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut body_ops = compile(&tokens[..pos], dict)?;
|
||||
match closer {
|
||||
AtCloser::Dot => body_ops.push(Op::Emit),
|
||||
AtCloser::MidiDot => body_ops.push(Op::MidiEmit),
|
||||
AtCloser::Done => {}
|
||||
}
|
||||
Ok(Some((body_ops, pos + 1)))
|
||||
}
|
||||
|
||||
fn compile_case(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize), String> {
|
||||
let mut depth = 1;
|
||||
let mut endcase_pos = None;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Forth virtual machine for the Cagire music sequencer.
|
||||
|
||||
mod compiler;
|
||||
mod ops;
|
||||
mod theory;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
//! Compiled operation variants for the Forth VM instruction set.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::types::SourceSpan;
|
||||
|
||||
/// Single VM instruction produced by the compiler.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Op {
|
||||
PushInt(i64, Option<SourceSpan>),
|
||||
@@ -65,7 +62,6 @@ pub enum Op {
|
||||
NewCmd,
|
||||
SetParam(&'static str),
|
||||
Emit,
|
||||
Print,
|
||||
Get,
|
||||
Set,
|
||||
SetKeep,
|
||||
@@ -78,7 +74,6 @@ pub enum Op {
|
||||
PCycle(Option<SourceSpan>),
|
||||
Choose(Option<SourceSpan>),
|
||||
Bounce(Option<SourceSpan>),
|
||||
PBounce(Option<SourceSpan>),
|
||||
WChoose(Option<SourceSpan>),
|
||||
ChanceExec(Option<SourceSpan>),
|
||||
ProbExec(Option<SourceSpan>),
|
||||
@@ -87,9 +82,6 @@ pub enum Op {
|
||||
Ftom,
|
||||
SetTempo,
|
||||
Every(Option<SourceSpan>),
|
||||
Except(Option<SourceSpan>),
|
||||
EveryOffset(Option<SourceSpan>),
|
||||
ExceptOffset(Option<SourceSpan>),
|
||||
Bjork(Option<SourceSpan>),
|
||||
PBjork(Option<SourceSpan>),
|
||||
Quotation(Arc<[Op]>, Option<SourceSpan>),
|
||||
@@ -101,17 +93,15 @@ pub enum Op {
|
||||
Ramp,
|
||||
Triangle,
|
||||
Range,
|
||||
LinMap,
|
||||
ExpMap,
|
||||
Perlin,
|
||||
Chain,
|
||||
Loop,
|
||||
Degree(&'static [i64]),
|
||||
Oct,
|
||||
ClearCmd,
|
||||
SetSpeed,
|
||||
At,
|
||||
AtLoop(Arc<[Op]>),
|
||||
|
||||
Arp,
|
||||
IntRange,
|
||||
StepRange,
|
||||
Generate,
|
||||
@@ -119,27 +109,12 @@ pub enum Op {
|
||||
Euclid,
|
||||
EuclidRot,
|
||||
Times,
|
||||
Map,
|
||||
Chord(&'static [i64]),
|
||||
Transpose,
|
||||
Invert,
|
||||
DownInvert,
|
||||
VoiceDrop2,
|
||||
VoiceDrop3,
|
||||
SetKey,
|
||||
DiatonicTriad(&'static [i64]),
|
||||
DiatonicSeventh(&'static [i64]),
|
||||
// Audio-rate modulation DSL
|
||||
ModLfo(u8),
|
||||
ModSlide(u8),
|
||||
ModRnd(u8),
|
||||
ModEnv,
|
||||
ModEnvAd,
|
||||
ModEnvAdr,
|
||||
Lpg,
|
||||
// Global params
|
||||
EmitAll,
|
||||
ClearGlobal,
|
||||
// MIDI
|
||||
MidiEmit,
|
||||
GetMidiCC,
|
||||
@@ -147,13 +122,4 @@ pub enum Op {
|
||||
MidiStart,
|
||||
MidiStop,
|
||||
MidiContinue,
|
||||
// Recording
|
||||
Rec,
|
||||
Overdub,
|
||||
Orec,
|
||||
Odub,
|
||||
// Bracket syntax (mark/count for auto-counting)
|
||||
Mark,
|
||||
Count(Option<SourceSpan>),
|
||||
Index(Option<SourceSpan>),
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
//! Chord definitions as semitone interval arrays.
|
||||
|
||||
/// Named chord with its interval pattern.
|
||||
pub struct Chord {
|
||||
pub name: &'static str,
|
||||
pub intervals: &'static [i64],
|
||||
}
|
||||
|
||||
/// All built-in chord types.
|
||||
pub static CHORDS: &[Chord] = &[
|
||||
// Triads
|
||||
Chord {
|
||||
@@ -109,47 +105,6 @@ pub static CHORDS: &[Chord] = &[
|
||||
name: "madd9",
|
||||
intervals: &[0, 3, 7, 14],
|
||||
},
|
||||
// Power chord
|
||||
Chord {
|
||||
name: "pwr",
|
||||
intervals: &[0, 7],
|
||||
},
|
||||
// Suspended seventh
|
||||
Chord {
|
||||
name: "7sus4",
|
||||
intervals: &[0, 5, 7, 10],
|
||||
},
|
||||
Chord {
|
||||
name: "9sus4",
|
||||
intervals: &[0, 5, 7, 10, 14],
|
||||
},
|
||||
// Augmented major
|
||||
Chord {
|
||||
name: "augmaj7",
|
||||
intervals: &[0, 4, 8, 11],
|
||||
},
|
||||
// 6/9 chords
|
||||
Chord {
|
||||
name: "maj69",
|
||||
intervals: &[0, 4, 7, 9, 14],
|
||||
},
|
||||
Chord {
|
||||
name: "min69",
|
||||
intervals: &[0, 3, 7, 9, 14],
|
||||
},
|
||||
// Extended
|
||||
Chord {
|
||||
name: "maj11",
|
||||
intervals: &[0, 4, 7, 11, 14, 17],
|
||||
},
|
||||
Chord {
|
||||
name: "maj13",
|
||||
intervals: &[0, 4, 7, 11, 14, 21],
|
||||
},
|
||||
Chord {
|
||||
name: "min13",
|
||||
intervals: &[0, 3, 7, 10, 14, 21],
|
||||
},
|
||||
// Altered dominants
|
||||
Chord {
|
||||
name: "dom7b9",
|
||||
@@ -167,13 +122,8 @@ pub static CHORDS: &[Chord] = &[
|
||||
name: "dom7s5",
|
||||
intervals: &[0, 4, 8, 10],
|
||||
},
|
||||
Chord {
|
||||
name: "dom7s11",
|
||||
intervals: &[0, 4, 7, 10, 18],
|
||||
},
|
||||
];
|
||||
|
||||
/// Find a chord's intervals by name.
|
||||
pub fn lookup(name: &str) -> Option<&'static [i64]> {
|
||||
CHORDS.iter().find(|c| c.name == name).map(|c| c.intervals)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Music theory data — chord and scale lookup tables.
|
||||
|
||||
pub mod chords;
|
||||
mod scales;
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
//! Scale definitions as semitone offset arrays.
|
||||
|
||||
/// Named scale with its semitone pattern.
|
||||
pub struct Scale {
|
||||
pub name: &'static str,
|
||||
pub pattern: &'static [i64],
|
||||
}
|
||||
|
||||
/// All built-in scale types.
|
||||
pub static SCALES: &[Scale] = &[
|
||||
Scale {
|
||||
name: "major",
|
||||
@@ -129,7 +125,6 @@ pub static SCALES: &[Scale] = &[
|
||||
},
|
||||
];
|
||||
|
||||
/// Find a scale's pattern by name.
|
||||
pub fn lookup(name: &str) -> Option<&'static [i64]> {
|
||||
SCALES.iter().find(|s| s.name == name).map(|s| s.pattern)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Core types for the Forth VM: values, execution context, and shared state.
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
@@ -14,14 +12,12 @@ pub trait CcAccess: Send + Sync {
|
||||
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8;
|
||||
}
|
||||
|
||||
/// Byte range in source text.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SourceSpan {
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
/// Concrete value resolved from a nondeterministic op, used for trace annotations.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ResolvedValue {
|
||||
Int(i64),
|
||||
@@ -41,7 +37,6 @@ impl ResolvedValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spans and resolved values collected during a single evaluation, used for UI highlighting.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExecutionTrace {
|
||||
pub executed_spans: Vec<SourceSpan>,
|
||||
@@ -49,7 +44,6 @@ pub struct ExecutionTrace {
|
||||
pub resolved: Vec<(SourceSpan, ResolvedValue)>,
|
||||
}
|
||||
|
||||
/// Per-step sequencer state passed into the VM.
|
||||
pub struct StepContext<'a> {
|
||||
pub step: usize,
|
||||
pub beat: f64,
|
||||
@@ -63,11 +57,14 @@ pub struct StepContext<'a> {
|
||||
pub speed: f64,
|
||||
pub fill: bool,
|
||||
pub nudge_secs: f64,
|
||||
pub sr: f64,
|
||||
pub cc_access: Option<&'a dyn CcAccess>,
|
||||
pub speed_key: &'a str,
|
||||
pub chain_key: &'a str,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_y: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_down: f64,
|
||||
}
|
||||
|
||||
@@ -77,18 +74,13 @@ impl StepContext<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Underlying map for user-defined variables.
|
||||
pub type VariablesMap = HashMap<String, Value>;
|
||||
/// Shared variable store, swapped atomically after each step.
|
||||
pub type Variables = Arc<ArcSwap<VariablesMap>>;
|
||||
/// Shared user-defined word dictionary.
|
||||
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||
/// Shared random number generator.
|
||||
pub type Rng = Arc<Mutex<StdRng>>;
|
||||
pub type Stack = Mutex<Vec<Value>>;
|
||||
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]);
|
||||
|
||||
/// Stack value in the Forth VM.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
Int(i64, Option<SourceSpan>),
|
||||
@@ -96,7 +88,7 @@ pub enum Value {
|
||||
Str(Arc<str>, Option<SourceSpan>),
|
||||
Quotation(Arc<[Op]>, Option<SourceSpan>),
|
||||
CycleList(Arc<[Value]>),
|
||||
|
||||
ArpList(Arc<[Value]>),
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
@@ -107,7 +99,7 @@ impl PartialEq for Value {
|
||||
(Value::Str(a, _), Value::Str(b, _)) => a == b,
|
||||
(Value::Quotation(a, _), Value::Quotation(b, _)) => a == b,
|
||||
(Value::CycleList(a), Value::CycleList(b)) => a == b,
|
||||
|
||||
(Value::ArpList(a), Value::ArpList(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -143,7 +135,7 @@ impl Value {
|
||||
Value::Float(f, _) => *f != 0.0,
|
||||
Value::Str(s, _) => !s.is_empty(),
|
||||
Value::Quotation(..) => true,
|
||||
Value::CycleList(items) => !items.is_empty(),
|
||||
Value::CycleList(items) | Value::ArpList(items) => !items.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +145,14 @@ impl Value {
|
||||
Value::Float(f, _) => f.to_string(),
|
||||
Value::Str(s, _) => s.to_string(),
|
||||
Value::Quotation(..) => String::new(),
|
||||
Value::CycleList(_) => String::new(),
|
||||
Value::CycleList(_) | Value::ArpList(_) => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn span(&self) -> Option<SourceSpan> {
|
||||
match self {
|
||||
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) | Value::Quotation(_, s) => *s,
|
||||
Value::CycleList(_) => None,
|
||||
Value::CycleList(_) | Value::ArpList(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,8 +162,6 @@ pub(super) struct CmdRegister {
|
||||
sound: Option<Value>,
|
||||
params: Vec<(&'static str, Value)>,
|
||||
deltas: Vec<Value>,
|
||||
global_params: Vec<(&'static str, Value)>,
|
||||
delta_secs: Option<f64>,
|
||||
}
|
||||
|
||||
impl CmdRegister {
|
||||
@@ -180,8 +170,6 @@ impl CmdRegister {
|
||||
sound: None,
|
||||
params: Vec::with_capacity(16),
|
||||
deltas: Vec::with_capacity(4),
|
||||
global_params: Vec::new(),
|
||||
delta_secs: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,48 +205,9 @@ impl CmdRegister {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn global_params(&self) -> &[(&'static str, Value)] {
|
||||
&self.global_params
|
||||
}
|
||||
|
||||
pub(super) fn commit_global(&mut self) {
|
||||
self.global_params.append(&mut self.params);
|
||||
self.sound = None;
|
||||
self.deltas.clear();
|
||||
}
|
||||
|
||||
pub(super) fn clear_global(&mut self) {
|
||||
self.global_params.clear();
|
||||
}
|
||||
|
||||
pub fn set_global(&mut self, params: Vec<(&'static str, Value)>) {
|
||||
self.global_params = params;
|
||||
}
|
||||
|
||||
pub fn take_global(&mut self) -> Vec<(&'static str, Value)> {
|
||||
std::mem::take(&mut self.global_params)
|
||||
}
|
||||
|
||||
pub(super) fn set_delta_secs(&mut self, secs: f64) {
|
||||
self.delta_secs = Some(secs);
|
||||
}
|
||||
|
||||
pub(super) fn take_delta_secs(&mut self) -> Option<f64> {
|
||||
self.delta_secs.take()
|
||||
}
|
||||
|
||||
pub(super) fn clear_sound(&mut self) {
|
||||
self.sound = None;
|
||||
}
|
||||
|
||||
pub(super) fn clear_params(&mut self) {
|
||||
self.params.clear();
|
||||
}
|
||||
|
||||
pub(super) fn clear(&mut self) {
|
||||
self.sound = None;
|
||||
self.params.clear();
|
||||
self.deltas.clear();
|
||||
self.delta_secs = None;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
||||
//! Word-to-Op translation: maps Forth word names to compiled instructions.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ops::Op;
|
||||
@@ -13,7 +11,6 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"dup" => Op::Dup,
|
||||
"dupn" => Op::Dupn,
|
||||
"drop" => Op::Drop,
|
||||
"print" => Op::Print,
|
||||
"swap" => Op::Swap,
|
||||
"over" => Op::Over,
|
||||
"rot" => Op::Rot,
|
||||
@@ -59,7 +56,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"nand" => Op::Nand,
|
||||
"nor" => Op::Nor,
|
||||
"ifelse" => Op::IfElse,
|
||||
"select" => Op::Pick,
|
||||
"pick" => Op::Pick,
|
||||
"sound" => Op::NewCmd,
|
||||
"." => Op::Emit,
|
||||
"rand" => Op::Rand(None),
|
||||
@@ -70,12 +67,8 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"pcycle" => Op::PCycle(None),
|
||||
"choose" => Op::Choose(None),
|
||||
"bounce" => Op::Bounce(None),
|
||||
"pbounce" => Op::PBounce(None),
|
||||
"wchoose" => Op::WChoose(None),
|
||||
"every" => Op::Every(None),
|
||||
"except" => Op::Except(None),
|
||||
"every+" => Op::EveryOffset(None),
|
||||
"except+" => Op::ExceptOffset(None),
|
||||
"bjork" => Op::Bjork(None),
|
||||
"pbjork" => Op::PBjork(None),
|
||||
"chance" => Op::ChanceExec(None),
|
||||
@@ -88,21 +81,18 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"tempo!" => Op::SetTempo,
|
||||
"speed!" => Op::SetSpeed,
|
||||
"at" => Op::At,
|
||||
|
||||
"arp" => Op::Arp,
|
||||
"adsr" => Op::Adsr,
|
||||
"ad" => Op::Ad,
|
||||
"apply" => Op::Apply,
|
||||
"ramp" => Op::Ramp,
|
||||
"triangle" => Op::Triangle,
|
||||
"range" => Op::Range,
|
||||
"linmap" => Op::LinMap,
|
||||
"expmap" => Op::ExpMap,
|
||||
"perlin" => Op::Perlin,
|
||||
"chain" => Op::Chain,
|
||||
"loop" => Op::Loop,
|
||||
"oct" => Op::Oct,
|
||||
"clear" => Op::ClearCmd,
|
||||
"all" => Op::EmitAll,
|
||||
"noall" => Op::ClearGlobal,
|
||||
".." => Op::IntRange,
|
||||
".," => Op::StepRange,
|
||||
"gen" => Op::Generate,
|
||||
@@ -110,25 +100,13 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"euclid" => Op::Euclid,
|
||||
"euclidrot" => Op::EuclidRot,
|
||||
"times" => Op::Times,
|
||||
"map" => Op::Map,
|
||||
"m." => Op::MidiEmit,
|
||||
"ccval" => Op::GetMidiCC,
|
||||
"mclock" => Op::MidiClock,
|
||||
"mstart" => Op::MidiStart,
|
||||
"mstop" => Op::MidiStop,
|
||||
"mcont" => Op::MidiContinue,
|
||||
"rec" => Op::Rec,
|
||||
"overdub" | "dub" => Op::Overdub,
|
||||
"orec" => Op::Orec,
|
||||
"odub" => Op::Odub,
|
||||
"forget" => Op::Forget,
|
||||
"index" => Op::Index(None),
|
||||
"key!" => Op::SetKey,
|
||||
"tp" => Op::Transpose,
|
||||
"inv" => Op::Invert,
|
||||
"dinv" => Op::DownInvert,
|
||||
"drop2" => Op::VoiceDrop2,
|
||||
"drop3" => Op::VoiceDrop3,
|
||||
"lfo" => Op::ModLfo(0),
|
||||
"tlfo" => Op::ModLfo(1),
|
||||
"wlfo" => Op::ModLfo(2),
|
||||
@@ -136,16 +114,10 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"slide" => Op::ModSlide(0),
|
||||
"expslide" => Op::ModSlide(1),
|
||||
"sslide" => Op::ModSlide(2),
|
||||
"islide" => Op::ModSlide(3),
|
||||
"oslide" => Op::ModSlide(4),
|
||||
"pslide" => Op::ModSlide(5),
|
||||
"jit" => Op::ModRnd(0),
|
||||
"sjit" => Op::ModRnd(1),
|
||||
"drunk" => Op::ModRnd(2),
|
||||
"ead" => Op::ModEnvAd,
|
||||
"eadr" => Op::ModEnvAdr,
|
||||
"eadsr" | "env" => Op::ModEnv,
|
||||
"lpg" => Op::Lpg,
|
||||
"env" => Op::ModEnv,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -222,10 +194,9 @@ fn attach_span(op: &mut Op, span: SourceSpan) {
|
||||
match op {
|
||||
Op::Rand(s) | Op::ExpRand(s) | Op::LogRand(s) | Op::Coin(s)
|
||||
| Op::Choose(s) | Op::WChoose(s) | Op::Cycle(s) | Op::PCycle(s)
|
||||
| Op::Bounce(s) | Op::PBounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
|
||||
| Op::Every(s) | Op::Except(s) | Op::EveryOffset(s) | Op::ExceptOffset(s)
|
||||
| Op::Bjork(s) | Op::PBjork(s)
|
||||
| Op::Count(s) | Op::Index(s) => *s = Some(span),
|
||||
| Op::Bounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
|
||||
| Op::Every(s)
|
||||
| Op::Bjork(s) | Op::PBjork(s) => *s = Some(span),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -255,20 +226,6 @@ pub(crate) fn compile_word(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if name == "triad" || name == "seventh" {
|
||||
if let Some(Op::Degree(pattern)) = ops.last() {
|
||||
let pattern = *pattern;
|
||||
ops.pop();
|
||||
ops.push(if name == "triad" {
|
||||
Op::DiatonicTriad(pattern)
|
||||
} else {
|
||||
Op::DiatonicSeventh(pattern)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(pattern) = theory::lookup(name) {
|
||||
ops.push(Op::Degree(pattern));
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Word metadata for core language primitives (stack, arithmetic, logic, variables, definitions).
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// Stack, Arithmetic, Comparison, Logic, Control, Variables, Definitions
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
// Stack manipulation
|
||||
Word {
|
||||
@@ -33,16 +33,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "print",
|
||||
aliases: &[],
|
||||
category: "Stack",
|
||||
stack: "(x --)",
|
||||
desc: "Print top of stack to footer bar",
|
||||
example: "42 print",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "swap",
|
||||
aliases: &[],
|
||||
@@ -364,26 +354,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "linmap",
|
||||
aliases: &[],
|
||||
category: "Arithmetic",
|
||||
stack: "(val inlo inhi outlo outhi -- mapped)",
|
||||
desc: "Linear map from [inlo,inhi] to [outlo,outhi]",
|
||||
example: "64 0 127 200 2000 linmap => 1007.87",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "expmap",
|
||||
aliases: &[],
|
||||
category: "Arithmetic",
|
||||
stack: "(val lo hi -- mapped)",
|
||||
desc: "Exponential map from [0,1] to [lo,hi]",
|
||||
example: "0.5 200 8000 expmap => 1264.91",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// Comparison
|
||||
Word {
|
||||
name: "=",
|
||||
@@ -512,17 +482,17 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Logic",
|
||||
stack: "(true-quot false-quot bool --)",
|
||||
desc: "Execute true-quot if true, else false-quot",
|
||||
example: "( 1 ) ( 2 ) coin ifelse",
|
||||
example: "{ 1 } { 2 } coin ifelse",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "select",
|
||||
name: "pick",
|
||||
aliases: &[],
|
||||
category: "Logic",
|
||||
stack: "(..quots n --)",
|
||||
desc: "Execute nth quotation (0-indexed)",
|
||||
example: "( 1 ) ( 2 ) ( 3 ) 2 select => 3",
|
||||
example: "{ 1 } { 2 } { 3 } 2 pick => 3",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
@@ -532,7 +502,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Logic",
|
||||
stack: "(quot bool --)",
|
||||
desc: "Execute quotation if true",
|
||||
example: "( 2 distort ) 0.5 chance ?",
|
||||
example: "{ 2 distort } 0.5 chance ?",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -542,7 +512,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Logic",
|
||||
stack: "(quot bool --)",
|
||||
desc: "Execute quotation if false",
|
||||
example: "( 1 distort ) 0.5 chance !?",
|
||||
example: "{ 1 distort } 0.5 chance !?",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -552,7 +522,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Logic",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation unconditionally",
|
||||
example: "( 2 * ) apply",
|
||||
example: "{ 2 * } apply",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -563,17 +533,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Control",
|
||||
stack: "(n quot --)",
|
||||
desc: "Execute quotation n times, @i holds current index",
|
||||
example: "4 ( @i . ) times => 0 1 2 3",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "map",
|
||||
aliases: &[],
|
||||
category: "Control",
|
||||
stack: "(..vals quot -- ..results)",
|
||||
desc: "Apply quotation to each stack element",
|
||||
example: "1 2 3 ( 10 * ) map => 10 20 30",
|
||||
example: "4 { @i . } times => 0 1 2 3",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Word metadata for audio effect parameters (filter, envelope, reverb, delay, lo-fi, stereo, mod FX).
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// Filter, Envelope, Reverb, Delay, Lo-fi, Stereo, Mod FX
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
// Envelope
|
||||
Word {
|
||||
@@ -28,14 +28,14 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set velocity (0-1)",
|
||||
example: "0.8 velocity",
|
||||
desc: "Set velocity",
|
||||
example: "100 velocity",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "attack",
|
||||
aliases: &["att", "a"],
|
||||
aliases: &["att"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set attack time",
|
||||
@@ -45,7 +45,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
},
|
||||
Word {
|
||||
name: "decay",
|
||||
aliases: &["dec", "d"],
|
||||
aliases: &["dec"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set decay time",
|
||||
@@ -55,7 +55,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
},
|
||||
Word {
|
||||
name: "sustain",
|
||||
aliases: &["sus", "s"],
|
||||
aliases: &["sus"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set sustain level",
|
||||
@@ -65,7 +65,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
},
|
||||
Word {
|
||||
name: "release",
|
||||
aliases: &["rel", "r"],
|
||||
aliases: &["rel"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set release time",
|
||||
@@ -73,26 +73,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "envdelay",
|
||||
aliases: &["envdly"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set envelope delay time",
|
||||
example: "0.1 envdelay",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hold",
|
||||
aliases: &["hld"],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set envelope hold time",
|
||||
example: "0.05 hold",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "adsr",
|
||||
aliases: &[],
|
||||
@@ -113,6 +93,56 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "penv",
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set pitch envelope",
|
||||
example: "0.5 penv",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "patt",
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set pitch attack",
|
||||
example: "0.01 patt",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "pdec",
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set pitch decay",
|
||||
example: "0.1 pdec",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "psus",
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set pitch sustain",
|
||||
example: "0 psus",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "prel",
|
||||
aliases: &[],
|
||||
category: "Envelope",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set pitch release",
|
||||
example: "0.1 prel",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
// Filter
|
||||
Word {
|
||||
name: "lpf",
|
||||
@@ -134,6 +164,56 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "lpe",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set lowpass envelope",
|
||||
example: "0.5 lpe",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "lpa",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set lowpass attack",
|
||||
example: "0.01 lpa",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "lpd",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set lowpass decay",
|
||||
example: "0.1 lpd",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "lps",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set lowpass sustain",
|
||||
example: "0.5 lps",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "lpr",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set lowpass release",
|
||||
example: "0.3 lpr",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hpf",
|
||||
aliases: &[],
|
||||
@@ -154,6 +234,56 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hpe",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set highpass envelope",
|
||||
example: "0.5 hpe",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hpa",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set highpass attack",
|
||||
example: "0.01 hpa",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hpd",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set highpass decay",
|
||||
example: "0.1 hpd",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hps",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set highpass sustain",
|
||||
example: "0.5 hps",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "hpr",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set highpass release",
|
||||
example: "0.3 hpr",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bpf",
|
||||
aliases: &[],
|
||||
@@ -174,6 +304,56 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bpe",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set bandpass envelope",
|
||||
example: "0.5 bpe",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bpa",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set bandpass attack",
|
||||
example: "0.01 bpa",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bpd",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set bandpass decay",
|
||||
example: "0.1 bpd",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bps",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set bandpass sustain",
|
||||
example: "0.5 bps",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "bpr",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set bandpass release",
|
||||
example: "0.3 bpr",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "llpf",
|
||||
aliases: &[],
|
||||
@@ -274,36 +454,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "eqlofreq",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set low shelf frequency (Hz)",
|
||||
example: "400 eqlofreq",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "eqmidfreq",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set mid peak frequency (Hz)",
|
||||
example: "2000 eqmidfreq",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "eqhifreq",
|
||||
aliases: &[],
|
||||
category: "Filter",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set high shelf frequency (Hz)",
|
||||
example: "8000 eqhifreq",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "tilt",
|
||||
aliases: &[],
|
||||
@@ -809,45 +959,4 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
// Compressor
|
||||
Word {
|
||||
name: "comp",
|
||||
aliases: &[],
|
||||
category: "Compressor",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set sidechain duck amount (0-1)",
|
||||
example: "0.8 comp",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "compattack",
|
||||
aliases: &["cattack"],
|
||||
category: "Compressor",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set compressor attack time in seconds",
|
||||
example: "0.01 compattack",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "comprelease",
|
||||
aliases: &["crelease"],
|
||||
category: "Compressor",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set compressor release time in seconds",
|
||||
example: "0.15 comprelease",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "comporbit",
|
||||
aliases: &["corbit"],
|
||||
category: "Compressor",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set sidechain source orbit",
|
||||
example: "0 comporbit",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! MIDI word definitions: channel, CC, pitch bend, transport, and device routing.
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// MIDI
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "chan",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Built-in word definitions and lookup for the Forth VM.
|
||||
|
||||
mod compile;
|
||||
mod core;
|
||||
mod effects;
|
||||
@@ -13,7 +11,6 @@ use std::sync::LazyLock;
|
||||
|
||||
pub(crate) use compile::compile_word;
|
||||
|
||||
/// How a word is compiled into ops.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum WordCompile {
|
||||
Simple,
|
||||
@@ -22,7 +19,6 @@ pub enum WordCompile {
|
||||
Probability(f64),
|
||||
}
|
||||
|
||||
/// Metadata for a built-in Forth word.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Word {
|
||||
pub name: &'static str,
|
||||
@@ -35,7 +31,6 @@ pub struct Word {
|
||||
pub varargs: bool,
|
||||
}
|
||||
|
||||
/// All built-in words, aggregated from every category module.
|
||||
pub static WORDS: LazyLock<Vec<Word>> = LazyLock::new(|| {
|
||||
let mut words = Vec::new();
|
||||
words.extend_from_slice(self::core::WORDS);
|
||||
@@ -47,7 +42,6 @@ pub static WORDS: LazyLock<Vec<Word>> = LazyLock::new(|| {
|
||||
words
|
||||
});
|
||||
|
||||
/// Index mapping word names and aliases to their definitions.
|
||||
static WORD_MAP: LazyLock<HashMap<&'static str, &'static Word>> = LazyLock::new(|| {
|
||||
let mut map = HashMap::with_capacity(WORDS.len() * 2);
|
||||
for word in WORDS.iter() {
|
||||
@@ -59,7 +53,6 @@ static WORD_MAP: LazyLock<HashMap<&'static str, &'static Word>> = LazyLock::new(
|
||||
map
|
||||
});
|
||||
|
||||
/// Find a word by name or alias.
|
||||
pub fn lookup_word(name: &str) -> Option<&'static Word> {
|
||||
WORD_MAP.get(name).copied()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Word definitions for music theory, harmony, and chord construction.
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// Music, Chord
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
// Music
|
||||
Word {
|
||||
@@ -24,100 +23,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// Harmony
|
||||
Word {
|
||||
name: "key!",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(root --)",
|
||||
desc: "Set tonal center for scale operations",
|
||||
example: "g3 key! 0 major => 55",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "triad",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(degree -- n1 n2 n3)",
|
||||
desc: "Diatonic triad from scale degree (follows a scale word)",
|
||||
example: "0 major triad => 60 64 67",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "seventh",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(degree -- n1 n2 n3 n4)",
|
||||
desc: "Diatonic seventh from scale degree (follows a scale word)",
|
||||
example: "0 major seventh => 60 64 67 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chord voicings
|
||||
Word {
|
||||
name: "inv",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c.. -- b c.. a+12)",
|
||||
desc: "Inversion: bottom note moves up an octave",
|
||||
example: "c4 maj inv => 64 67 72",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "dinv",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b.. z -- z-12 a b..)",
|
||||
desc: "Down inversion: top note moves down an octave",
|
||||
example: "c4 maj dinv => 55 60 64",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "drop2",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c d -- b-12 a c d)",
|
||||
desc: "Drop-2 voicing: 2nd from top moves down an octave",
|
||||
example: "c4 maj7 drop2 => 55 60 64 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "drop3",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c d -- c-12 a b d)",
|
||||
desc: "Drop-3 voicing: 3rd from top moves down an octave",
|
||||
example: "c4 maj7 drop3 => 52 60 67 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Transpose
|
||||
Word {
|
||||
name: "tp",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(n --)",
|
||||
desc: "Transpose all ints on stack by N semitones",
|
||||
example: "c4 maj 3 tp => 63 67 70",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Triads
|
||||
Word {
|
||||
name: "pwr",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fifth)",
|
||||
desc: "Power chord",
|
||||
example: "c4 pwr => 60 67",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj",
|
||||
aliases: &[],
|
||||
@@ -249,36 +155,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "augmaj7",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh)",
|
||||
desc: "Augmented major 7th",
|
||||
example: "c4 augmaj7 => 60 64 68 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "7sus4",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fourth fifth seventh)",
|
||||
desc: "Dominant 7 sus4",
|
||||
example: "c4 7sus4 => 60 65 67 70",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "9sus4",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fourth fifth seventh ninth)",
|
||||
desc: "9 sus4",
|
||||
example: "c4 9sus4 => 60 65 67 70 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Sixth
|
||||
Word {
|
||||
name: "maj6",
|
||||
@@ -300,26 +176,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj69",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth sixth ninth)",
|
||||
desc: "Major 6/9",
|
||||
example: "c4 maj69 => 60 64 67 69 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min69",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth sixth ninth)",
|
||||
desc: "Minor 6/9",
|
||||
example: "c4 min69 => 60 63 67 69 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Extended
|
||||
Word {
|
||||
name: "dom9",
|
||||
@@ -361,16 +217,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj11",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth eleventh)",
|
||||
desc: "Major 11th",
|
||||
example: "c4 maj11 => 60 64 67 71 74 77",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min11",
|
||||
aliases: &[],
|
||||
@@ -391,26 +237,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj13",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth thirteenth)",
|
||||
desc: "Major 13th",
|
||||
example: "c4 maj13 => 60 64 67 71 74 81",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min13",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth thirteenth)",
|
||||
desc: "Minor 13th",
|
||||
example: "c4 min13 => 60 63 67 70 74 81",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Add
|
||||
Word {
|
||||
name: "add9",
|
||||
@@ -483,14 +309,4 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "dom7s11",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh sharpelev)",
|
||||
desc: "7th sharp 11 (lydian dominant)",
|
||||
example: "c4 dom7s11 => 60 64 67 70 78",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Word metadata for sequencing: probability, timing, context queries, generators.
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// Time, Context, Probability, Generator, Desktop
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
// Probability
|
||||
Word {
|
||||
@@ -60,7 +59,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot prob --)",
|
||||
desc: "Execute quotation with probability (0.0-1.0)",
|
||||
example: "( 2 distort ) 0.75 chance",
|
||||
example: "{ 2 distort } 0.75 chance",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -70,7 +69,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot pct --)",
|
||||
desc: "Execute quotation with probability (0-100)",
|
||||
example: "( 2 distort ) 75 prob",
|
||||
example: "{ 2 distort } 75 prob",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -114,26 +113,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "pbounce",
|
||||
aliases: &[],
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n -- selected)",
|
||||
desc: "Ping-pong cycle through n items by pattern iteration",
|
||||
example: "60 64 67 72 4 pbounce",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "index",
|
||||
aliases: &[],
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n idx -- selected)",
|
||||
desc: "Select item at explicit index",
|
||||
example: "[ c4 e4 g4 ] step index",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "wchoose",
|
||||
aliases: &[],
|
||||
@@ -150,7 +129,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Always execute quotation",
|
||||
example: "( 2 distort ) always",
|
||||
example: "{ 2 distort } always",
|
||||
compile: Probability(1.0),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -160,7 +139,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Never execute quotation",
|
||||
example: "( 2 distort ) never",
|
||||
example: "{ 2 distort } never",
|
||||
compile: Probability(0.0),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -170,7 +149,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation 75% of the time",
|
||||
example: "( 2 distort ) often",
|
||||
example: "{ 2 distort } often",
|
||||
compile: Probability(0.75),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -180,7 +159,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation 50% of the time",
|
||||
example: "( 2 distort ) sometimes",
|
||||
example: "{ 2 distort } sometimes",
|
||||
compile: Probability(0.5),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -190,7 +169,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation 25% of the time",
|
||||
example: "( 2 distort ) rarely",
|
||||
example: "{ 2 distort } rarely",
|
||||
compile: Probability(0.25),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -200,7 +179,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation 10% of the time",
|
||||
example: "( 2 distort ) almostNever",
|
||||
example: "{ 2 distort } almostNever",
|
||||
compile: Probability(0.1),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -210,7 +189,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Probability",
|
||||
stack: "(quot --)",
|
||||
desc: "Execute quotation 90% of the time",
|
||||
example: "( 2 distort ) almostAlways",
|
||||
example: "{ 2 distort } almostAlways",
|
||||
compile: Probability(0.9),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -221,37 +200,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Time",
|
||||
stack: "(quot n --)",
|
||||
desc: "Execute quotation every nth iteration",
|
||||
example: "( 2 distort ) 4 every",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "except",
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(quot n --)",
|
||||
desc: "Execute quotation on all iterations except every nth",
|
||||
example: "( 2 distort ) 4 except",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "every+",
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(quot n offset --)",
|
||||
desc: "Execute quotation every nth iteration with phase offset",
|
||||
example: "( snare ) 4 2 every+ => fires at iter 2, 6, 10...",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "except+",
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(quot n offset --)",
|
||||
desc: "Skip quotation every nth iteration with phase offset",
|
||||
example: "( snare ) 4 2 except+ => skips at iter 2, 6, 10...",
|
||||
example: "{ 2 distort } 4 every",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -261,7 +210,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Time",
|
||||
stack: "(quot k n --)",
|
||||
desc: "Execute quotation using Euclidean distribution over step runs",
|
||||
example: "( 2 distort ) 3 8 bjork",
|
||||
example: "{ 2 distort } 3 8 bjork",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -271,7 +220,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Time",
|
||||
stack: "(quot k n --)",
|
||||
desc: "Execute quotation using Euclidean distribution over pattern iterations",
|
||||
example: "( 2 distort ) 3 8 pbjork",
|
||||
example: "{ 2 distort } 3 8 pbjork",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -280,8 +229,8 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(n --)",
|
||||
desc: "Fit sample to n steps",
|
||||
example: "\"break\" s 16 loop @",
|
||||
desc: "Fit sample to n beats",
|
||||
example: "\"break\" s 4 loop @",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
@@ -305,13 +254,23 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "chain",
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(bank pattern --)",
|
||||
desc: "Chain to bank/pattern (1-indexed) when current pattern ends",
|
||||
example: "1 4 chain",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "at",
|
||||
aliases: &[],
|
||||
category: "Time",
|
||||
stack: "(v1..vn -- )",
|
||||
desc: "Looping block: re-executes body per delta. Close with . (audio), m. (MIDI), or done (no emit)",
|
||||
example: "0 0.5 at kick snd 1 2 rand freq . | 0 0.5 at 60 note m. | 0 0.5 at !x done",
|
||||
stack: "(v1..vn --)",
|
||||
desc: "Set delta context for emit timing",
|
||||
example: "0 0.5 at kick s . => emits at 0 and 0.5 of step",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
@@ -456,7 +415,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Desktop",
|
||||
stack: "(-- bool)",
|
||||
desc: "1 when mouse button held, 0 otherwise",
|
||||
example: "mdown ( \"crash\" s . ) ?",
|
||||
example: "mdown { \"crash\" s . } ?",
|
||||
compile: Context("mdown"),
|
||||
varargs: false,
|
||||
},
|
||||
@@ -487,7 +446,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
category: "Generator",
|
||||
stack: "(quot n -- results...)",
|
||||
desc: "Execute quotation n times, push all results",
|
||||
example: "( 1 6 rand ) 4 gen => 4 random values",
|
||||
example: "{ 1 6 rand } 4 gen => 4 random values",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! Word metadata for sound commands, sample/oscillator params, FM, modulation, and LFO.
|
||||
|
||||
use super::{Word, WordCompile::*};
|
||||
|
||||
// Sound, Oscillator, Sample, Wavetable, FM, Modulation, LFO
|
||||
pub(super) const WORDS: &[Word] = &[
|
||||
// Sound
|
||||
Word {
|
||||
name: "sound",
|
||||
aliases: &["snd"],
|
||||
aliases: &["s"],
|
||||
category: "Sound",
|
||||
stack: "(name --)",
|
||||
desc: "Begin sound command",
|
||||
@@ -24,6 +23,16 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "arp",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(v1..vn -- arplist)",
|
||||
desc: "Wrap stack values as arpeggio list for spreading across deltas",
|
||||
example: "c4 e4 g4 b4 arp note => arpeggio",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "clear",
|
||||
aliases: &[],
|
||||
@@ -34,67 +43,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "all",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(--)",
|
||||
desc: "Apply current params to all sounds",
|
||||
example: "500 lpf 0.5 verb all",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "noall",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(--)",
|
||||
desc: "Clear global params",
|
||||
example: "noall",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// Recording
|
||||
Word {
|
||||
name: "rec",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(name --)",
|
||||
desc: "Toggle recording audio output to named sample",
|
||||
example: "\"loop1\" rec",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "overdub",
|
||||
aliases: &["dub"],
|
||||
category: "Sound",
|
||||
stack: "(name --)",
|
||||
desc: "Toggle overdub recording onto existing named sample",
|
||||
example: "\"loop1\" overdub",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "orec",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(name orbit --)",
|
||||
desc: "Toggle recording a single orbit into named sample",
|
||||
example: "\"drums\" 0 orec",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "odub",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(name orbit --)",
|
||||
desc: "Toggle overdub recording a single orbit onto named sample",
|
||||
example: "\"drums\" 0 odub",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// Sample
|
||||
Word {
|
||||
name: "bank",
|
||||
@@ -116,12 +64,22 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "repeat",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set repeat count",
|
||||
example: "4 repeat",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "dur",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set MIDI note duration (for audio, use gate)",
|
||||
desc: "Set duration",
|
||||
example: "0.5 dur",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
@@ -131,7 +89,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set gate duration (total note length, 0 = infinite sustain)",
|
||||
desc: "Set gate time",
|
||||
example: "0.8 gate",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
@@ -146,16 +104,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "stretch",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Time stretch factor (pitch-independent)",
|
||||
example: "2 stretch",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "begin",
|
||||
aliases: &[],
|
||||
@@ -176,26 +124,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "slice",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Divide sample into N equal slices",
|
||||
example: r#""break" s 8 slice 3 pick ."#,
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "pick",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Select which slice to play (0-indexed, wraps)",
|
||||
example: r#""break" s 8 slice 3 pick ."#,
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "voice",
|
||||
aliases: &[],
|
||||
@@ -226,16 +154,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "inchan",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Select input channel for live input (0-indexed)",
|
||||
example: "0 inchan",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "cut",
|
||||
aliases: &[],
|
||||
@@ -277,6 +195,16 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "glide",
|
||||
aliases: &[],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set glide/portamento",
|
||||
example: "0.1 glide",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "pw",
|
||||
aliases: &[],
|
||||
@@ -287,16 +215,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "wave",
|
||||
aliases: &["waveform"],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set drum waveform [0,1]: 0=sine, 0.5=tri, 1=saw",
|
||||
example: "0.5 wave",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "spread",
|
||||
aliases: &[],
|
||||
@@ -342,7 +260,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set harmonics (add source)",
|
||||
desc: "Set harmonics (mutable only)",
|
||||
example: "4 harmonics",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
@@ -352,7 +270,7 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set timbre (add source)",
|
||||
desc: "Set timbre (mutable only)",
|
||||
example: "0.5 timbre",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
@@ -362,21 +280,11 @@ pub(super) const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set morph (add source)",
|
||||
desc: "Set morph (mutable only)",
|
||||
example: "0.5 morph",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "partials",
|
||||
aliases: &[],
|
||||
category: "Oscillator",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set number of active harmonics (add source only)",
|
||||
example: "16 partials",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "coarse",
|
||||
aliases: &[],
|
||||
@@ -448,6 +356,36 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "scanlfo",
|
||||
aliases: &[],
|
||||
category: "Wavetable",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set scan LFO rate (Hz)",
|
||||
example: "0.2 scanlfo",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "scandepth",
|
||||
aliases: &[],
|
||||
category: "Wavetable",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set scan LFO depth (0-1)",
|
||||
example: "0.4 scandepth",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "scanshape",
|
||||
aliases: &[],
|
||||
category: "Wavetable",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set scan LFO shape (sine/tri/saw/square/sh)",
|
||||
example: "\"tri\" scanshape",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
// FM
|
||||
Word {
|
||||
name: "fm",
|
||||
@@ -479,6 +417,56 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fme",
|
||||
aliases: &[],
|
||||
category: "FM",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set FM envelope",
|
||||
example: "0.5 fme",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fma",
|
||||
aliases: &[],
|
||||
category: "FM",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set FM attack",
|
||||
example: "0.01 fma",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fmd",
|
||||
aliases: &[],
|
||||
category: "FM",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set FM decay",
|
||||
example: "0.1 fmd",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fms",
|
||||
aliases: &[],
|
||||
category: "FM",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set FM sustain",
|
||||
example: "0.5 fms",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fmr",
|
||||
aliases: &[],
|
||||
category: "FM",
|
||||
stack: "(v.. --)",
|
||||
desc: "Set FM release",
|
||||
example: "0.1 fmr",
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "fm2",
|
||||
aliases: &[],
|
||||
@@ -752,36 +740,6 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "islide",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(start end dur -- str)",
|
||||
desc: "Swell transition (slow start, fast finish): start>end:duri",
|
||||
example: "200 4000 1 islide lpf",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "oslide",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(start end dur -- str)",
|
||||
desc: "Pluck transition (fast attack, slow settle): start>end:duro",
|
||||
example: "0 1 0.5 oslide gain",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "pslide",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(start end dur -- str)",
|
||||
desc: "Stair transition (8 discrete steps): start>end:durp",
|
||||
example: "0 1 2 pslide gain",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "jit",
|
||||
aliases: &[],
|
||||
@@ -812,53 +770,13 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "ead",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(min max a d -- str)",
|
||||
desc: "Percussive envelope mod: min^max:a:d:0:0",
|
||||
example: "200 8000 0.01 0.1 ead lpf",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "eadr",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(min max a d r -- str)",
|
||||
desc: "Percussive envelope mod with release: min^max:a:d:0:r",
|
||||
example: "200 8000 0.01 0.1 0.3 eadr lpf",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "eadsr",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(min max a d s r -- str)",
|
||||
desc: "ADSR envelope mod: min^max:a:d:s:r",
|
||||
example: "200 8000 0.01 0.1 0.5 0.3 eadsr lpf",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "env",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(min max a d s r -- str)",
|
||||
desc: "DAHDSR envelope modulation: min^max:a:d:s:r",
|
||||
example: "200 8000 0.01 0.1 0.5 0.3 env lpf",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "lpg",
|
||||
aliases: &[],
|
||||
category: "Audio Modulation",
|
||||
stack: "(min max depth --)",
|
||||
desc: "Low pass gate: pairs amp envelope with lpf modulation",
|
||||
example: "0.01 0.1 ad 200 8000 1 lpg .",
|
||||
stack: "(start t1 d1 ... -- str)",
|
||||
desc: "Multi-segment envelope: start>t1:d1>...",
|
||||
example: "0 1 0.01 0.7 0.1 0 2 env gain",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# cagire-markdown
|
||||
|
||||
Markdown parser and renderer that produces ratatui-styled lines. Used for the built-in help/documentation views.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `parser` | Markdown-to-styled-lines conversion |
|
||||
| `highlighter` | `CodeHighlighter` trait for syntax highlighting in fenced code blocks |
|
||||
| `theme` | Color mappings for markdown elements |
|
||||
|
||||
## Key Trait
|
||||
|
||||
- **`CodeHighlighter`** — Implement to provide language-specific syntax highlighting. Returns `Vec<(Style, String)>` per line.
|
||||
@@ -1,13 +1,9 @@
|
||||
//! Syntax highlighting trait for fenced code blocks in markdown.
|
||||
|
||||
use ratatui::style::Style;
|
||||
|
||||
/// Produce styled spans from a single line of source code.
|
||||
pub trait CodeHighlighter {
|
||||
fn highlight(&self, line: &str) -> Vec<(Style, String)>;
|
||||
}
|
||||
|
||||
/// Pass-through highlighter that applies no styling.
|
||||
pub struct NoHighlight;
|
||||
|
||||
impl CodeHighlighter for NoHighlight {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Parse markdown into styled ratatui lines with pluggable syntax highlighting.
|
||||
|
||||
mod highlighter;
|
||||
mod parser;
|
||||
mod theme;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Parse markdown text into styled ratatui lines with syntax-highlighted code blocks.
|
||||
|
||||
use minimad::{Composite, CompositeStyle, Compound, Line, TableRow};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
@@ -7,20 +5,17 @@ use ratatui::text::{Line as RLine, Span};
|
||||
use crate::highlighter::CodeHighlighter;
|
||||
use crate::theme::MarkdownTheme;
|
||||
|
||||
/// Span of lines within a parsed document that form a fenced code block.
|
||||
pub struct CodeBlock {
|
||||
pub start_line: usize,
|
||||
pub end_line: usize,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
/// Result of parsing a markdown string: styled lines and extracted code blocks.
|
||||
pub struct ParsedMarkdown {
|
||||
pub lines: Vec<RLine<'static>>,
|
||||
pub code_blocks: Vec<CodeBlock>,
|
||||
}
|
||||
|
||||
/// Parse markdown text into themed, syntax-highlighted ratatui lines.
|
||||
pub fn parse<T: MarkdownTheme, H: CodeHighlighter>(
|
||||
md: &str,
|
||||
theme: &T,
|
||||
@@ -49,7 +44,7 @@ pub fn parse<T: MarkdownTheme, H: CodeHighlighter>(
|
||||
let close_block = |start: Option<usize>,
|
||||
source: &mut Vec<String>,
|
||||
blocks: &mut Vec<CodeBlock>,
|
||||
lines: &[RLine<'static>]| {
|
||||
lines: &Vec<RLine<'static>>| {
|
||||
if let Some(start) = start {
|
||||
blocks.push(CodeBlock {
|
||||
start_line: start,
|
||||
@@ -123,7 +118,7 @@ pub fn parse<T: MarkdownTheme, H: CodeHighlighter>(
|
||||
ParsedMarkdown { lines, code_blocks }
|
||||
}
|
||||
|
||||
fn preprocess_markdown(md: &str) -> String {
|
||||
pub fn preprocess_markdown(md: &str) -> String {
|
||||
let mut out = String::with_capacity(md.len());
|
||||
for line in md.lines() {
|
||||
let line = convert_dash_lists(line);
|
||||
@@ -167,7 +162,7 @@ fn preprocess_markdown(md: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
fn convert_dash_lists(line: &str) -> String {
|
||||
pub fn convert_dash_lists(line: &str) -> String {
|
||||
let trimmed = line.trim_start();
|
||||
if let Some(rest) = trimmed.strip_prefix("- ") {
|
||||
let indent = line.len() - trimmed.len();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
//! Style provider trait for markdown rendering.
|
||||
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
/// Style provider for each markdown element type.
|
||||
pub trait MarkdownTheme {
|
||||
fn h1(&self) -> Style;
|
||||
fn h2(&self) -> Style;
|
||||
@@ -19,7 +16,6 @@ pub trait MarkdownTheme {
|
||||
fn table_row_odd(&self) -> Color;
|
||||
}
|
||||
|
||||
/// Fallback theme with hardcoded terminal colors, used in tests.
|
||||
pub struct DefaultTheme;
|
||||
|
||||
impl MarkdownTheme for DefaultTheme {
|
||||
|
||||
@@ -10,9 +10,3 @@ description = "Project data structures for cagire sequencer"
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
rmp-serde = "1"
|
||||
brotli = "7"
|
||||
base64 = "0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
flate2 = "1"
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# cagire-project
|
||||
|
||||
Project data model and persistence for Cagire.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `project` | `Project`, `Bank`, `Pattern`, `Step` structs and constants |
|
||||
| `file` | File I/O (save/load) |
|
||||
| `share` | Project sharing/export |
|
||||
|
||||
## Key Types
|
||||
|
||||
- **`Project`** — Top-level container: banks of patterns
|
||||
- **`Bank`** — Collection of patterns
|
||||
- **`Pattern`** — Sequence of steps with metadata
|
||||
- **`Step`** — Single step holding a Forth script
|
||||
|
||||
## Constants
|
||||
|
||||
`MAX_BANKS=32`, `MAX_PATTERNS=32`, `MAX_STEPS=1024`
|
||||
@@ -1,17 +1,15 @@
|
||||
//! JSON-based project file persistence with versioned format.
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::project::{Bank, PatternSpeed, Project};
|
||||
use crate::project::{Bank, Project};
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
const EXTENSION: &str = "cagire";
|
||||
pub const EXTENSION: &str = "cagire";
|
||||
|
||||
fn ensure_extension(path: &Path) -> PathBuf {
|
||||
pub fn ensure_extension(path: &Path) -> PathBuf {
|
||||
if path.extension().map(|e| e == EXTENSION).unwrap_or(false) {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
@@ -31,24 +29,6 @@ struct ProjectFile {
|
||||
playing_patterns: Vec<(usize, usize)>,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
prelude: String,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
script: String,
|
||||
#[serde(default, skip_serializing_if = "is_default_speed")]
|
||||
script_speed: PatternSpeed,
|
||||
#[serde(default = "default_script_length", skip_serializing_if = "is_default_script_length")]
|
||||
script_length: usize,
|
||||
}
|
||||
|
||||
fn is_default_speed(s: &PatternSpeed) -> bool {
|
||||
*s == PatternSpeed::default()
|
||||
}
|
||||
|
||||
fn default_script_length() -> usize {
|
||||
16
|
||||
}
|
||||
|
||||
fn is_default_script_length(n: &usize) -> bool {
|
||||
*n == default_script_length()
|
||||
}
|
||||
|
||||
fn default_tempo() -> f64 {
|
||||
@@ -64,9 +44,6 @@ impl From<&Project> for ProjectFile {
|
||||
tempo: project.tempo,
|
||||
playing_patterns: project.playing_patterns.clone(),
|
||||
prelude: project.prelude.clone(),
|
||||
script: project.script.clone(),
|
||||
script_speed: project.script_speed,
|
||||
script_length: project.script_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,16 +56,12 @@ impl From<ProjectFile> for Project {
|
||||
tempo: file.tempo,
|
||||
playing_patterns: file.playing_patterns,
|
||||
prelude: file.prelude,
|
||||
script: file.script,
|
||||
script_speed: file.script_speed,
|
||||
script_length: file.script_length,
|
||||
};
|
||||
project.normalize();
|
||||
project
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by project save/load operations.
|
||||
#[derive(Debug)]
|
||||
pub enum FileError {
|
||||
Io(io::Error),
|
||||
@@ -118,7 +91,6 @@ impl From<serde_json::Error> for FileError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a project to disk as pretty-printed JSON, returning the final path.
|
||||
pub fn save(project: &Project, path: &Path) -> Result<PathBuf, FileError> {
|
||||
let path = ensure_extension(path);
|
||||
let file = ProjectFile::from(project);
|
||||
@@ -127,15 +99,9 @@ pub fn save(project: &Project, path: &Path) -> Result<PathBuf, FileError> {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Read a project from a `.cagire` file on disk.
|
||||
pub fn load(path: &Path) -> Result<Project, FileError> {
|
||||
let json = fs::read_to_string(path)?;
|
||||
load_str(&json)
|
||||
}
|
||||
|
||||
/// Parse a project from a JSON string.
|
||||
pub fn load_str(json: &str) -> Result<Project, FileError> {
|
||||
let file: ProjectFile = serde_json::from_str(json)?;
|
||||
let file: ProjectFile = serde_json::from_str(&json)?;
|
||||
if file.version > VERSION {
|
||||
return Err(FileError::Version(file.version));
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
//! Project data model: banks, patterns, and steps for the Cagire sequencer.
|
||||
|
||||
mod file;
|
||||
mod project;
|
||||
pub mod share;
|
||||
|
||||
/// Maximum number of banks in a project.
|
||||
pub const MAX_BANKS: usize = 32;
|
||||
/// Maximum number of patterns per bank.
|
||||
pub const MAX_PATTERNS: usize = 32;
|
||||
/// Maximum number of steps per pattern.
|
||||
pub const MAX_STEPS: usize = 1024;
|
||||
/// Default pattern length in steps.
|
||||
pub const DEFAULT_LENGTH: usize = 16;
|
||||
|
||||
pub use file::{load, load_str, save, FileError};
|
||||
pub use project::{Bank, FollowUp, LaunchQuantization, Pattern, PatternSpeed, Project, Step};
|
||||
pub use file::{load, save, FileError};
|
||||
pub use project::{Bank, LaunchQuantization, Pattern, PatternSpeed, Project, Step, SyncMode};
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
//! Project, Bank, Pattern, and Step structs with serialization.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS};
|
||||
|
||||
/// Speed multiplier for a pattern, expressed as a rational fraction.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PatternSpeed {
|
||||
pub num: u8,
|
||||
@@ -38,12 +35,10 @@ impl PatternSpeed {
|
||||
Self::OCTO,
|
||||
];
|
||||
|
||||
/// Return the speed as a floating-point multiplier.
|
||||
pub fn multiplier(&self) -> f64 {
|
||||
self.num as f64 / self.denom as f64
|
||||
}
|
||||
|
||||
/// Format as a human-readable label (e.g. "2x", "1/4x").
|
||||
pub fn label(&self) -> String {
|
||||
if self.denom == 1 {
|
||||
format!("{}x", self.num)
|
||||
@@ -52,7 +47,6 @@ impl PatternSpeed {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the next faster preset, or self if already at maximum.
|
||||
pub fn next(&self) -> Self {
|
||||
let current = self.multiplier();
|
||||
Self::PRESETS
|
||||
@@ -62,7 +56,6 @@ impl PatternSpeed {
|
||||
.unwrap_or(*self)
|
||||
}
|
||||
|
||||
/// Return the next slower preset, or self if already at minimum.
|
||||
pub fn prev(&self) -> Self {
|
||||
let current = self.multiplier();
|
||||
Self::PRESETS
|
||||
@@ -73,7 +66,6 @@ impl PatternSpeed {
|
||||
.unwrap_or(*self)
|
||||
}
|
||||
|
||||
/// Parse a speed label like "2x" or "1/4x" into a `PatternSpeed`.
|
||||
pub fn from_label(s: &str) -> Option<Self> {
|
||||
let s = s.trim().trim_end_matches('x');
|
||||
if let Some((num, denom)) = s.split_once('/') {
|
||||
@@ -145,7 +137,6 @@ impl<'de> Deserialize<'de> for PatternSpeed {
|
||||
}
|
||||
}
|
||||
|
||||
/// Quantization grid for launching patterns.
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub enum LaunchQuantization {
|
||||
Immediate,
|
||||
@@ -158,7 +149,6 @@ pub enum LaunchQuantization {
|
||||
}
|
||||
|
||||
impl LaunchQuantization {
|
||||
/// Human-readable label for display.
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Immediate => "Immediate",
|
||||
@@ -170,18 +160,6 @@ impl LaunchQuantization {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Immediate => "Imm",
|
||||
Self::Beat => "Bt",
|
||||
Self::Bar => "1B",
|
||||
Self::Bars2 => "2B",
|
||||
Self::Bars4 => "4B",
|
||||
Self::Bars8 => "8B",
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle to the next longer quantization, clamped at `Bars8`.
|
||||
pub fn next(&self) -> Self {
|
||||
match self {
|
||||
Self::Immediate => Self::Beat,
|
||||
@@ -193,7 +171,6 @@ impl LaunchQuantization {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle to the next shorter quantization, clamped at `Immediate`.
|
||||
pub fn prev(&self) -> Self {
|
||||
match self {
|
||||
Self::Immediate => Self::Immediate,
|
||||
@@ -206,49 +183,29 @@ impl LaunchQuantization {
|
||||
}
|
||||
}
|
||||
|
||||
/// What happens when a pattern finishes: loop, stop, or chain to another.
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub enum FollowUp {
|
||||
pub enum SyncMode {
|
||||
#[default]
|
||||
Loop,
|
||||
Stop,
|
||||
Chain { bank: usize, pattern: usize },
|
||||
Reset,
|
||||
PhaseLock,
|
||||
}
|
||||
|
||||
impl FollowUp {
|
||||
/// Human-readable label for display.
|
||||
impl SyncMode {
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Loop => "Loop",
|
||||
Self::Stop => "Stop",
|
||||
Self::Chain { .. } => "Chain",
|
||||
Self::Reset => "Reset",
|
||||
Self::PhaseLock => "Phase-Lock",
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle forward through follow-up modes.
|
||||
pub fn next_mode(&self) -> Self {
|
||||
pub fn toggle(&self) -> Self {
|
||||
match self {
|
||||
Self::Loop => Self::Stop,
|
||||
Self::Stop => Self::Chain { bank: 0, pattern: 0 },
|
||||
Self::Chain { .. } => Self::Loop,
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle backward through follow-up modes.
|
||||
pub fn prev_mode(&self) -> Self {
|
||||
match self {
|
||||
Self::Loop => Self::Chain { bank: 0, pattern: 0 },
|
||||
Self::Stop => Self::Loop,
|
||||
Self::Chain { .. } => Self::Stop,
|
||||
Self::Reset => Self::PhaseLock,
|
||||
Self::PhaseLock => Self::Reset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_follow_up(f: &FollowUp) -> bool {
|
||||
*f == FollowUp::default()
|
||||
}
|
||||
|
||||
/// Single step in a pattern, holding a Forth script and optional metadata.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Step {
|
||||
pub active: bool,
|
||||
@@ -260,12 +217,10 @@ pub struct Step {
|
||||
}
|
||||
|
||||
impl Step {
|
||||
/// True if all fields are at their default values.
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.active && self.script.is_empty() && self.source.is_none() && self.name.is_none()
|
||||
}
|
||||
|
||||
/// True if the script is non-empty.
|
||||
pub fn has_content(&self) -> bool {
|
||||
!self.script.is_empty()
|
||||
}
|
||||
@@ -282,16 +237,14 @@ impl Default for Step {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sequence of steps with playback settings (speed, quantization, follow-up).
|
||||
#[derive(Clone)]
|
||||
pub struct Pattern {
|
||||
pub steps: Vec<Step>,
|
||||
pub length: usize,
|
||||
pub speed: PatternSpeed,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub quantization: LaunchQuantization,
|
||||
pub follow_up: FollowUp,
|
||||
pub sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -323,18 +276,20 @@ struct SparsePattern {
|
||||
speed: PatternSpeed,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "is_default_quantization")]
|
||||
quantization: LaunchQuantization,
|
||||
#[serde(default, skip_serializing_if = "is_default_follow_up")]
|
||||
follow_up: FollowUp,
|
||||
#[serde(default, skip_serializing_if = "is_default_sync_mode")]
|
||||
sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
fn is_default_quantization(q: &LaunchQuantization) -> bool {
|
||||
*q == LaunchQuantization::default()
|
||||
}
|
||||
|
||||
fn is_default_sync_mode(s: &SyncMode) -> bool {
|
||||
*s == SyncMode::default()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LegacyPattern {
|
||||
steps: Vec<Step>,
|
||||
@@ -344,11 +299,9 @@ struct LegacyPattern {
|
||||
#[serde(default)]
|
||||
name: Option<String>,
|
||||
#[serde(default)]
|
||||
description: Option<String>,
|
||||
#[serde(default)]
|
||||
quantization: LaunchQuantization,
|
||||
#[serde(default)]
|
||||
follow_up: FollowUp,
|
||||
sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
impl Serialize for Pattern {
|
||||
@@ -372,9 +325,8 @@ impl Serialize for Pattern {
|
||||
length: self.length,
|
||||
speed: self.speed,
|
||||
name: self.name.clone(),
|
||||
description: self.description.clone(),
|
||||
quantization: self.quantization,
|
||||
follow_up: self.follow_up,
|
||||
sync_mode: self.sync_mode,
|
||||
};
|
||||
sparse.serialize(serializer)
|
||||
}
|
||||
@@ -407,9 +359,8 @@ impl<'de> Deserialize<'de> for Pattern {
|
||||
length: sparse.length,
|
||||
speed: sparse.speed,
|
||||
name: sparse.name,
|
||||
description: sparse.description,
|
||||
quantization: sparse.quantization,
|
||||
follow_up: sparse.follow_up,
|
||||
sync_mode: sparse.sync_mode,
|
||||
})
|
||||
}
|
||||
PatternFormat::Legacy(legacy) => Ok(Pattern {
|
||||
@@ -417,9 +368,8 @@ impl<'de> Deserialize<'de> for Pattern {
|
||||
length: legacy.length,
|
||||
speed: legacy.speed,
|
||||
name: legacy.name,
|
||||
description: legacy.description,
|
||||
quantization: legacy.quantization,
|
||||
follow_up: legacy.follow_up,
|
||||
sync_mode: legacy.sync_mode,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -432,25 +382,21 @@ impl Default for Pattern {
|
||||
length: DEFAULT_LENGTH,
|
||||
speed: PatternSpeed::default(),
|
||||
name: None,
|
||||
description: None,
|
||||
quantization: LaunchQuantization::default(),
|
||||
follow_up: FollowUp::default(),
|
||||
sync_mode: SyncMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// Borrow a step by index.
|
||||
pub fn step(&self, index: usize) -> Option<&Step> {
|
||||
self.steps.get(index)
|
||||
}
|
||||
|
||||
/// Mutably borrow a step by index.
|
||||
pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> {
|
||||
self.steps.get_mut(index)
|
||||
}
|
||||
|
||||
/// Set the active length, clamped to `[1, MAX_STEPS]`.
|
||||
pub fn set_length(&mut self, length: usize) {
|
||||
let length = length.clamp(1, MAX_STEPS);
|
||||
while self.steps.len() < length {
|
||||
@@ -459,7 +405,6 @@ impl Pattern {
|
||||
self.length = length;
|
||||
}
|
||||
|
||||
/// Follow the source chain from `index` to find the originating step.
|
||||
pub fn resolve_source(&self, index: usize) -> usize {
|
||||
let mut current = index;
|
||||
for _ in 0..self.steps.len() {
|
||||
@@ -476,39 +421,17 @@ impl Pattern {
|
||||
index
|
||||
}
|
||||
|
||||
/// Return the script at the resolved source of `index`.
|
||||
pub fn resolve_script(&self, index: usize) -> Option<&str> {
|
||||
let source_idx = self.resolve_source(index);
|
||||
self.steps.get(source_idx).map(|s| s.script.as_str())
|
||||
}
|
||||
|
||||
/// Count active-length steps that have a script or a source reference.
|
||||
pub fn content_step_count(&self) -> usize {
|
||||
self.steps[..self.length]
|
||||
.iter()
|
||||
.filter(|s| s.has_content() || s.source.is_some())
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Collection of patterns forming a bank.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Bank {
|
||||
pub patterns: Vec<Pattern>,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub prelude: String,
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
/// Count patterns that contain at least one non-empty step.
|
||||
pub fn content_pattern_count(&self) -> usize {
|
||||
self.patterns
|
||||
.iter()
|
||||
.filter(|p| p.content_step_count() > 0)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bank {
|
||||
@@ -516,12 +439,10 @@ impl Default for Bank {
|
||||
Self {
|
||||
patterns: (0..MAX_PATTERNS).map(|_| Pattern::default()).collect(),
|
||||
name: None,
|
||||
prelude: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Top-level project: banks, tempo, sample paths, and prelude script.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
pub banks: Vec<Bank>,
|
||||
@@ -533,22 +454,12 @@ pub struct Project {
|
||||
pub playing_patterns: Vec<(usize, usize)>,
|
||||
#[serde(default)]
|
||||
pub prelude: String,
|
||||
#[serde(default)]
|
||||
pub script: String,
|
||||
#[serde(default)]
|
||||
pub script_speed: PatternSpeed,
|
||||
#[serde(default = "default_script_length")]
|
||||
pub script_length: usize,
|
||||
}
|
||||
|
||||
fn default_tempo() -> f64 {
|
||||
120.0
|
||||
}
|
||||
|
||||
fn default_script_length() -> usize {
|
||||
16
|
||||
}
|
||||
|
||||
impl Default for Project {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -557,25 +468,19 @@ impl Default for Project {
|
||||
tempo: default_tempo(),
|
||||
playing_patterns: Vec::new(),
|
||||
prelude: String::new(),
|
||||
script: String::new(),
|
||||
script_speed: PatternSpeed::default(),
|
||||
script_length: default_script_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Project {
|
||||
/// Borrow a pattern by bank and pattern index.
|
||||
pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern {
|
||||
&self.banks[bank].patterns[pattern]
|
||||
}
|
||||
|
||||
/// Mutably borrow a pattern by bank and pattern index.
|
||||
pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern {
|
||||
&mut self.banks[bank].patterns[pattern]
|
||||
}
|
||||
|
||||
/// Pad banks, patterns, and steps to their maximum sizes after deserialization.
|
||||
pub fn normalize(&mut self) {
|
||||
self.banks.resize_with(MAX_BANKS, Bank::default);
|
||||
for bank in &mut self.banks {
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
//! Pattern and project sharing via compact text strings.
|
||||
//!
|
||||
//! Export: data → MessagePack → Brotli → base64 URL-safe → prefix
|
||||
//! Import: strip prefix → base64 decode → Brotli decompress → MessagePack → data
|
||||
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use base64::Engine;
|
||||
|
||||
use crate::{Bank, Pattern};
|
||||
|
||||
const PATTERN_PREFIX: &str = "cgr:";
|
||||
const BANK_PREFIX: &str = "cgrb:";
|
||||
|
||||
pub enum ImportResult {
|
||||
Pattern(Pattern),
|
||||
Bank(Bank),
|
||||
}
|
||||
|
||||
/// Auto-detect format from the prefix and decode.
|
||||
pub fn import_auto(text: &str) -> Result<ImportResult, ShareError> {
|
||||
// Strip everything non-ASCII — valid share strings are pure ASCII
|
||||
let clean: String = text.chars().filter(|c| c.is_ascii_graphic()).collect();
|
||||
if clean.starts_with(BANK_PREFIX) {
|
||||
Ok(ImportResult::Bank(decode(&clean, BANK_PREFIX)?))
|
||||
} else if clean.starts_with(PATTERN_PREFIX) {
|
||||
Ok(ImportResult::Pattern(decode(&clean, PATTERN_PREFIX)?))
|
||||
} else {
|
||||
Err(ShareError::InvalidPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error during pattern or bank import/export.
|
||||
#[derive(Debug)]
|
||||
pub enum ShareError {
|
||||
InvalidPrefix,
|
||||
Base64(base64::DecodeError),
|
||||
Decompress(std::io::Error),
|
||||
Deserialize(rmp_serde::decode::Error),
|
||||
Serialize(rmp_serde::encode::Error),
|
||||
Compress(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ShareError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidPrefix => write!(f, "missing cgr:/cgrb: prefix"),
|
||||
Self::Base64(e) => write!(f, "base64: {e}"),
|
||||
Self::Decompress(e) => write!(f, "decompress: {e}"),
|
||||
Self::Deserialize(e) => write!(f, "deserialize: {e}"),
|
||||
Self::Serialize(e) => write!(f, "serialize: {e}"),
|
||||
Self::Compress(e) => write!(f, "compress: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compress(data: &[u8]) -> Result<Vec<u8>, ShareError> {
|
||||
let mut output = Vec::new();
|
||||
let params = brotli::enc::BrotliEncoderParams {
|
||||
quality: 11,
|
||||
lgwin: 22,
|
||||
lgblock: 0,
|
||||
..Default::default()
|
||||
};
|
||||
brotli::BrotliCompress(&mut &data[..], &mut output, ¶ms).map_err(ShareError::Compress)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn decompress(data: &[u8]) -> Result<Vec<u8>, ShareError> {
|
||||
let mut output = Vec::new();
|
||||
brotli::BrotliDecompress(&mut &data[..], &mut output).map_err(ShareError::Decompress)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn encode<T: serde::Serialize>(value: &T, prefix: &str) -> Result<String, ShareError> {
|
||||
let packed = rmp_serde::to_vec_named(value).map_err(ShareError::Serialize)?;
|
||||
let compressed = compress(&packed)?;
|
||||
let encoded = URL_SAFE_NO_PAD.encode(&compressed);
|
||||
Ok(format!("{prefix}{encoded}"))
|
||||
}
|
||||
|
||||
fn decode<T: serde::de::DeserializeOwned>(text: &str, prefix: &str) -> Result<T, ShareError> {
|
||||
let text = text.trim();
|
||||
let payload = text.strip_prefix(prefix).ok_or(ShareError::InvalidPrefix)?;
|
||||
// Strip invisible characters that clipboard managers / web copies can inject
|
||||
let clean: String = payload
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_')
|
||||
.collect();
|
||||
let compressed = URL_SAFE_NO_PAD.decode(&clean).map_err(ShareError::Base64)?;
|
||||
let packed = decompress(&compressed)?;
|
||||
rmp_serde::from_slice(&packed).map_err(ShareError::Deserialize)
|
||||
}
|
||||
|
||||
/// Encode a pattern as a shareable `cgr:` string.
|
||||
pub fn export(pattern: &Pattern) -> Result<String, ShareError> {
|
||||
encode(pattern, PATTERN_PREFIX)
|
||||
}
|
||||
|
||||
/// Decode a `cgr:` string back into a pattern.
|
||||
pub fn import(text: &str) -> Result<Pattern, ShareError> {
|
||||
decode(text, PATTERN_PREFIX)
|
||||
}
|
||||
|
||||
/// Encode a bank as a shareable `cgrb:` string.
|
||||
pub fn export_bank(bank: &Bank) -> Result<String, ShareError> {
|
||||
encode(bank, BANK_PREFIX)
|
||||
}
|
||||
|
||||
/// Decode a `cgrb:` string back into a bank.
|
||||
pub fn import_bank(text: &str) -> Result<Bank, ShareError> {
|
||||
decode(text, BANK_PREFIX)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Step;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_empty() {
|
||||
let pattern = Pattern::default();
|
||||
let encoded = export(&pattern).expect("export pattern");
|
||||
assert!(encoded.starts_with("cgr:"));
|
||||
let decoded = import(&encoded).expect("import pattern");
|
||||
assert_eq!(decoded.length, pattern.length);
|
||||
assert_eq!(decoded.steps.len(), pattern.steps.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_with_steps() {
|
||||
let mut pattern = Pattern::default();
|
||||
pattern.steps[0] = Step {
|
||||
active: true,
|
||||
script: "kick 60 note".to_string(),
|
||||
source: None,
|
||||
name: Some("kick".to_string()),
|
||||
};
|
||||
pattern.steps[1] = Step {
|
||||
active: false,
|
||||
script: "snare".to_string(),
|
||||
source: None,
|
||||
name: None,
|
||||
};
|
||||
pattern.steps[3] = Step {
|
||||
active: true,
|
||||
script: String::new(),
|
||||
source: Some(0),
|
||||
name: None,
|
||||
};
|
||||
pattern.length = 8;
|
||||
pattern.name = Some("Test".to_string());
|
||||
|
||||
let encoded = export(&pattern).expect("export pattern");
|
||||
let decoded = import(&encoded).expect("import pattern");
|
||||
|
||||
assert_eq!(decoded.length, 8);
|
||||
assert_eq!(decoded.name.as_deref(), Some("Test"));
|
||||
assert_eq!(decoded.steps[0].script, "kick 60 note");
|
||||
assert_eq!(decoded.steps[0].name.as_deref(), Some("kick"));
|
||||
assert!(!decoded.steps[1].active);
|
||||
assert_eq!(decoded.steps[1].script, "snare");
|
||||
assert_eq!(decoded.steps[3].source, Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_prefix() {
|
||||
assert!(matches!(import("xxx:abc"), Err(ShareError::InvalidPrefix)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_base64() {
|
||||
assert!(import("cgr:not-valid-data").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace_trimming() {
|
||||
let pattern = Pattern::default();
|
||||
let encoded = export(&pattern).expect("export pattern");
|
||||
let padded = format!(" {encoded} \n");
|
||||
let decoded = import(&padded).expect("import padded pattern");
|
||||
assert_eq!(decoded.length, pattern.length);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msgpack_brotli_smaller_than_json_deflate() {
|
||||
let mut pattern = Pattern::default();
|
||||
for i in 0..16 {
|
||||
pattern.steps[i] = Step {
|
||||
active: true,
|
||||
script: format!("kick {i} note 0.5 gate"),
|
||||
source: None,
|
||||
name: Some(format!("step_{i}")),
|
||||
};
|
||||
}
|
||||
pattern.length = 16;
|
||||
|
||||
// Current (msgpack+brotli)
|
||||
let new_encoded = export(&pattern).expect("export pattern");
|
||||
|
||||
// Old pipeline (json+deflate) for comparison
|
||||
use std::io::Write;
|
||||
let json = serde_json::to_vec(&pattern).expect("serialize json");
|
||||
let mut encoder =
|
||||
flate2::write::DeflateEncoder::new(Vec::new(), flate2::Compression::best());
|
||||
encoder.write_all(&json).expect("write to encoder");
|
||||
let old_compressed = encoder.finish().expect("finish encoder");
|
||||
let old_encoded = format!("cgr:{}", URL_SAFE_NO_PAD.encode(&old_compressed));
|
||||
|
||||
assert!(
|
||||
new_encoded.len() < old_encoded.len(),
|
||||
"msgpack+brotli ({}) should be smaller than json+deflate ({})",
|
||||
new_encoded.len(),
|
||||
old_encoded.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_bank() {
|
||||
let mut bank = Bank::default();
|
||||
bank.patterns[0].steps[0] = Step {
|
||||
active: true,
|
||||
script: "kick 60 note".to_string(),
|
||||
source: None,
|
||||
name: Some("kick".to_string()),
|
||||
};
|
||||
bank.patterns[0].length = 8;
|
||||
bank.name = Some("Drums".to_string());
|
||||
|
||||
let encoded = export_bank(&bank).expect("export bank");
|
||||
assert!(encoded.starts_with("cgrb:"));
|
||||
let decoded = import_bank(&encoded).expect("import bank");
|
||||
|
||||
assert_eq!(decoded.name.as_deref(), Some("Drums"));
|
||||
assert_eq!(decoded.patterns[0].length, 8);
|
||||
assert_eq!(decoded.patterns[0].steps[0].script, "kick 60 note");
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,4 @@ description = "TUI components for cagire sequencer"
|
||||
rand = "0.8"
|
||||
ratatui = "0.30"
|
||||
regex = "1"
|
||||
tui-textarea = { git = "https://github.com/phsym/tui-textarea", rev = "e2ec4d3", features = ["search"] }
|
||||
tui-textarea = { git = "https://github.com/phsym/tui-textarea", branch = "main", features = ["search"] }
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# cagire-ratatui
|
||||
|
||||
TUI widget library and theme system for Cagire.
|
||||
|
||||
## Widgets
|
||||
|
||||
`category_list`, `confirm`, `editor`, `file_browser`, `hint_bar`, `lissajous`, `list_select`, `modal`, `nav_minimap`, `props_form`, `sample_browser`, `scope`, `scroll_indicators`, `search_bar`, `section_header`, `sparkles`, `spectrum`, `text_input`, `vu_meter`, `waveform`
|
||||
|
||||
## Theme System
|
||||
|
||||
The `theme/` module provides a palette-based theming system using Oklab color space.
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `mod` | `THEMES` array, `CURRENT_THEME` thread-local, `get()`/`set()` |
|
||||
| `palette` | `Palette` (14 fields), color manipulation helpers (`shift`, `mix`, `tint_bg`, ...) |
|
||||
| `build` | Derives ~190 `ThemeColors` fields from a `Palette` |
|
||||
| `transform` | HSV-based hue rotation for generated palettes |
|
||||
|
||||
25 built-in themes.
|
||||
|
||||
## Key Types
|
||||
|
||||
- **`Palette`** — 14-field color definition, input to theme generation
|
||||
- **`ThemeColors`** — ~190 derived semantic colors used throughout the UI
|
||||
104
crates/ratatui/src/active_patterns.rs
Normal file
104
crates/ratatui/src/active_patterns.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MuteStatus {
|
||||
Normal,
|
||||
Muted,
|
||||
Soloed,
|
||||
EffectivelyMuted, // Solo active on another pattern
|
||||
}
|
||||
|
||||
pub struct ActivePatterns<'a> {
|
||||
patterns: &'a [(usize, usize, usize)], // (bank, pattern, iter)
|
||||
mute_status: Option<&'a [MuteStatus]>,
|
||||
current_step: Option<(usize, usize)>, // (current_step, total_steps)
|
||||
}
|
||||
|
||||
impl<'a> ActivePatterns<'a> {
|
||||
pub fn new(patterns: &'a [(usize, usize, usize)]) -> Self {
|
||||
Self {
|
||||
patterns,
|
||||
mute_status: None,
|
||||
current_step: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_step(mut self, current: usize, total: usize) -> Self {
|
||||
self.current_step = Some((current, total));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mute_status(mut self, status: &'a [MuteStatus]) -> Self {
|
||||
self.mute_status = Some(status);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ActivePatterns<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if area.width < 10 || area.height == 0 {
|
||||
return;
|
||||
}
|
||||
let theme = theme::get();
|
||||
|
||||
let max_pattern_rows = if self.current_step.is_some() {
|
||||
area.height.saturating_sub(1) as usize
|
||||
} else {
|
||||
area.height as usize
|
||||
};
|
||||
|
||||
for (row, &(bank, pattern, iter)) in self.patterns.iter().enumerate() {
|
||||
if row >= max_pattern_rows {
|
||||
break;
|
||||
}
|
||||
|
||||
let mute_status = self
|
||||
.mute_status
|
||||
.and_then(|s| s.get(row))
|
||||
.copied()
|
||||
.unwrap_or(MuteStatus::Normal);
|
||||
|
||||
let (prefix, fg, bg) = match mute_status {
|
||||
MuteStatus::Soloed => ("S", theme.list.soloed_fg, theme.list.soloed_bg),
|
||||
MuteStatus::Muted => ("M", theme.list.muted_fg, theme.list.muted_bg),
|
||||
MuteStatus::EffectivelyMuted => (" ", theme.list.muted_fg, theme.list.muted_bg),
|
||||
MuteStatus::Normal => {
|
||||
let bg = if row % 2 == 0 {
|
||||
theme.table.row_even
|
||||
} else {
|
||||
theme.table.row_odd
|
||||
};
|
||||
(" ", theme.ui.text_primary, bg)
|
||||
}
|
||||
};
|
||||
|
||||
let text = format!("{}B{:02}:{:02}({:02})", prefix, bank + 1, pattern + 1, iter.min(99));
|
||||
let y = area.y + row as u16;
|
||||
|
||||
let mut chars = text.chars();
|
||||
for col in 0..area.width as usize {
|
||||
let ch = chars.next().unwrap_or(' ');
|
||||
buf[(area.x + col as u16, y)]
|
||||
.set_char(ch)
|
||||
.set_fg(fg)
|
||||
.set_bg(bg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((current, total)) = self.current_step {
|
||||
let text = format!("{:02}/{:02}", current + 1, total);
|
||||
let y = area.y + area.height.saturating_sub(1);
|
||||
let mut chars = text.chars();
|
||||
for col in 0..area.width as usize {
|
||||
let ch = chars.next().unwrap_or(' ');
|
||||
buf[(area.x + col as u16, y)]
|
||||
.set_char(ch)
|
||||
.set_fg(theme.ui.text_primary)
|
||||
.set_bg(theme.table.row_even);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Collapsible categorized list widget with section headers.
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem};
|
||||
@@ -7,23 +5,14 @@ use ratatui::Frame;
|
||||
|
||||
use crate::theme;
|
||||
|
||||
/// Entry in a category list: either a section header or a leaf item.
|
||||
pub struct CategoryItem<'a> {
|
||||
pub label: &'a str,
|
||||
pub is_section: bool,
|
||||
pub collapsed: bool,
|
||||
}
|
||||
|
||||
/// What is currently selected: a leaf item or a section header.
|
||||
pub enum Selection {
|
||||
Item(usize),
|
||||
Section(usize),
|
||||
}
|
||||
|
||||
/// Scrollable list with collapsible section headers.
|
||||
pub struct CategoryList<'a> {
|
||||
items: &'a [CategoryItem<'a>],
|
||||
selection: Selection,
|
||||
selected: usize,
|
||||
focused: bool,
|
||||
title: &'a str,
|
||||
section_color: Color,
|
||||
@@ -34,11 +23,11 @@ pub struct CategoryList<'a> {
|
||||
}
|
||||
|
||||
impl<'a> CategoryList<'a> {
|
||||
pub fn new(items: &'a [CategoryItem<'a>], selection: Selection) -> Self {
|
||||
pub fn new(items: &'a [CategoryItem<'a>], selected: usize) -> Self {
|
||||
let theme = theme::get();
|
||||
Self {
|
||||
items,
|
||||
selection,
|
||||
selected,
|
||||
focused: false,
|
||||
title: "",
|
||||
section_color: theme.ui.text_dim,
|
||||
@@ -74,44 +63,25 @@ impl<'a> CategoryList<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the visible items list, filtering out children of collapsed sections.
|
||||
/// Returns (item, section_index_if_section, item_index_if_item).
|
||||
fn visible_items(&self) -> Vec<(&CategoryItem<'a>, Option<usize>, Option<usize>)> {
|
||||
let mut result = Vec::new();
|
||||
let mut skipping = false;
|
||||
let mut section_idx = 0usize;
|
||||
let mut item_idx = 0usize;
|
||||
for item in self.items.iter() {
|
||||
if item.is_section {
|
||||
skipping = item.collapsed;
|
||||
result.push((item, Some(section_idx), None));
|
||||
section_idx += 1;
|
||||
} else if !skipping {
|
||||
result.push((item, None, Some(item_idx)));
|
||||
item_idx += 1;
|
||||
} else {
|
||||
item_idx += 1;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn render(self, frame: &mut Frame, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let visible = self.visible_items();
|
||||
|
||||
let visible_height = area.height.saturating_sub(2) as usize;
|
||||
let total_items = visible.len();
|
||||
let total_items = self.items.len();
|
||||
|
||||
let selected_visual_idx = match &self.selection {
|
||||
Selection::Item(sel) => visible
|
||||
.iter()
|
||||
.position(|(_, _, item_idx)| *item_idx == Some(*sel))
|
||||
.unwrap_or(0),
|
||||
Selection::Section(sel) => visible
|
||||
.iter()
|
||||
.position(|(_, sec_idx, _)| *sec_idx == Some(*sel))
|
||||
.unwrap_or(0),
|
||||
let selected_visual_idx = {
|
||||
let mut visual = 0;
|
||||
let mut selectable_count = 0;
|
||||
for item in self.items.iter() {
|
||||
if !item.is_section {
|
||||
if selectable_count == self.selected {
|
||||
break;
|
||||
}
|
||||
selectable_count += 1;
|
||||
}
|
||||
visual += 1;
|
||||
}
|
||||
visual
|
||||
};
|
||||
|
||||
let scroll = if selected_visual_idx < visible_height / 2 {
|
||||
@@ -122,35 +92,24 @@ impl<'a> CategoryList<'a> {
|
||||
selected_visual_idx.saturating_sub(visible_height / 2)
|
||||
};
|
||||
|
||||
let mut selectable_idx = self.items
|
||||
.iter()
|
||||
.take(scroll)
|
||||
.filter(|e| !e.is_section)
|
||||
.count();
|
||||
|
||||
let is_dimmed = self.dimmed_color.is_some();
|
||||
|
||||
let items: Vec<ListItem> = visible
|
||||
let items: Vec<ListItem> = self.items
|
||||
.iter()
|
||||
.skip(scroll)
|
||||
.take(visible_height)
|
||||
.enumerate()
|
||||
.map(|(vis_offset, (item, sec_idx, _itm_idx))| {
|
||||
let visual_pos = scroll + vis_offset;
|
||||
.map(|item| {
|
||||
if item.is_section {
|
||||
let is_selected =
|
||||
matches!(&self.selection, Selection::Section(s) if Some(*s) == *sec_idx);
|
||||
let arrow = if item.collapsed { "▸" } else { "▾" };
|
||||
let style = if is_selected && self.focused {
|
||||
Style::new()
|
||||
.fg(self.focused_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if is_selected {
|
||||
Style::new()
|
||||
.fg(self.selected_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new().fg(self.section_color)
|
||||
};
|
||||
let prefix = if is_selected && !is_dimmed { "> " } else { "" };
|
||||
ListItem::new(format!("{prefix}{arrow} {}", item.label)).style(style)
|
||||
let style = Style::new().fg(self.section_color);
|
||||
ListItem::new(format!("─ {} ─", item.label)).style(style)
|
||||
} else {
|
||||
let is_selected = visual_pos == selected_visual_idx
|
||||
&& matches!(&self.selection, Selection::Item(_));
|
||||
let is_selected = selectable_idx == self.selected;
|
||||
let style = if let Some(dim_color) = self.dimmed_color {
|
||||
Style::new().fg(dim_color)
|
||||
} else if is_selected && self.focused {
|
||||
@@ -164,11 +123,8 @@ impl<'a> CategoryList<'a> {
|
||||
} else {
|
||||
Style::new().fg(self.normal_color)
|
||||
};
|
||||
let prefix = if is_selected && !is_dimmed {
|
||||
"> "
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let prefix = if is_selected && !is_dimmed { "> " } else { " " };
|
||||
selectable_idx += 1;
|
||||
ListItem::new(format!("{prefix}{}", item.label)).style(style)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Yes/No confirmation dialog widget.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::Style;
|
||||
@@ -9,7 +7,6 @@ use ratatui::Frame;
|
||||
|
||||
use super::ModalFrame;
|
||||
|
||||
/// Modal dialog with Yes/No buttons.
|
||||
pub struct ConfirmModal<'a> {
|
||||
title: &'a str,
|
||||
message: &'a str,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
//! Script editor widget with completion, search, and sample finder popups.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::{
|
||||
@@ -13,10 +10,8 @@ use ratatui::{
|
||||
};
|
||||
use tui_textarea::TextArea;
|
||||
|
||||
/// Callback that syntax-highlights a single line, returning styled spans (bool = annotation).
|
||||
pub type Highlighter<'a> = &'a dyn Fn(usize, &str) -> Vec<(Style, String, bool)>;
|
||||
|
||||
/// Metadata for a single autocomplete entry.
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionCandidate {
|
||||
pub name: String,
|
||||
@@ -26,7 +21,7 @@ pub struct CompletionCandidate {
|
||||
}
|
||||
|
||||
struct CompletionState {
|
||||
candidates: Arc<[CompletionCandidate]>,
|
||||
candidates: Vec<CompletionCandidate>,
|
||||
matches: Vec<usize>,
|
||||
cursor: usize,
|
||||
prefix: String,
|
||||
@@ -38,7 +33,7 @@ struct CompletionState {
|
||||
impl CompletionState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
candidates: Arc::from([]),
|
||||
candidates: Vec::new(),
|
||||
matches: Vec::new(),
|
||||
cursor: 0,
|
||||
prefix: String::new(),
|
||||
@@ -83,7 +78,6 @@ impl SearchState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Multi-line text editor backed by tui_textarea.
|
||||
pub struct Editor {
|
||||
text: TextArea<'static>,
|
||||
completion: CompletionState,
|
||||
@@ -105,14 +99,6 @@ impl Editor {
|
||||
self.text.is_selecting()
|
||||
}
|
||||
|
||||
pub fn move_cursor_to(&mut self, row: u16, col: u16) {
|
||||
self.text.move_cursor(tui_textarea::CursorMove::Jump(row, col));
|
||||
}
|
||||
|
||||
pub fn scroll_offset(&self) -> u16 {
|
||||
self.scroll_offset.get()
|
||||
}
|
||||
|
||||
pub fn copy(&mut self) {
|
||||
self.text.copy();
|
||||
}
|
||||
@@ -125,14 +111,6 @@ impl Editor {
|
||||
self.text.paste()
|
||||
}
|
||||
|
||||
pub fn yank_text(&self) -> String {
|
||||
self.text.yank_text()
|
||||
}
|
||||
|
||||
pub fn set_yank_text(&mut self, text: impl Into<String>) {
|
||||
self.text.set_yank_text(text);
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self) {
|
||||
self.text.select_all();
|
||||
}
|
||||
@@ -160,11 +138,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, lines: Vec<String>) {
|
||||
let yank = self.text.yank_text();
|
||||
self.text = TextArea::new(lines);
|
||||
if !yank.is_empty() {
|
||||
self.text.set_yank_text(yank);
|
||||
}
|
||||
self.completion.active = false;
|
||||
self.sample_finder.active = false;
|
||||
self.search.query.clear();
|
||||
@@ -172,7 +146,7 @@ impl Editor {
|
||||
self.scroll_offset.set(0);
|
||||
}
|
||||
|
||||
pub fn set_candidates(&mut self, candidates: Arc<[CompletionCandidate]>) {
|
||||
pub fn set_candidates(&mut self, candidates: Vec<CompletionCandidate>) {
|
||||
self.completion.candidates = candidates;
|
||||
}
|
||||
|
||||
@@ -488,7 +462,7 @@ impl Editor {
|
||||
if is_cursor {
|
||||
cursor_style
|
||||
} else if is_selected {
|
||||
base_style.bg(selection_style.bg.expect("selection style has bg"))
|
||||
base_style.bg(selection_style.bg.unwrap())
|
||||
} else {
|
||||
base_style
|
||||
}
|
||||
@@ -708,7 +682,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Score a fuzzy match of `query` against `target`. Lower is better; `None` if no match.
|
||||
pub fn fuzzy_match(query: &str, target: &str) -> Option<usize> {
|
||||
let target_lower: Vec<char> = target.to_lowercase().chars().collect();
|
||||
let query_lower: Vec<char> = query.to_lowercase().chars().collect();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! File/directory browser modal widget.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
@@ -9,19 +7,15 @@ use ratatui::Frame;
|
||||
|
||||
use super::ModalFrame;
|
||||
|
||||
/// Modal listing files and directories with a filter input line.
|
||||
pub struct FileBrowserModal<'a> {
|
||||
title: &'a str,
|
||||
input: &'a str,
|
||||
entries: &'a [(String, bool, bool)],
|
||||
audio_counts: &'a [Option<usize>],
|
||||
selected: usize,
|
||||
scroll_offset: usize,
|
||||
border_color: Option<Color>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hints: Option<Line<'a>>,
|
||||
color_path: bool,
|
||||
}
|
||||
|
||||
impl<'a> FileBrowserModal<'a> {
|
||||
@@ -30,14 +24,11 @@ impl<'a> FileBrowserModal<'a> {
|
||||
title,
|
||||
input,
|
||||
entries,
|
||||
audio_counts: &[],
|
||||
selected: 0,
|
||||
scroll_offset: 0,
|
||||
border_color: None,
|
||||
width: 60,
|
||||
height: 16,
|
||||
hints: None,
|
||||
color_path: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,21 +57,6 @@ impl<'a> FileBrowserModal<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hints(mut self, hints: Line<'a>) -> Self {
|
||||
self.hints = Some(hints);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn audio_counts(mut self, counts: &'a [Option<usize>]) -> Self {
|
||||
self.audio_counts = counts;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color_path(mut self) -> Self {
|
||||
self.color_path = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render_centered(self, frame: &mut Frame, term: Rect) -> Rect {
|
||||
let colors = theme::get();
|
||||
let border_color = self.border_color.unwrap_or(colors.ui.text_primary);
|
||||
@@ -91,61 +67,37 @@ impl<'a> FileBrowserModal<'a> {
|
||||
.border_color(border_color)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let has_hints = self.hints.is_some();
|
||||
let constraints = if has_hints {
|
||||
vec![
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(1),
|
||||
]
|
||||
} else {
|
||||
vec![Constraint::Length(1), Constraint::Min(1)]
|
||||
};
|
||||
let rows = Layout::vertical(constraints).split(inner);
|
||||
let rows = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner);
|
||||
|
||||
// Input line
|
||||
let input_spans = if self.color_path {
|
||||
let (path_part, filter_part) = match self.input.rfind('/') {
|
||||
Some(pos) => (&self.input[..=pos], &self.input[pos + 1..]),
|
||||
None => ("", self.input),
|
||||
};
|
||||
vec![
|
||||
Span::raw("> "),
|
||||
Span::styled(path_part.to_string(), Style::new().fg(colors.browser.directory)),
|
||||
Span::styled(filter_part.to_string(), Style::new().fg(colors.input.text)),
|
||||
Span::styled("█", Style::new().fg(colors.input.cursor)),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::raw("> "),
|
||||
Span::styled(self.input, Style::new().fg(colors.input.text)),
|
||||
Span::styled("█", Style::new().fg(colors.input.cursor)),
|
||||
]
|
||||
};
|
||||
frame.render_widget(Paragraph::new(Line::from(input_spans)), rows[0]);
|
||||
|
||||
// Hints bar
|
||||
if let Some(hints) = self.hints {
|
||||
let hint_row = rows[2];
|
||||
frame.render_widget(
|
||||
Paragraph::new(hints).alignment(ratatui::layout::Alignment::Right),
|
||||
hint_row,
|
||||
);
|
||||
}
|
||||
])),
|
||||
rows[0],
|
||||
);
|
||||
|
||||
// Entries list
|
||||
let visible_height = rows[1].height as usize;
|
||||
let visible_entries = self
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.scroll_offset)
|
||||
.take(visible_height);
|
||||
|
||||
let lines: Vec<Line> = visible_entries
|
||||
.map(|(abs_idx, (name, is_dir, is_cagire))| {
|
||||
.enumerate()
|
||||
.map(|(i, (name, is_dir, is_cagire))| {
|
||||
let abs_idx = i + self.scroll_offset;
|
||||
let is_selected = abs_idx == self.selected;
|
||||
let prefix = if is_selected { "> " } else { " " };
|
||||
let display = if *is_dir {
|
||||
format!("{prefix}{name}/")
|
||||
} else {
|
||||
format!("{prefix}{name}")
|
||||
};
|
||||
let color = if is_selected {
|
||||
colors.browser.selected
|
||||
} else if *is_dir {
|
||||
@@ -155,21 +107,7 @@ impl<'a> FileBrowserModal<'a> {
|
||||
} else {
|
||||
colors.browser.file
|
||||
};
|
||||
let display = if *is_dir {
|
||||
format!("{prefix}{name}/")
|
||||
} else {
|
||||
format!("{prefix}{name}")
|
||||
};
|
||||
let mut spans = vec![Span::styled(display, Style::new().fg(color))];
|
||||
if *is_dir && name != ".." {
|
||||
if let Some(Some(count)) = self.audio_counts.get(abs_idx) {
|
||||
spans.push(Span::styled(
|
||||
format!(" ({count})"),
|
||||
Style::new().fg(colors.browser.file),
|
||||
));
|
||||
}
|
||||
}
|
||||
Line::from(spans)
|
||||
Line::from(Span::styled(display, Style::new().fg(color)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
//! Bottom-bar keyboard hint renderer.
|
||||
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::style::Style;
|
||||
|
||||
use crate::theme;
|
||||
|
||||
/// Build a styled line of key/action pairs for the hint bar.
|
||||
pub fn hint_line(pairs: &[(&str, &str)]) -> Line<'static> {
|
||||
let theme = theme::get();
|
||||
let key_style = Style::default().fg(theme.hint.key);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
//! Reusable TUI widgets for the Cagire sequencer interface.
|
||||
|
||||
mod active_patterns;
|
||||
mod category_list;
|
||||
mod confirm;
|
||||
mod editor;
|
||||
mod file_browser;
|
||||
mod hint_bar;
|
||||
mod lissajous;
|
||||
mod list_select;
|
||||
mod modal;
|
||||
mod nav_minimap;
|
||||
@@ -22,12 +20,12 @@ pub mod theme;
|
||||
mod vu_meter;
|
||||
mod waveform;
|
||||
|
||||
pub use category_list::{CategoryItem, CategoryList, Selection};
|
||||
pub use active_patterns::{ActivePatterns, MuteStatus};
|
||||
pub use category_list::{CategoryItem, CategoryList};
|
||||
pub use confirm::ConfirmModal;
|
||||
pub use editor::{fuzzy_match, CompletionCandidate, Editor};
|
||||
pub use file_browser::FileBrowserModal;
|
||||
pub use hint_bar::hint_line;
|
||||
pub use lissajous::Lissajous;
|
||||
pub use list_select::ListSelect;
|
||||
pub use modal::ModalFrame;
|
||||
pub use nav_minimap::{hit_test_tile, minimap_area, NavMinimap, NavTile};
|
||||
@@ -38,7 +36,7 @@ pub use scroll_indicators::{render_scroll_indicators, IndicatorAlign};
|
||||
pub use search_bar::render_search_bar;
|
||||
pub use section_header::render_section_header;
|
||||
pub use sparkles::Sparkles;
|
||||
pub use spectrum::{Spectrum, SpectrumStyle};
|
||||
pub use spectrum::Spectrum;
|
||||
pub use text_input::TextInputModal;
|
||||
pub use vu_meter::VuMeter;
|
||||
pub use waveform::Waveform;
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
//! Lissajous XY oscilloscope widget using braille characters.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
||||
static TRAIL: RefCell<TrailState> = const { RefCell::new(TrailState { fine_w: 0, fine_h: 0, heat: Vec::new() }) };
|
||||
}
|
||||
|
||||
struct TrailState {
|
||||
fine_w: usize,
|
||||
fine_h: usize,
|
||||
heat: Vec<f32>,
|
||||
}
|
||||
|
||||
/// XY oscilloscope plotting left vs right channels as a Lissajous curve.
|
||||
pub struct Lissajous<'a> {
|
||||
left: &'a [f32],
|
||||
right: &'a [f32],
|
||||
color: Option<Color>,
|
||||
gain: f32,
|
||||
trails: bool,
|
||||
}
|
||||
|
||||
impl<'a> Lissajous<'a> {
|
||||
pub fn new(left: &'a [f32], right: &'a [f32]) -> Self {
|
||||
Self {
|
||||
left,
|
||||
right,
|
||||
color: None,
|
||||
gain: 1.0,
|
||||
trails: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trails(mut self, enabled: bool) -> Self {
|
||||
self.trails = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, c: Color) -> Self {
|
||||
self.color = Some(c);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gain(mut self, g: f32) -> Self {
|
||||
self.gain = g;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Lissajous<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if area.width == 0 || area.height == 0 || self.left.is_empty() || self.right.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.trails {
|
||||
self.render_trails(area, buf);
|
||||
} else {
|
||||
self.render_normal(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Lissajous<'_> {
|
||||
fn render_normal(self, area: Rect, buf: &mut Buffer) {
|
||||
let color = self.color.unwrap_or_else(|| theme::get().meter.low);
|
||||
let width = area.width as usize;
|
||||
let height = area.height as usize;
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
let len = self.left.len().min(self.right.len());
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
let size = width * height;
|
||||
patterns.clear();
|
||||
patterns.resize(size, 0);
|
||||
|
||||
for i in 0..len {
|
||||
let l = (self.left[i] * self.gain).clamp(-1.0, 1.0);
|
||||
let r = (self.right[i] * self.gain).clamp(-1.0, 1.0);
|
||||
|
||||
let fine_x = ((r + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
|
||||
let fine_y = ((1.0 - l) * 0.5 * (fine_height - 1) as f32).round() as usize;
|
||||
let fine_x = fine_x.min(fine_width - 1);
|
||||
let fine_y = fine_y.min(fine_height - 1);
|
||||
|
||||
let char_x = fine_x / 2;
|
||||
let char_y = fine_y / 4;
|
||||
let dot_x = fine_x % 2;
|
||||
let dot_y = fine_y % 4;
|
||||
|
||||
patterns[char_y * width + char_x] |= braille_bit(dot_x, dot_y);
|
||||
}
|
||||
|
||||
for cy in 0..height {
|
||||
for cx in 0..width {
|
||||
let pattern = patterns[cy * width + cx];
|
||||
if pattern != 0 {
|
||||
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
||||
buf[(area.x + cx as u16, area.y + cy as u16)]
|
||||
.set_char(ch)
|
||||
.set_fg(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn render_trails(self, area: Rect, buf: &mut Buffer) {
|
||||
let theme = theme::get();
|
||||
let width = area.width as usize;
|
||||
let height = area.height as usize;
|
||||
let fine_w = width * 2;
|
||||
let fine_h = height * 4;
|
||||
let len = self.left.len().min(self.right.len());
|
||||
|
||||
TRAIL.with(|t| {
|
||||
let mut trail = t.borrow_mut();
|
||||
|
||||
// Reset if dimensions changed
|
||||
if trail.fine_w != fine_w || trail.fine_h != fine_h {
|
||||
trail.fine_w = fine_w;
|
||||
trail.fine_h = fine_h;
|
||||
trail.heat.clear();
|
||||
trail.heat.resize(fine_w * fine_h, 0.0);
|
||||
}
|
||||
|
||||
// Decay existing heat
|
||||
for h in trail.heat.iter_mut() {
|
||||
*h *= 0.85;
|
||||
}
|
||||
|
||||
// Plot new sample points
|
||||
for i in 0..len {
|
||||
let l = (self.left[i] * self.gain).clamp(-1.0, 1.0);
|
||||
let r = (self.right[i] * self.gain).clamp(-1.0, 1.0);
|
||||
|
||||
let fx = ((r + 1.0) * 0.5 * (fine_w - 1) as f32).round() as usize;
|
||||
let fy = ((1.0 - l) * 0.5 * (fine_h - 1) as f32).round() as usize;
|
||||
let fx = fx.min(fine_w - 1);
|
||||
let fy = fy.min(fine_h - 1);
|
||||
|
||||
trail.heat[fy * fine_w + fx] = 1.0;
|
||||
}
|
||||
|
||||
// Convert heat map to braille
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
patterns.clear();
|
||||
patterns.resize(width * height, 0);
|
||||
|
||||
// Track brightest color per cell
|
||||
let mut colors: Vec<Option<Color>> = vec![None; width * height];
|
||||
|
||||
for fy in 0..fine_h {
|
||||
for fx in 0..fine_w {
|
||||
let h = trail.heat[fy * fine_w + fx];
|
||||
if h < 0.05 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cx = fx / 2;
|
||||
let cy = fy / 4;
|
||||
let dx = fx % 2;
|
||||
let dy = fy % 4;
|
||||
|
||||
let idx = cy * width + cx;
|
||||
patterns[idx] |= braille_bit(dx, dy);
|
||||
|
||||
let dot_color = if h > 0.7 {
|
||||
theme.meter.high
|
||||
} else if h > 0.25 {
|
||||
theme.meter.mid
|
||||
} else {
|
||||
theme.meter.low
|
||||
};
|
||||
|
||||
let replace = match colors[idx] {
|
||||
None => true,
|
||||
Some(cur) => {
|
||||
rank_color(dot_color, &theme) > rank_color(cur, &theme)
|
||||
}
|
||||
};
|
||||
if replace {
|
||||
colors[idx] = Some(dot_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for cy in 0..height {
|
||||
for cx in 0..width {
|
||||
let idx = cy * width + cx;
|
||||
let pattern = patterns[idx];
|
||||
if pattern != 0 {
|
||||
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
||||
let color = colors[idx].unwrap_or(theme.meter.low);
|
||||
buf[(area.x + cx as u16, area.y + cy as u16)]
|
||||
.set_char(ch)
|
||||
.set_fg(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn braille_bit(dot_x: usize, dot_y: usize) -> u8 {
|
||||
match (dot_x, dot_y) {
|
||||
(0, 0) => 0x01,
|
||||
(0, 1) => 0x02,
|
||||
(0, 2) => 0x04,
|
||||
(0, 3) => 0x40,
|
||||
(1, 0) => 0x08,
|
||||
(1, 1) => 0x10,
|
||||
(1, 2) => 0x20,
|
||||
(1, 3) => 0x80,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rank_color(c: Color, theme: &crate::theme::ThemeColors) -> u8 {
|
||||
if c == theme.meter.high { 2 }
|
||||
else if c == theme.meter.mid { 1 }
|
||||
else { 0 }
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Scrollable single-select list widget with cursor highlight.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Modifier, Style};
|
||||
@@ -7,7 +5,6 @@ use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
|
||||
/// Scrollable list with a highlighted cursor and selected-item marker.
|
||||
pub struct ListSelect<'a> {
|
||||
items: &'a [String],
|
||||
selected: usize,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
//! Centered modal frame with border and title.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
/// Centered modal overlay with titled border.
|
||||
pub struct ModalFrame<'a> {
|
||||
title: &'a str,
|
||||
width: u16,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Page navigation minimap showing a 3x2 grid of tiles.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Alignment, Rect};
|
||||
use ratatui::style::Style;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Vertical label/value property form renderer.
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::widgets::Paragraph;
|
||||
@@ -7,7 +5,6 @@ use ratatui::Frame;
|
||||
|
||||
use crate::theme;
|
||||
|
||||
/// Render a vertical list of label/value pairs with selection highlight.
|
||||
pub fn render_props_form(frame: &mut Frame, area: Rect, fields: &[(&str, &str, bool)]) {
|
||||
let theme = theme::get();
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
//! Tree-view sample browser with search filtering.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
/// Node type in the sample tree.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TreeLineKind {
|
||||
Root { expanded: bool },
|
||||
@@ -15,7 +12,6 @@ pub enum TreeLineKind {
|
||||
File,
|
||||
}
|
||||
|
||||
/// A single row in the sample browser tree.
|
||||
#[derive(Clone)]
|
||||
pub struct TreeLine {
|
||||
pub depth: u8,
|
||||
@@ -23,10 +19,8 @@ pub struct TreeLine {
|
||||
pub label: String,
|
||||
pub folder: String,
|
||||
pub index: usize,
|
||||
pub child_count: usize,
|
||||
}
|
||||
|
||||
/// Tree-view browser for navigating sample folders.
|
||||
pub struct SampleBrowser<'a> {
|
||||
entries: &'a [TreeLine],
|
||||
cursor: usize,
|
||||
@@ -117,13 +111,13 @@ impl<'a> SampleBrowser<'a> {
|
||||
fn render_tree(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) {
|
||||
let height = area.height as usize;
|
||||
if self.entries.is_empty() {
|
||||
if self.search_query.is_empty() {
|
||||
self.render_empty_guide(frame, area, colors);
|
||||
let msg = if self.search_query.is_empty() {
|
||||
"No samples loaded"
|
||||
} else {
|
||||
let line =
|
||||
Line::from(Span::styled("No matches", Style::new().fg(colors.browser.empty_text)));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
}
|
||||
"No matches"
|
||||
};
|
||||
let line = Line::from(Span::styled(msg, Style::new().fg(colors.browser.empty_text)));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,10 +131,10 @@ impl<'a> SampleBrowser<'a> {
|
||||
|
||||
let (icon, icon_color) = match entry.kind {
|
||||
TreeLineKind::Root { expanded: true } | TreeLineKind::Folder { expanded: true } => {
|
||||
("\u{2212} ", colors.browser.folder_icon)
|
||||
("\u{25BC} ", colors.browser.folder_icon)
|
||||
}
|
||||
TreeLineKind::Root { expanded: false }
|
||||
| TreeLineKind::Folder { expanded: false } => ("+ ", colors.browser.folder_icon),
|
||||
| TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", colors.browser.folder_icon),
|
||||
TreeLineKind::File => ("\u{266A} ", colors.browser.file_icon),
|
||||
};
|
||||
|
||||
@@ -164,43 +158,15 @@ impl<'a> SampleBrowser<'a> {
|
||||
Style::new().fg(icon_color)
|
||||
};
|
||||
|
||||
let prefix_width = indent.len() + 2; // indent + icon
|
||||
let suffix = match entry.kind {
|
||||
TreeLineKind::File => format!(" {}", entry.index),
|
||||
TreeLineKind::Root { expanded: false }
|
||||
| TreeLineKind::Folder { expanded: false }
|
||||
if entry.child_count > 0 =>
|
||||
{
|
||||
format!(" ({})", entry.child_count)
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
let max_label = (area.width as usize)
|
||||
.saturating_sub(prefix_width)
|
||||
.saturating_sub(suffix.len());
|
||||
let label: std::borrow::Cow<str> = if entry.label.len() > max_label && max_label > 1 {
|
||||
let truncated: String = entry.label.chars().take(max_label - 1).collect();
|
||||
format!("{}\u{2026}", truncated).into()
|
||||
} else {
|
||||
(&entry.label).into()
|
||||
};
|
||||
|
||||
let mut spans = vec![
|
||||
Span::raw(indent),
|
||||
Span::styled(icon, icon_style),
|
||||
Span::styled(label, label_style),
|
||||
Span::styled(&entry.label, label_style),
|
||||
];
|
||||
|
||||
match entry.kind {
|
||||
TreeLineKind::File => {
|
||||
let idx_style = Style::new().fg(colors.browser.empty_text);
|
||||
spans.push(Span::styled(suffix, idx_style));
|
||||
}
|
||||
_ if !suffix.is_empty() => {
|
||||
let dim_style = Style::new().fg(colors.browser.empty_text);
|
||||
spans.push(Span::styled(suffix, dim_style));
|
||||
}
|
||||
_ => {}
|
||||
if matches!(entry.kind, TreeLineKind::File) {
|
||||
let idx_style = Style::new().fg(colors.browser.empty_text);
|
||||
spans.push(Span::styled(format!(" {}", entry.index), idx_style));
|
||||
}
|
||||
|
||||
lines.push(Line::from(spans));
|
||||
@@ -208,47 +174,4 @@ impl<'a> SampleBrowser<'a> {
|
||||
|
||||
frame.render_widget(Paragraph::new(lines), area);
|
||||
}
|
||||
|
||||
fn render_empty_guide(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) {
|
||||
let muted = Style::new().fg(colors.browser.empty_text);
|
||||
let heading = Style::new().fg(colors.ui.text_primary);
|
||||
let key = Style::new().fg(colors.hint.key);
|
||||
let desc = Style::new().fg(colors.hint.text);
|
||||
let code = Style::new().fg(colors.ui.accent);
|
||||
|
||||
let lines = vec![
|
||||
Line::from(Span::styled(" No samples loaded.", muted)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(" Load from the Engine page:", heading)),
|
||||
Line::from(""),
|
||||
Line::from(vec![
|
||||
Span::styled(" F6 ", key),
|
||||
Span::styled("Go to Engine page", desc),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" A ", key),
|
||||
Span::styled("Add a sample folder", desc),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(" Organize samples like this:", heading)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(" samples/", code)),
|
||||
Line::from(Span::styled(" \u{251C}\u{2500}\u{2500} kick/", code)),
|
||||
Line::from(Span::styled(" \u{2502} \u{2514}\u{2500}\u{2500} kick.wav", code)),
|
||||
Line::from(Span::styled(" \u{251C}\u{2500}\u{2500} snare/", code)),
|
||||
Line::from(Span::styled(" \u{2502} \u{2514}\u{2500}\u{2500} snare.wav", code)),
|
||||
Line::from(Span::styled(" \u{2514}\u{2500}\u{2500} hats/", code)),
|
||||
Line::from(Span::styled(" \u{251C}\u{2500}\u{2500} closed.wav", code)),
|
||||
Line::from(Span::styled(" \u{251C}\u{2500}\u{2500} open.wav", code)),
|
||||
Line::from(Span::styled(" \u{2514}\u{2500}\u{2500} pedal.wav", code)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(" Folders become Forth words:", heading)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(" kick sound .", code)),
|
||||
Line::from(Span::styled(" hats sound 2 n .", code)),
|
||||
Line::from(Span::styled(" snare sound 0.5 speed .", code)),
|
||||
];
|
||||
|
||||
frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), area);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Oscilloscope waveform widget using braille characters.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
@@ -11,14 +9,12 @@ thread_local! {
|
||||
static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// Rendering direction for the oscilloscope.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Orientation {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Single-channel oscilloscope using braille dot plotting.
|
||||
pub struct Scope<'a> {
|
||||
data: &'a [f32],
|
||||
orientation: Orientation,
|
||||
@@ -45,11 +41,6 @@ impl<'a> Scope<'a> {
|
||||
self.color = Some(c);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gain(mut self, g: f32) -> Self {
|
||||
self.gain = g;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Scope<'_> {
|
||||
@@ -75,6 +66,9 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
let size = width * height;
|
||||
@@ -83,7 +77,7 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g
|
||||
|
||||
for fine_x in 0..fine_width {
|
||||
let sample_idx = (fine_x * data.len()) / fine_width;
|
||||
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);
|
||||
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * auto_gain).clamp(-1.0, 1.0);
|
||||
|
||||
let fine_y = ((1.0 - sample) * 0.5 * (fine_height - 1) as f32).round() as usize;
|
||||
let fine_y = fine_y.min(fine_height - 1);
|
||||
@@ -128,6 +122,9 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
let size = width * height;
|
||||
@@ -136,7 +133,7 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai
|
||||
|
||||
for fine_y in 0..fine_height {
|
||||
let sample_idx = (fine_y * data.len()) / fine_height;
|
||||
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);
|
||||
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * auto_gain).clamp(-1.0, 1.0);
|
||||
|
||||
let fine_x = ((sample + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
|
||||
let fine_x = fine_x.min(fine_width - 1);
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
//! Up/down arrow scroll indicators for bounded lists.
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
|
||||
/// Horizontal alignment for scroll indicators.
|
||||
pub enum IndicatorAlign {
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
/// Render up/down scroll arrows when content overflows.
|
||||
pub fn render_scroll_indicators(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Inline search bar with active/inactive styling.
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::{Line, Span};
|
||||
@@ -8,7 +6,6 @@ use ratatui::Frame;
|
||||
|
||||
use crate::theme;
|
||||
|
||||
/// Render a `/query` search bar.
|
||||
pub fn render_search_bar(frame: &mut Frame, area: Rect, query: &str, active: bool) {
|
||||
let theme = theme::get();
|
||||
let style = if active {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Section header with horizontal divider for engine-view panels.
|
||||
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::widgets::Paragraph;
|
||||
@@ -7,7 +5,6 @@ use ratatui::Frame;
|
||||
|
||||
use crate::theme;
|
||||
|
||||
/// Render a section title with a horizontal divider below it.
|
||||
pub fn render_section_header(frame: &mut Frame, title: &str, focused: bool, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let [header_area, divider_area] =
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Decorative particle effect using random Unicode glyphs.
|
||||
|
||||
use crate::theme;
|
||||
use rand::Rng;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -16,7 +14,6 @@ struct Sparkle {
|
||||
life: u8,
|
||||
}
|
||||
|
||||
/// Animated sparkle particles for visual flair.
|
||||
#[derive(Default)]
|
||||
pub struct Sparkles {
|
||||
sparkles: Vec<Sparkle>,
|
||||
|
||||
@@ -1,58 +1,18 @@
|
||||
//! 32-band frequency spectrum display with optional peak hold.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::cell::RefCell;
|
||||
|
||||
const BLOCKS: [char; 8] = ['\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}', '\u{2588}'];
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum SpectrumStyle {
|
||||
#[default]
|
||||
Bars,
|
||||
Line,
|
||||
Filled,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static PEAKS: RefCell<[f32; 32]> = const { RefCell::new([0.0; 32]) };
|
||||
static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// 32-band spectrum analyzer using block characters.
|
||||
pub struct Spectrum<'a> {
|
||||
data: &'a [f32; 32],
|
||||
gain: f32,
|
||||
style: SpectrumStyle,
|
||||
peaks: bool,
|
||||
}
|
||||
|
||||
impl<'a> Spectrum<'a> {
|
||||
pub fn new(data: &'a [f32; 32]) -> Self {
|
||||
Self {
|
||||
data,
|
||||
gain: 1.0,
|
||||
style: SpectrumStyle::Bars,
|
||||
peaks: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gain(mut self, g: f32) -> Self {
|
||||
self.gain = g;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, s: SpectrumStyle) -> Self {
|
||||
self.style = s;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn peaks(mut self, enabled: bool) -> Self {
|
||||
self.peaks = enabled;
|
||||
self
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,177 +22,45 @@ impl Widget for Spectrum<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update peak hold state
|
||||
let peak_values = if self.peaks {
|
||||
Some(PEAKS.with(|p| {
|
||||
let mut peaks = p.borrow_mut();
|
||||
for (i, &mag) in self.data.iter().enumerate() {
|
||||
let v = (mag * self.gain).min(1.0);
|
||||
if v >= peaks[i] {
|
||||
peaks[i] = v;
|
||||
} else {
|
||||
peaks[i] = (peaks[i] - 0.02).max(v);
|
||||
}
|
||||
}
|
||||
*peaks
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match self.style {
|
||||
SpectrumStyle::Bars => render_bars(self.data, area, buf, self.gain, peak_values.as_ref()),
|
||||
SpectrumStyle::Line => render_braille(self.data, area, buf, self.gain, false, peak_values.as_ref()),
|
||||
SpectrumStyle::Filled => render_braille(self.data, area, buf, self.gain, true, peak_values.as_ref()),
|
||||
let colors = theme::get();
|
||||
let height = area.height as f32;
|
||||
let base = area.width as usize / 32;
|
||||
let remainder = area.width as usize % 32;
|
||||
if base == 0 && remainder == 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn band_color(ratio: f32, colors: &theme::ThemeColors) -> Color {
|
||||
if ratio < 0.33 {
|
||||
Color::Rgb(colors.meter.low_rgb.0, colors.meter.low_rgb.1, colors.meter.low_rgb.2)
|
||||
} else if ratio < 0.66 {
|
||||
Color::Rgb(colors.meter.mid_rgb.0, colors.meter.mid_rgb.1, colors.meter.mid_rgb.2)
|
||||
} else {
|
||||
Color::Rgb(colors.meter.high_rgb.0, colors.meter.high_rgb.1, colors.meter.high_rgb.2)
|
||||
}
|
||||
}
|
||||
let mut x_start = area.x;
|
||||
for (band, &mag) in self.data.iter().enumerate() {
|
||||
let w = base + if band < remainder { 1 } else { 0 };
|
||||
if w == 0 {
|
||||
continue;
|
||||
}
|
||||
let bar_height = mag * height;
|
||||
let full_cells = bar_height as usize;
|
||||
let frac = bar_height - full_cells as f32;
|
||||
let frac_idx = (frac * 8.0) as usize;
|
||||
|
||||
fn render_bars(data: &[f32; 32], area: Rect, buf: &mut Buffer, gain: f32, peaks: Option<&[f32; 32]>) {
|
||||
let colors = theme::get();
|
||||
let height = area.height as f32;
|
||||
let base = area.width as usize / 32;
|
||||
let remainder = area.width as usize % 32;
|
||||
if base == 0 && remainder == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut x_start = area.x;
|
||||
for (band, &mag) in data.iter().enumerate() {
|
||||
let w = base + if band < remainder { 1 } else { 0 };
|
||||
if w == 0 {
|
||||
continue;
|
||||
}
|
||||
let bar_height = (mag * gain).min(1.0) * height;
|
||||
let full_cells = bar_height as usize;
|
||||
let frac = bar_height - full_cells as f32;
|
||||
let frac_idx = (frac * 8.0) as usize;
|
||||
|
||||
// Peak hold row
|
||||
let peak_row = peaks.map(|p| {
|
||||
let ph = p[band] * height;
|
||||
let row = (height - ph).max(0.0) as usize;
|
||||
row.min(area.height as usize - 1)
|
||||
});
|
||||
|
||||
for row in 0..area.height as usize {
|
||||
let y = area.y + area.height - 1 - row as u16;
|
||||
let ratio = row as f32 / area.height as f32;
|
||||
let color = band_color(ratio, &colors);
|
||||
|
||||
for dx in 0..w as u16 {
|
||||
let x = x_start + dx;
|
||||
if row < full_cells {
|
||||
buf[(x, y)].set_char(BLOCKS[7]).set_fg(color);
|
||||
} else if row == full_cells && frac_idx > 0 {
|
||||
buf[(x, y)].set_char(BLOCKS[frac_idx - 1]).set_fg(color);
|
||||
} else if let Some(pr) = peak_row {
|
||||
// peak_row is from top (0 = top), row is from bottom
|
||||
let from_top = area.height as usize - 1 - row;
|
||||
if from_top == pr {
|
||||
buf[(x, y)].set_char('─').set_fg(colors.meter.high);
|
||||
for row in 0..area.height as usize {
|
||||
let y = area.y + area.height - 1 - row as u16;
|
||||
let ratio = row as f32 / area.height as f32;
|
||||
let color = if ratio < 0.33 {
|
||||
Color::Rgb(colors.meter.low_rgb.0, colors.meter.low_rgb.1, colors.meter.low_rgb.2)
|
||||
} else if ratio < 0.66 {
|
||||
Color::Rgb(colors.meter.mid_rgb.0, colors.meter.mid_rgb.1, colors.meter.mid_rgb.2)
|
||||
} else {
|
||||
Color::Rgb(colors.meter.high_rgb.0, colors.meter.high_rgb.1, colors.meter.high_rgb.2)
|
||||
};
|
||||
for dx in 0..w as u16 {
|
||||
let x = x_start + dx;
|
||||
if row < full_cells {
|
||||
buf[(x, y)].set_char(BLOCKS[7]).set_fg(color);
|
||||
} else if row == full_cells && frac_idx > 0 {
|
||||
buf[(x, y)].set_char(BLOCKS[frac_idx - 1]).set_fg(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
x_start += w as u16;
|
||||
}
|
||||
x_start += w as u16;
|
||||
}
|
||||
}
|
||||
|
||||
fn render_braille(
|
||||
data: &[f32; 32],
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
gain: f32,
|
||||
filled: bool,
|
||||
peaks: Option<&[f32; 32]>,
|
||||
) {
|
||||
let colors = theme::get();
|
||||
let width = area.width as usize;
|
||||
let height = area.height as usize;
|
||||
let fine_w = width * 2;
|
||||
let fine_h = height * 4;
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
patterns.clear();
|
||||
patterns.resize(width * height, 0);
|
||||
|
||||
// Interpolate 32 bands across fine_w columns
|
||||
for fx in 0..fine_w {
|
||||
let band_f = fx as f32 * 31.0 / (fine_w - 1).max(1) as f32;
|
||||
let lo = band_f as usize;
|
||||
let hi = (lo + 1).min(31);
|
||||
let t = band_f - lo as f32;
|
||||
let mag = ((data[lo] * (1.0 - t) + data[hi] * t) * gain).min(1.0);
|
||||
let fy = ((1.0 - mag) * (fine_h - 1) as f32).round() as usize;
|
||||
let fy = fy.min(fine_h - 1);
|
||||
|
||||
if filled {
|
||||
for y in fy..fine_h {
|
||||
let cy = y / 4;
|
||||
let dy = y % 4;
|
||||
let cx = fx / 2;
|
||||
let dx = fx % 2;
|
||||
patterns[cy * width + cx] |= braille_bit(dx, dy);
|
||||
}
|
||||
} else {
|
||||
let cy = fy / 4;
|
||||
let dy = fy % 4;
|
||||
let cx = fx / 2;
|
||||
let dx = fx % 2;
|
||||
patterns[cy * width + cx] |= braille_bit(dx, dy);
|
||||
}
|
||||
|
||||
// Peak dots
|
||||
if let Some(pk) = peaks {
|
||||
let pv = (pk[lo] * (1.0 - t) + pk[hi] * t).min(1.0);
|
||||
let py = ((1.0 - pv) * (fine_h - 1) as f32).round() as usize;
|
||||
let py = py.min(fine_h - 1);
|
||||
let cy = py / 4;
|
||||
let dy = py % 4;
|
||||
let cx = fx / 2;
|
||||
let dx = fx % 2;
|
||||
patterns[cy * width + cx] |= braille_bit(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
for cy in 0..height {
|
||||
for cx in 0..width {
|
||||
let pattern = patterns[cy * width + cx];
|
||||
if pattern != 0 {
|
||||
let ratio = 1.0 - (cy as f32 / height as f32);
|
||||
let color = band_color(ratio, &colors);
|
||||
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
||||
buf[(area.x + cx as u16, area.y + cy as u16)]
|
||||
.set_char(ch)
|
||||
.set_fg(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn braille_bit(dot_x: usize, dot_y: usize) -> u8 {
|
||||
match (dot_x, dot_y) {
|
||||
(0, 0) => 0x01,
|
||||
(0, 1) => 0x02,
|
||||
(0, 2) => 0x04,
|
||||
(0, 3) => 0x40,
|
||||
(1, 0) => 0x08,
|
||||
(1, 1) => 0x10,
|
||||
(1, 2) => 0x20,
|
||||
(1, 3) => 0x80,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Single-line text input modal with optional hint.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
@@ -9,7 +7,6 @@ use ratatui::Frame;
|
||||
|
||||
use super::ModalFrame;
|
||||
|
||||
/// Modal dialog with a single-line text input.
|
||||
pub struct TextInputModal<'a> {
|
||||
title: &'a str,
|
||||
input: &'a str,
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
//! Derive [`ThemeColors`] from a [`Palette`].
|
||||
|
||||
use super::*;
|
||||
use super::palette::{Palette, Rgb, darken, mid, rgb, tint};
|
||||
|
||||
/// Build a complete [`ThemeColors`] from a [`Palette`].
|
||||
pub fn build(p: &Palette) -> ThemeColors {
|
||||
let darker_bg = darken(p.bg, 0.15);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: rgb(p.bg),
|
||||
bg_rgb: p.bg,
|
||||
text_primary: rgb(p.fg),
|
||||
text_muted: rgb(p.fg_dim),
|
||||
text_dim: rgb(p.fg_muted),
|
||||
border: rgb(p.surface2),
|
||||
header: rgb(p.cyan),
|
||||
unfocused: rgb(p.fg_muted),
|
||||
accent: rgb(p.accent),
|
||||
surface: rgb(p.surface),
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: rgb(tint(p.bg, p.green, 0.25)),
|
||||
playing_fg: rgb(p.green),
|
||||
stopped_bg: rgb(tint(p.bg, p.red, 0.25)),
|
||||
stopped_fg: rgb(p.red),
|
||||
fill_on: rgb(p.green),
|
||||
fill_off: rgb(p.fg_muted),
|
||||
fill_bg: rgb(p.surface),
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: rgb(p.accent),
|
||||
cursor_fg: rgb(p.bg),
|
||||
selected_bg: rgb(tint(p.bg, p.accent, 0.30)),
|
||||
selected_fg: rgb(p.accent),
|
||||
in_range_bg: rgb(tint(p.bg, p.accent, 0.20)),
|
||||
in_range_fg: rgb(p.fg),
|
||||
cursor: rgb(p.accent),
|
||||
selected: rgb(tint(p.bg, p.accent, 0.30)),
|
||||
in_range: rgb(tint(p.bg, p.accent, 0.20)),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: rgb(tint(p.bg, p.orange, 0.35)),
|
||||
playing_active_fg: rgb(p.orange),
|
||||
playing_inactive_bg: rgb(tint(p.bg, p.yellow, 0.30)),
|
||||
playing_inactive_fg: rgb(p.yellow),
|
||||
active_bg: rgb(tint(p.bg, p.cyan, 0.25)),
|
||||
active_fg: rgb(p.cyan),
|
||||
content_bg: rgb(tint(p.bg, p.cyan, 0.30)),
|
||||
inactive_bg: rgb(p.surface),
|
||||
inactive_fg: rgb(p.fg_dim),
|
||||
active_selected_bg: rgb(tint(p.bg, p.accent, 0.35)),
|
||||
active_in_range_bg: rgb(tint(p.bg, p.accent, 0.22)),
|
||||
link_bright: p.link_bright,
|
||||
link_dim: p.link_dim,
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: rgb(tint(p.bg, p.tempo_color, 0.30)),
|
||||
tempo_fg: rgb(p.tempo_color),
|
||||
beat_bg: rgb(tint(p.bg, p.tempo_color, 0.45)),
|
||||
bank_bg: rgb(tint(p.bg, p.bank_color, 0.25)),
|
||||
bank_fg: rgb(p.bank_color),
|
||||
pattern_bg: rgb(tint(p.bg, p.pattern_color, 0.25)),
|
||||
pattern_fg: rgb(p.pattern_color),
|
||||
stats_bg: rgb(p.surface),
|
||||
stats_fg: rgb(p.fg_dim),
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: rgb(p.cyan),
|
||||
border_accent: rgb(p.accent),
|
||||
border_warn: rgb(p.orange),
|
||||
border_dim: rgb(p.fg_muted),
|
||||
confirm: rgb(p.orange),
|
||||
rename: rgb(p.purple),
|
||||
input: rgb(p.cyan),
|
||||
editor: rgb(p.cyan),
|
||||
preview: rgb(p.fg_muted),
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: rgb(tint(p.bg, p.red, 0.30)),
|
||||
error_fg: rgb(p.red),
|
||||
success_bg: rgb(tint(p.bg, p.green, 0.25)),
|
||||
success_fg: rgb(p.green),
|
||||
info_bg: rgb(p.surface),
|
||||
info_fg: rgb(p.fg),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: rgb(tint(p.bg, p.green, 0.25)),
|
||||
playing_fg: rgb(p.green),
|
||||
staged_play_bg: rgb(tint(p.bg, p.purple, 0.30)),
|
||||
staged_play_fg: rgb(p.purple),
|
||||
staged_stop_bg: rgb(tint(p.bg, p.red, 0.30)),
|
||||
staged_stop_fg: rgb(p.red),
|
||||
edit_bg: rgb(tint(p.bg, p.cyan, 0.25)),
|
||||
edit_fg: rgb(p.cyan),
|
||||
hover_bg: rgb(p.surface2),
|
||||
hover_fg: rgb(p.fg),
|
||||
muted_bg: rgb(tint(p.bg, p.surface, 0.30)),
|
||||
muted_fg: rgb(p.fg_muted),
|
||||
soloed_bg: rgb(tint(p.bg, p.yellow, 0.30)),
|
||||
soloed_fg: rgb(p.yellow),
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: rgb(p.red),
|
||||
connected: rgb(p.green),
|
||||
listening: rgb(p.yellow),
|
||||
},
|
||||
syntax: syntax_colors(p, darker_bg),
|
||||
table: TableColors {
|
||||
row_even: rgb(darker_bg),
|
||||
row_odd: rgb(p.bg),
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: rgb(p.orange),
|
||||
value: rgb(p.fg_dim),
|
||||
},
|
||||
hint: HintColors {
|
||||
key: rgb(p.orange),
|
||||
text: rgb(p.fg_muted),
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: rgb(p.fg),
|
||||
fg: rgb(p.bg),
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: rgb(tint(p.bg, p.accent, 0.35)),
|
||||
selected_fg: rgb(p.fg),
|
||||
unselected_bg: rgb(p.surface),
|
||||
unselected_fg: rgb(p.fg_muted),
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: rgb(p.fg),
|
||||
cursor_fg: rgb(p.bg),
|
||||
selection_bg: rgb(tint(p.bg, p.accent, 0.30)),
|
||||
completion_bg: rgb(p.surface),
|
||||
completion_fg: rgb(p.fg),
|
||||
completion_selected: rgb(p.orange),
|
||||
completion_example: rgb(p.cyan),
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: rgb(p.blue),
|
||||
project_file: rgb(p.purple),
|
||||
selected: rgb(p.orange),
|
||||
file: rgb(p.fg),
|
||||
focused_border: rgb(p.orange),
|
||||
unfocused_border: rgb(p.fg_muted),
|
||||
root: rgb(p.fg),
|
||||
file_icon: rgb(p.fg_muted),
|
||||
folder_icon: rgb(p.blue),
|
||||
empty_text: rgb(p.fg_muted),
|
||||
},
|
||||
input: InputColors {
|
||||
text: rgb(p.cyan),
|
||||
cursor: rgb(p.fg),
|
||||
hint: rgb(p.fg_muted),
|
||||
},
|
||||
search: SearchColors {
|
||||
active: rgb(p.orange),
|
||||
inactive: rgb(p.fg_muted),
|
||||
match_bg: rgb(p.yellow),
|
||||
match_fg: rgb(p.bg),
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: rgb(p.cyan),
|
||||
h2: rgb(p.orange),
|
||||
h3: rgb(p.purple),
|
||||
code: rgb(p.green),
|
||||
code_border: rgb(mid(p.surface2, p.fg_muted, 0.3)),
|
||||
link: rgb(p.accent),
|
||||
link_url: rgb(mid(p.fg_muted, p.fg_dim, 0.3)),
|
||||
quote: rgb(p.fg_muted),
|
||||
text: rgb(p.fg),
|
||||
list: rgb(p.fg),
|
||||
},
|
||||
engine: engine_colors(p),
|
||||
dict: dict_colors(p),
|
||||
title: TitleColors {
|
||||
big_title: rgb(p.title_accent),
|
||||
author: rgb(p.title_author),
|
||||
link: rgb(p.green),
|
||||
license: rgb(p.orange),
|
||||
prompt: rgb(mid(p.fg_dim, p.fg, 0.3)),
|
||||
subtitle: rgb(p.fg),
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: rgb(p.green),
|
||||
mid: rgb(p.yellow),
|
||||
high: rgb(p.red),
|
||||
low_rgb: p.meter[0],
|
||||
mid_rgb: p.meter[1],
|
||||
high_rgb: p.meter[2],
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: p.sparkle,
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: rgb(p.orange),
|
||||
button_selected_bg: rgb(p.orange),
|
||||
button_selected_fg: rgb(p.bg),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn syntax_colors(p: &Palette, darker_bg: Rgb) -> SyntaxColors {
|
||||
let syn_bg = |accent: Rgb| -> Color { rgb(tint(p.bg, accent, 0.20)) };
|
||||
let interval_fg = mid(p.green, p.fg, 0.3);
|
||||
|
||||
SyntaxColors {
|
||||
gap_bg: rgb(darker_bg),
|
||||
executed_bg: rgb(tint(p.bg, p.purple, 0.15)),
|
||||
selected_bg: rgb(tint(p.bg, p.orange, 0.30)),
|
||||
emit: (rgb(p.fg), syn_bg(p.accent)),
|
||||
number: (rgb(p.purple), syn_bg(p.purple)),
|
||||
string: (rgb(p.green), syn_bg(p.green)),
|
||||
comment: (rgb(p.fg_muted), rgb(darker_bg)),
|
||||
keyword: (rgb(p.accent), syn_bg(p.accent)),
|
||||
stack_op: (rgb(p.blue), syn_bg(p.blue)),
|
||||
operator: (rgb(p.red), syn_bg(p.red)),
|
||||
sound: (rgb(p.cyan), syn_bg(p.cyan)),
|
||||
param: (rgb(p.orange), syn_bg(p.orange)),
|
||||
context: (rgb(p.orange), syn_bg(p.orange)),
|
||||
note: (rgb(p.green), syn_bg(p.green)),
|
||||
interval: (rgb(interval_fg), syn_bg(p.green)),
|
||||
variable: (rgb(p.purple), syn_bg(p.purple)),
|
||||
vary: (rgb(p.yellow), syn_bg(p.yellow)),
|
||||
generator: (rgb(p.cyan), syn_bg(p.cyan)),
|
||||
user_defined: (rgb(p.secondary), syn_bg(p.secondary)),
|
||||
default: (rgb(p.fg_dim), rgb(darker_bg)),
|
||||
}
|
||||
}
|
||||
|
||||
fn engine_colors(p: &Palette) -> EngineColors {
|
||||
let divider = mid(p.surface2, p.fg_muted, 0.2);
|
||||
let label = mid(p.fg_muted, p.fg, 0.4);
|
||||
|
||||
EngineColors {
|
||||
header: rgb(p.cyan),
|
||||
header_focused: rgb(p.yellow),
|
||||
divider: rgb(divider),
|
||||
scroll_indicator: rgb(mid(divider, p.fg_muted, 0.3)),
|
||||
label: rgb(label),
|
||||
label_focused: rgb(mid(label, p.fg, 0.3)),
|
||||
label_dim: rgb(mid(p.fg_muted, label, 0.3)),
|
||||
value: rgb(mid(p.fg_dim, p.fg, 0.5)),
|
||||
focused: rgb(p.yellow),
|
||||
normal: rgb(p.fg),
|
||||
dim: rgb(mid(divider, p.fg_muted, 0.3)),
|
||||
path: rgb(label),
|
||||
border_magenta: rgb(p.purple),
|
||||
border_green: rgb(p.green),
|
||||
border_cyan: rgb(p.cyan),
|
||||
separator: rgb(divider),
|
||||
hint_active: rgb(mid(p.orange, p.yellow, 0.5)),
|
||||
hint_inactive: rgb(divider),
|
||||
}
|
||||
}
|
||||
|
||||
fn dict_colors(p: &Palette) -> DictColors {
|
||||
let divider = mid(p.surface2, p.fg_muted, 0.2);
|
||||
let label = mid(p.fg_muted, p.fg, 0.4);
|
||||
|
||||
DictColors {
|
||||
word_name: rgb(p.green),
|
||||
word_bg: rgb(tint(p.bg, p.cyan, 0.20)),
|
||||
alias: rgb(p.fg_muted),
|
||||
stack_sig: rgb(p.purple),
|
||||
description: rgb(p.fg),
|
||||
example: rgb(label),
|
||||
category_focused: rgb(p.yellow),
|
||||
category_selected: rgb(p.cyan),
|
||||
category_normal: rgb(p.fg),
|
||||
category_dimmed: rgb(mid(divider, p.fg_muted, 0.3)),
|
||||
border_focused: rgb(p.yellow),
|
||||
border_normal: rgb(divider),
|
||||
header_desc: rgb(mid(label, p.fg, 0.3)),
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,287 @@
|
||||
//! Catppuccin Latte palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let crust = Color::Rgb(220, 224, 232);
|
||||
let mantle = Color::Rgb(230, 233, 239);
|
||||
let base = Color::Rgb(239, 241, 245);
|
||||
let surface0 = Color::Rgb(204, 208, 218);
|
||||
let surface1 = Color::Rgb(188, 192, 204);
|
||||
let overlay0 = Color::Rgb(156, 160, 176);
|
||||
let overlay1 = Color::Rgb(140, 143, 161);
|
||||
let subtext0 = Color::Rgb(108, 111, 133);
|
||||
let subtext1 = Color::Rgb(92, 95, 119);
|
||||
let text = Color::Rgb(76, 79, 105);
|
||||
let pink = Color::Rgb(234, 118, 203);
|
||||
let mauve = Color::Rgb(136, 57, 239);
|
||||
let red = Color::Rgb(210, 15, 57);
|
||||
let maroon = Color::Rgb(230, 69, 83);
|
||||
let peach = Color::Rgb(254, 100, 11);
|
||||
let yellow = Color::Rgb(223, 142, 29);
|
||||
let green = Color::Rgb(64, 160, 43);
|
||||
let teal = Color::Rgb(23, 146, 153);
|
||||
let sapphire = Color::Rgb(32, 159, 181);
|
||||
let lavender = Color::Rgb(114, 135, 253);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (239, 241, 245),
|
||||
surface: (204, 208, 218),
|
||||
surface2: (188, 192, 204),
|
||||
fg: (76, 79, 105),
|
||||
fg_dim: (108, 111, 133),
|
||||
fg_muted: (140, 143, 161),
|
||||
accent: (136, 57, 239), // mauve
|
||||
red: (210, 15, 57),
|
||||
green: (64, 160, 43),
|
||||
yellow: (223, 142, 29),
|
||||
blue: (32, 159, 181), // sapphire
|
||||
purple: (136, 57, 239),
|
||||
cyan: (23, 146, 153), // teal
|
||||
orange: (254, 100, 11), // peach
|
||||
tempo_color: (136, 57, 239),
|
||||
bank_color: (32, 159, 181),
|
||||
pattern_color: (23, 146, 153),
|
||||
title_accent: (136, 57, 239),
|
||||
title_author: (114, 135, 253),
|
||||
secondary: (230, 69, 83), // maroon
|
||||
link_bright: [
|
||||
(136, 57, 239), (234, 118, 203), (254, 100, 11),
|
||||
(4, 165, 229), (64, 160, 43),
|
||||
],
|
||||
link_dim: [
|
||||
(210, 200, 240), (240, 210, 230), (250, 220, 200),
|
||||
(200, 230, 240), (210, 235, 210),
|
||||
],
|
||||
sparkle: [
|
||||
(114, 135, 253), (254, 100, 11), (64, 160, 43),
|
||||
(234, 118, 203), (136, 57, 239),
|
||||
],
|
||||
meter: [(50, 150, 40), (200, 140, 30), (200, 40, 50)],
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: base,
|
||||
bg_rgb: (239, 241, 245),
|
||||
text_primary: text,
|
||||
text_muted: subtext0,
|
||||
text_dim: overlay1,
|
||||
border: surface1,
|
||||
header: lavender,
|
||||
unfocused: overlay0,
|
||||
accent: mauve,
|
||||
surface: surface0,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(220, 240, 225),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(245, 220, 225),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: overlay0,
|
||||
fill_bg: surface0,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: mauve,
|
||||
cursor_fg: base,
|
||||
selected_bg: Color::Rgb(200, 200, 230),
|
||||
selected_fg: lavender,
|
||||
in_range_bg: Color::Rgb(210, 210, 235),
|
||||
in_range_fg: subtext1,
|
||||
cursor: mauve,
|
||||
selected: Color::Rgb(200, 200, 230),
|
||||
in_range: Color::Rgb(210, 210, 235),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(250, 220, 210),
|
||||
playing_active_fg: peach,
|
||||
playing_inactive_bg: Color::Rgb(250, 235, 200),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(200, 235, 235),
|
||||
active_fg: teal,
|
||||
content_bg: Color::Rgb(185, 225, 225),
|
||||
inactive_bg: surface0,
|
||||
inactive_fg: subtext0,
|
||||
active_selected_bg: Color::Rgb(215, 210, 240),
|
||||
active_in_range_bg: Color::Rgb(210, 215, 230),
|
||||
link_bright: [
|
||||
(136, 57, 239),
|
||||
(234, 118, 203),
|
||||
(254, 100, 11),
|
||||
(4, 165, 229),
|
||||
(64, 160, 43),
|
||||
],
|
||||
link_dim: [
|
||||
(210, 200, 240),
|
||||
(240, 210, 230),
|
||||
(250, 220, 200),
|
||||
(200, 230, 240),
|
||||
(210, 235, 210),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(220, 210, 240),
|
||||
tempo_fg: mauve,
|
||||
bank_bg: Color::Rgb(200, 230, 235),
|
||||
bank_fg: sapphire,
|
||||
pattern_bg: Color::Rgb(200, 230, 225),
|
||||
pattern_fg: teal,
|
||||
stats_bg: surface0,
|
||||
stats_fg: subtext0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: lavender,
|
||||
border_accent: mauve,
|
||||
border_warn: peach,
|
||||
border_dim: overlay1,
|
||||
confirm: peach,
|
||||
rename: mauve,
|
||||
input: sapphire,
|
||||
editor: lavender,
|
||||
preview: overlay1,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(250, 215, 220),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(210, 240, 215),
|
||||
success_fg: green,
|
||||
info_bg: surface0,
|
||||
info_fg: text,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(210, 235, 220),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(225, 215, 245),
|
||||
staged_play_fg: mauve,
|
||||
staged_stop_bg: Color::Rgb(245, 215, 225),
|
||||
staged_stop_fg: maroon,
|
||||
edit_bg: Color::Rgb(210, 235, 235),
|
||||
edit_fg: teal,
|
||||
hover_bg: surface1,
|
||||
hover_fg: text,
|
||||
muted_bg: Color::Rgb(215, 215, 225),
|
||||
muted_fg: overlay0,
|
||||
soloed_bg: Color::Rgb(250, 235, 200),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: mantle,
|
||||
executed_bg: Color::Rgb(225, 220, 240),
|
||||
selected_bg: Color::Rgb(250, 235, 210),
|
||||
emit: (text, Color::Rgb(250, 220, 215)),
|
||||
number: (peach, Color::Rgb(252, 235, 220)),
|
||||
string: (green, Color::Rgb(215, 240, 215)),
|
||||
comment: (overlay1, crust),
|
||||
keyword: (mauve, Color::Rgb(230, 220, 245)),
|
||||
stack_op: (sapphire, Color::Rgb(215, 230, 240)),
|
||||
operator: (yellow, Color::Rgb(245, 235, 210)),
|
||||
sound: (teal, Color::Rgb(210, 240, 240)),
|
||||
param: (lavender, Color::Rgb(220, 225, 245)),
|
||||
context: (peach, Color::Rgb(252, 235, 220)),
|
||||
note: (green, Color::Rgb(215, 240, 215)),
|
||||
interval: (Color::Rgb(50, 140, 30), Color::Rgb(215, 240, 210)),
|
||||
variable: (pink, Color::Rgb(245, 220, 240)),
|
||||
vary: (yellow, Color::Rgb(245, 235, 210)),
|
||||
generator: (teal, Color::Rgb(210, 240, 235)),
|
||||
user_defined: (maroon, Color::Rgb(245, 225, 230)),
|
||||
default: (subtext0, mantle),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: mantle,
|
||||
row_odd: base,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: peach,
|
||||
value: subtext0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: peach,
|
||||
text: overlay1,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: text, fg: base },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(215, 205, 245),
|
||||
selected_fg: text,
|
||||
unselected_bg: surface0,
|
||||
unselected_fg: overlay1,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: text,
|
||||
cursor_fg: base,
|
||||
selection_bg: Color::Rgb(200, 210, 240),
|
||||
completion_bg: surface0,
|
||||
completion_fg: text,
|
||||
completion_selected: peach,
|
||||
completion_example: teal,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: sapphire,
|
||||
project_file: mauve,
|
||||
selected: peach,
|
||||
file: text,
|
||||
focused_border: peach,
|
||||
unfocused_border: overlay0,
|
||||
root: text,
|
||||
file_icon: overlay1,
|
||||
folder_icon: sapphire,
|
||||
empty_text: overlay1,
|
||||
},
|
||||
input: InputColors {
|
||||
text: sapphire,
|
||||
cursor: text,
|
||||
hint: overlay1,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: peach,
|
||||
inactive: overlay0,
|
||||
match_bg: yellow,
|
||||
match_fg: base,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: sapphire,
|
||||
h2: peach,
|
||||
h3: mauve,
|
||||
code: green,
|
||||
code_border: Color::Rgb(190, 195, 205),
|
||||
link: teal,
|
||||
link_url: Color::Rgb(150, 150, 150),
|
||||
quote: overlay1,
|
||||
text,
|
||||
list: text,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: Color::Rgb(30, 120, 150),
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(180, 185, 195),
|
||||
scroll_indicator: Color::Rgb(160, 165, 175),
|
||||
label: Color::Rgb(100, 105, 120),
|
||||
label_focused: Color::Rgb(70, 75, 90),
|
||||
label_dim: Color::Rgb(120, 125, 140),
|
||||
value: Color::Rgb(60, 65, 80),
|
||||
focused: yellow,
|
||||
normal: text,
|
||||
dim: Color::Rgb(160, 165, 175),
|
||||
path: Color::Rgb(100, 105, 120),
|
||||
border_magenta: mauve,
|
||||
border_green: green,
|
||||
border_cyan: sapphire,
|
||||
separator: Color::Rgb(180, 185, 200),
|
||||
hint_active: Color::Rgb(180, 140, 40),
|
||||
hint_inactive: Color::Rgb(190, 195, 205),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(210, 225, 235),
|
||||
alias: overlay1,
|
||||
stack_sig: mauve,
|
||||
description: text,
|
||||
example: Color::Rgb(100, 105, 115),
|
||||
category_focused: yellow,
|
||||
category_selected: sapphire,
|
||||
category_normal: text,
|
||||
category_dimmed: Color::Rgb(160, 165, 175),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(180, 185, 195),
|
||||
header_desc: Color::Rgb(90, 95, 110),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: mauve,
|
||||
author: lavender,
|
||||
link: teal,
|
||||
license: peach,
|
||||
prompt: Color::Rgb(90, 100, 115),
|
||||
subtitle: text,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (50, 150, 40),
|
||||
mid_rgb: (200, 140, 30),
|
||||
high_rgb: (200, 40, 50),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(114, 135, 253),
|
||||
(254, 100, 11),
|
||||
(64, 160, 43),
|
||||
(234, 118, 203),
|
||||
(136, 57, 239),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: peach,
|
||||
button_selected_bg: peach,
|
||||
button_selected_fg: base,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,290 @@
|
||||
//! Catppuccin Mocha palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let crust = Color::Rgb(17, 17, 27);
|
||||
let mantle = Color::Rgb(24, 24, 37);
|
||||
let base = Color::Rgb(30, 30, 46);
|
||||
let surface0 = Color::Rgb(49, 50, 68);
|
||||
let surface1 = Color::Rgb(69, 71, 90);
|
||||
let overlay0 = Color::Rgb(108, 112, 134);
|
||||
let overlay1 = Color::Rgb(127, 132, 156);
|
||||
let subtext0 = Color::Rgb(166, 173, 200);
|
||||
let subtext1 = Color::Rgb(186, 194, 222);
|
||||
let text = Color::Rgb(205, 214, 244);
|
||||
let pink = Color::Rgb(245, 194, 231);
|
||||
let mauve = Color::Rgb(203, 166, 247);
|
||||
let red = Color::Rgb(243, 139, 168);
|
||||
let maroon = Color::Rgb(235, 160, 172);
|
||||
let peach = Color::Rgb(250, 179, 135);
|
||||
let yellow = Color::Rgb(249, 226, 175);
|
||||
let green = Color::Rgb(166, 227, 161);
|
||||
let teal = Color::Rgb(148, 226, 213);
|
||||
let sapphire = Color::Rgb(116, 199, 236);
|
||||
let lavender = Color::Rgb(180, 190, 254);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (30, 30, 46),
|
||||
surface: (49, 50, 68),
|
||||
surface2: (69, 71, 90),
|
||||
fg: (205, 214, 244),
|
||||
fg_dim: (166, 173, 200),
|
||||
fg_muted: (127, 132, 156),
|
||||
accent: (203, 166, 247), // mauve
|
||||
red: (243, 139, 168),
|
||||
green: (166, 227, 161),
|
||||
yellow: (249, 226, 175),
|
||||
blue: (116, 199, 236), // sapphire
|
||||
purple: (203, 166, 247), // mauve
|
||||
cyan: (148, 226, 213), // teal
|
||||
orange: (250, 179, 135), // peach
|
||||
tempo_color: (203, 166, 247),
|
||||
bank_color: (116, 199, 236),
|
||||
pattern_color: (148, 226, 213),
|
||||
title_accent: (203, 166, 247),
|
||||
title_author: (180, 190, 254),
|
||||
secondary: (235, 160, 172), // maroon
|
||||
link_bright: [
|
||||
(203, 166, 247), (245, 194, 231), (250, 179, 135),
|
||||
(137, 220, 235), (166, 227, 161),
|
||||
],
|
||||
link_dim: [
|
||||
(70, 55, 85), (85, 65, 80), (85, 60, 45),
|
||||
(45, 75, 80), (55, 80, 55),
|
||||
],
|
||||
sparkle: [
|
||||
(200, 220, 255), (250, 179, 135), (166, 227, 161),
|
||||
(245, 194, 231), (203, 166, 247),
|
||||
],
|
||||
meter: [(40, 180, 80), (220, 180, 40), (220, 60, 40)],
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: base,
|
||||
bg_rgb: (30, 30, 46),
|
||||
text_primary: text,
|
||||
text_muted: subtext0,
|
||||
text_dim: overlay1,
|
||||
border: surface1,
|
||||
header: lavender,
|
||||
unfocused: overlay0,
|
||||
accent: mauve,
|
||||
surface: surface0,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(30, 50, 40),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(50, 30, 40),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: overlay0,
|
||||
fill_bg: surface0,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: mauve,
|
||||
cursor_fg: crust,
|
||||
selected_bg: Color::Rgb(60, 60, 90),
|
||||
selected_fg: lavender,
|
||||
in_range_bg: Color::Rgb(50, 50, 75),
|
||||
in_range_fg: subtext1,
|
||||
cursor: mauve,
|
||||
selected: Color::Rgb(60, 60, 90),
|
||||
in_range: Color::Rgb(50, 50, 75),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(80, 50, 60),
|
||||
playing_active_fg: peach,
|
||||
playing_inactive_bg: Color::Rgb(70, 55, 45),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(40, 55, 55),
|
||||
active_fg: teal,
|
||||
content_bg: Color::Rgb(47, 62, 62),
|
||||
inactive_bg: surface0,
|
||||
inactive_fg: subtext0,
|
||||
active_selected_bg: Color::Rgb(70, 60, 80),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 70),
|
||||
link_bright: [
|
||||
(203, 166, 247),
|
||||
(245, 194, 231),
|
||||
(250, 179, 135),
|
||||
(137, 220, 235),
|
||||
(166, 227, 161),
|
||||
],
|
||||
link_dim: [
|
||||
(70, 55, 85),
|
||||
(85, 65, 80),
|
||||
(85, 60, 45),
|
||||
(45, 75, 80),
|
||||
(55, 80, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 40, 60),
|
||||
tempo_fg: mauve,
|
||||
bank_bg: Color::Rgb(35, 50, 55),
|
||||
bank_fg: sapphire,
|
||||
pattern_bg: Color::Rgb(40, 50, 50),
|
||||
pattern_fg: teal,
|
||||
stats_bg: surface0,
|
||||
stats_fg: subtext0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: lavender,
|
||||
border_accent: mauve,
|
||||
border_warn: peach,
|
||||
border_dim: overlay1,
|
||||
confirm: peach,
|
||||
rename: mauve,
|
||||
input: sapphire,
|
||||
editor: lavender,
|
||||
preview: overlay1,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(50, 30, 40),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(30, 50, 40),
|
||||
success_fg: green,
|
||||
info_bg: surface0,
|
||||
info_fg: text,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(35, 55, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(55, 45, 65),
|
||||
staged_play_fg: mauve,
|
||||
staged_stop_bg: Color::Rgb(60, 40, 50),
|
||||
staged_stop_fg: maroon,
|
||||
edit_bg: Color::Rgb(40, 55, 55),
|
||||
edit_fg: teal,
|
||||
hover_bg: surface1,
|
||||
hover_fg: text,
|
||||
muted_bg: Color::Rgb(40, 40, 50),
|
||||
muted_fg: overlay0,
|
||||
soloed_bg: Color::Rgb(60, 55, 35),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: mantle,
|
||||
executed_bg: Color::Rgb(45, 40, 55),
|
||||
selected_bg: Color::Rgb(70, 55, 40),
|
||||
emit: (text, Color::Rgb(80, 50, 60)),
|
||||
number: (peach, Color::Rgb(55, 45, 35)),
|
||||
string: (green, Color::Rgb(35, 50, 40)),
|
||||
comment: (overlay1, crust),
|
||||
keyword: (mauve, Color::Rgb(50, 40, 60)),
|
||||
stack_op: (sapphire, Color::Rgb(35, 45, 55)),
|
||||
operator: (yellow, Color::Rgb(55, 50, 35)),
|
||||
sound: (teal, Color::Rgb(35, 55, 55)),
|
||||
param: (lavender, Color::Rgb(45, 45, 60)),
|
||||
context: (peach, Color::Rgb(55, 45, 35)),
|
||||
note: (green, Color::Rgb(35, 50, 40)),
|
||||
interval: (Color::Rgb(180, 230, 150), Color::Rgb(40, 55, 35)),
|
||||
variable: (pink, Color::Rgb(55, 40, 55)),
|
||||
vary: (yellow, Color::Rgb(55, 50, 35)),
|
||||
generator: (teal, Color::Rgb(35, 55, 50)),
|
||||
user_defined: (maroon, Color::Rgb(55, 35, 40)),
|
||||
default: (subtext0, mantle),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: mantle,
|
||||
row_odd: base,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: peach,
|
||||
value: subtext0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: peach,
|
||||
text: overlay1,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: text,
|
||||
fg: crust,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 75),
|
||||
selected_fg: text,
|
||||
unselected_bg: surface0,
|
||||
unselected_fg: overlay1,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: text,
|
||||
cursor_fg: crust,
|
||||
selection_bg: Color::Rgb(50, 60, 90),
|
||||
completion_bg: surface0,
|
||||
completion_fg: text,
|
||||
completion_selected: peach,
|
||||
completion_example: teal,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: sapphire,
|
||||
project_file: mauve,
|
||||
selected: peach,
|
||||
file: text,
|
||||
focused_border: peach,
|
||||
unfocused_border: overlay0,
|
||||
root: text,
|
||||
file_icon: overlay1,
|
||||
folder_icon: sapphire,
|
||||
empty_text: overlay1,
|
||||
},
|
||||
input: InputColors {
|
||||
text: sapphire,
|
||||
cursor: text,
|
||||
hint: overlay1,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: peach,
|
||||
inactive: overlay0,
|
||||
match_bg: yellow,
|
||||
match_fg: crust,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: sapphire,
|
||||
h2: peach,
|
||||
h3: mauve,
|
||||
code: green,
|
||||
code_border: Color::Rgb(60, 60, 70),
|
||||
link: teal,
|
||||
link_url: Color::Rgb(100, 100, 100),
|
||||
quote: overlay1,
|
||||
text,
|
||||
list: text,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: Color::Rgb(100, 160, 180),
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(60, 65, 70),
|
||||
scroll_indicator: Color::Rgb(80, 85, 95),
|
||||
label: Color::Rgb(120, 125, 135),
|
||||
label_focused: Color::Rgb(150, 155, 165),
|
||||
label_dim: Color::Rgb(100, 105, 115),
|
||||
value: Color::Rgb(180, 180, 190),
|
||||
focused: yellow,
|
||||
normal: text,
|
||||
dim: Color::Rgb(80, 85, 95),
|
||||
path: Color::Rgb(120, 125, 135),
|
||||
border_magenta: mauve,
|
||||
border_green: green,
|
||||
border_cyan: sapphire,
|
||||
separator: Color::Rgb(60, 65, 75),
|
||||
hint_active: Color::Rgb(180, 180, 100),
|
||||
hint_inactive: Color::Rgb(60, 60, 70),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(40, 50, 60),
|
||||
alias: overlay1,
|
||||
stack_sig: mauve,
|
||||
description: text,
|
||||
example: Color::Rgb(120, 130, 140),
|
||||
category_focused: yellow,
|
||||
category_selected: sapphire,
|
||||
category_normal: text,
|
||||
category_dimmed: Color::Rgb(80, 80, 90),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(60, 60, 70),
|
||||
header_desc: Color::Rgb(140, 145, 155),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: mauve,
|
||||
author: lavender,
|
||||
link: teal,
|
||||
license: peach,
|
||||
prompt: Color::Rgb(140, 160, 170),
|
||||
subtitle: text,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (40, 180, 80),
|
||||
mid_rgb: (220, 180, 40),
|
||||
high_rgb: (220, 60, 40),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(200, 220, 255),
|
||||
(250, 179, 135),
|
||||
(166, 227, 161),
|
||||
(245, 194, 231),
|
||||
(203, 166, 247),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: peach,
|
||||
button_selected_bg: peach,
|
||||
button_selected_fg: crust,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,284 @@
|
||||
//! Dracula palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let background = Color::Rgb(40, 42, 54);
|
||||
let current_line = Color::Rgb(68, 71, 90);
|
||||
let foreground = Color::Rgb(248, 248, 242);
|
||||
let comment = Color::Rgb(98, 114, 164);
|
||||
let cyan = Color::Rgb(139, 233, 253);
|
||||
let green = Color::Rgb(80, 250, 123);
|
||||
let orange = Color::Rgb(255, 184, 108);
|
||||
let pink = Color::Rgb(255, 121, 198);
|
||||
let purple = Color::Rgb(189, 147, 249);
|
||||
let red = Color::Rgb(255, 85, 85);
|
||||
let yellow = Color::Rgb(241, 250, 140);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (40, 42, 54),
|
||||
surface: (68, 71, 90),
|
||||
surface2: (55, 57, 70),
|
||||
fg: (248, 248, 242),
|
||||
fg_dim: (98, 114, 164),
|
||||
fg_muted: (80, 85, 110),
|
||||
accent: (189, 147, 249), // purple
|
||||
red: (255, 85, 85),
|
||||
green: (80, 250, 123),
|
||||
yellow: (241, 250, 140),
|
||||
blue: (139, 233, 253), // cyan
|
||||
purple: (189, 147, 249),
|
||||
cyan: (139, 233, 253),
|
||||
orange: (255, 184, 108),
|
||||
tempo_color: (189, 147, 249),
|
||||
bank_color: (139, 233, 253),
|
||||
pattern_color: (80, 250, 123),
|
||||
title_accent: (189, 147, 249),
|
||||
title_author: (255, 121, 198),
|
||||
secondary: (255, 184, 108),
|
||||
link_bright: [
|
||||
(189, 147, 249), (255, 121, 198), (255, 184, 108),
|
||||
(139, 233, 253), (80, 250, 123),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 60, 95), (95, 55, 80), (95, 70, 50),
|
||||
(55, 90, 95), (40, 95, 55),
|
||||
],
|
||||
sparkle: [
|
||||
(189, 147, 249), (255, 184, 108), (80, 250, 123),
|
||||
(255, 121, 198), (139, 233, 253),
|
||||
],
|
||||
meter: [(70, 230, 110), (230, 240, 130), (240, 80, 80)],
|
||||
let darker_bg = Color::Rgb(33, 34, 44);
|
||||
let lighter_bg = Color::Rgb(55, 57, 70);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: background,
|
||||
bg_rgb: (40, 42, 54),
|
||||
text_primary: foreground,
|
||||
text_muted: comment,
|
||||
text_dim: Color::Rgb(80, 85, 110),
|
||||
border: current_line,
|
||||
header: purple,
|
||||
unfocused: comment,
|
||||
accent: purple,
|
||||
surface: current_line,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 60, 50),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(65, 45, 50),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: current_line,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: purple,
|
||||
cursor_fg: background,
|
||||
selected_bg: Color::Rgb(80, 75, 110),
|
||||
selected_fg: purple,
|
||||
in_range_bg: Color::Rgb(65, 65, 90),
|
||||
in_range_fg: foreground,
|
||||
cursor: purple,
|
||||
selected: Color::Rgb(80, 75, 110),
|
||||
in_range: Color::Rgb(65, 65, 90),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(85, 60, 65),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 55),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(50, 70, 70),
|
||||
active_fg: cyan,
|
||||
content_bg: Color::Rgb(57, 77, 77),
|
||||
inactive_bg: current_line,
|
||||
inactive_fg: comment,
|
||||
active_selected_bg: Color::Rgb(80, 70, 95),
|
||||
active_in_range_bg: Color::Rgb(65, 65, 85),
|
||||
link_bright: [
|
||||
(189, 147, 249),
|
||||
(255, 121, 198),
|
||||
(255, 184, 108),
|
||||
(139, 233, 253),
|
||||
(80, 250, 123),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 60, 95),
|
||||
(95, 55, 80),
|
||||
(95, 70, 50),
|
||||
(55, 90, 95),
|
||||
(40, 95, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 50, 75),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(45, 65, 70),
|
||||
bank_fg: cyan,
|
||||
pattern_bg: Color::Rgb(40, 70, 60),
|
||||
pattern_fg: green,
|
||||
stats_bg: current_line,
|
||||
stats_fg: comment,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: purple,
|
||||
border_accent: pink,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: cyan,
|
||||
editor: purple,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(70, 45, 50),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(40, 65, 50),
|
||||
success_fg: green,
|
||||
info_bg: current_line,
|
||||
info_fg: foreground,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(40, 65, 50),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 85),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(80, 50, 60),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(45, 70, 70),
|
||||
edit_fg: cyan,
|
||||
hover_bg: lighter_bg,
|
||||
hover_fg: foreground,
|
||||
muted_bg: Color::Rgb(50, 52, 65),
|
||||
muted_fg: comment,
|
||||
soloed_bg: Color::Rgb(70, 70, 50),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 70),
|
||||
selected_bg: Color::Rgb(85, 70, 50),
|
||||
emit: (foreground, Color::Rgb(85, 55, 65)),
|
||||
number: (orange, Color::Rgb(75, 55, 45)),
|
||||
string: (yellow, Color::Rgb(70, 70, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (pink, Color::Rgb(80, 50, 70)),
|
||||
stack_op: (cyan, Color::Rgb(45, 65, 75)),
|
||||
operator: (green, Color::Rgb(40, 70, 50)),
|
||||
sound: (cyan, Color::Rgb(45, 70, 70)),
|
||||
param: (purple, Color::Rgb(60, 50, 75)),
|
||||
context: (orange, Color::Rgb(75, 55, 45)),
|
||||
note: (green, Color::Rgb(40, 70, 50)),
|
||||
interval: (Color::Rgb(120, 255, 150), Color::Rgb(40, 75, 50)),
|
||||
variable: (pink, Color::Rgb(80, 50, 65)),
|
||||
vary: (yellow, Color::Rgb(70, 70, 45)),
|
||||
generator: (cyan, Color::Rgb(45, 70, 65)),
|
||||
user_defined: (orange, Color::Rgb(65, 55, 40)),
|
||||
default: (comment, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: background,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: comment,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: foreground,
|
||||
fg: background,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(75, 65, 100),
|
||||
selected_fg: foreground,
|
||||
unselected_bg: current_line,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: foreground,
|
||||
cursor_fg: background,
|
||||
selection_bg: Color::Rgb(70, 75, 105),
|
||||
completion_bg: current_line,
|
||||
completion_fg: foreground,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: cyan,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: foreground,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: foreground,
|
||||
file_icon: comment,
|
||||
folder_icon: cyan,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: cyan,
|
||||
cursor: foreground,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: background,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: cyan,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(85, 90, 110),
|
||||
link: pink,
|
||||
link_url: Color::Rgb(120, 130, 150),
|
||||
quote: comment,
|
||||
text: foreground,
|
||||
list: foreground,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: cyan,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(80, 85, 105),
|
||||
scroll_indicator: Color::Rgb(95, 100, 120),
|
||||
label: Color::Rgb(140, 145, 165),
|
||||
label_focused: Color::Rgb(170, 175, 195),
|
||||
label_dim: Color::Rgb(110, 115, 135),
|
||||
value: Color::Rgb(200, 205, 220),
|
||||
focused: yellow,
|
||||
normal: foreground,
|
||||
dim: Color::Rgb(95, 100, 120),
|
||||
path: Color::Rgb(140, 145, 165),
|
||||
border_magenta: pink,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(80, 85, 105),
|
||||
hint_active: Color::Rgb(220, 200, 100),
|
||||
hint_inactive: Color::Rgb(80, 85, 105),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 65, 80),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: foreground,
|
||||
example: Color::Rgb(140, 145, 165),
|
||||
category_focused: yellow,
|
||||
category_selected: cyan,
|
||||
category_normal: foreground,
|
||||
category_dimmed: Color::Rgb(95, 100, 120),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(80, 85, 105),
|
||||
header_desc: Color::Rgb(160, 165, 185),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: purple,
|
||||
author: pink,
|
||||
link: cyan,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(160, 165, 185),
|
||||
subtitle: foreground,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (70, 230, 110),
|
||||
mid_rgb: (230, 240, 130),
|
||||
high_rgb: (240, 80, 80),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(189, 147, 249),
|
||||
(255, 184, 108),
|
||||
(80, 250, 123),
|
||||
(255, 121, 198),
|
||||
(139, 233, 253),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: background,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,284 @@
|
||||
//! Eden palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(8, 12, 8);
|
||||
let surface = Color::Rgb(16, 24, 16);
|
||||
let surface2 = Color::Rgb(24, 32, 24);
|
||||
let border = Color::Rgb(32, 48, 32);
|
||||
let fg = Color::Rgb(200, 216, 192);
|
||||
let fg_dim = Color::Rgb(122, 144, 112);
|
||||
let fg_muted = Color::Rgb(64, 88, 56);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (8, 12, 8),
|
||||
surface: (16, 24, 16),
|
||||
surface2: (32, 48, 32),
|
||||
fg: (200, 216, 192),
|
||||
fg_dim: (122, 144, 112),
|
||||
fg_muted: (64, 88, 56),
|
||||
accent: (64, 192, 64), // green
|
||||
red: (192, 80, 64),
|
||||
green: (96, 224, 96), // bright_green
|
||||
yellow: (160, 160, 64),
|
||||
blue: (80, 128, 160),
|
||||
purple: (128, 104, 144),
|
||||
cyan: (80, 168, 144),
|
||||
orange: (176, 128, 48),
|
||||
tempo_color: (128, 104, 144),
|
||||
bank_color: (80, 128, 160),
|
||||
pattern_color: (80, 168, 144),
|
||||
title_accent: (64, 192, 64),
|
||||
title_author: (80, 168, 144),
|
||||
secondary: (176, 128, 48),
|
||||
link_bright: [
|
||||
(64, 192, 64), (80, 168, 144), (160, 160, 64),
|
||||
(80, 128, 160), (192, 80, 64),
|
||||
],
|
||||
link_dim: [
|
||||
(14, 38, 14), (16, 34, 28), (32, 32, 14),
|
||||
(16, 26, 32), (38, 16, 14),
|
||||
],
|
||||
sparkle: [
|
||||
(64, 192, 64), (96, 224, 96), (80, 168, 144),
|
||||
(160, 160, 64), (48, 128, 48),
|
||||
],
|
||||
meter: [(64, 192, 64), (160, 160, 64), (192, 80, 64)],
|
||||
let green = Color::Rgb(64, 192, 64);
|
||||
let bright_green = Color::Rgb(96, 224, 96);
|
||||
let dark_green = Color::Rgb(48, 128, 48);
|
||||
let cyan = Color::Rgb(80, 168, 144);
|
||||
let yellow = Color::Rgb(160, 160, 64);
|
||||
let red = Color::Rgb(192, 80, 64);
|
||||
let orange = Color::Rgb(176, 128, 48);
|
||||
let blue = Color::Rgb(80, 128, 160);
|
||||
let purple = Color::Rgb(128, 104, 144);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (8, 12, 8),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: green,
|
||||
unfocused: fg_muted,
|
||||
accent: green,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(14, 30, 14),
|
||||
playing_fg: bright_green,
|
||||
stopped_bg: Color::Rgb(36, 16, 14),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: dark_green,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: green,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(20, 40, 20),
|
||||
selected_fg: bright_green,
|
||||
in_range_bg: Color::Rgb(16, 30, 16),
|
||||
in_range_fg: fg,
|
||||
cursor: green,
|
||||
selected: Color::Rgb(20, 40, 20),
|
||||
in_range: Color::Rgb(16, 30, 16),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(16, 38, 16),
|
||||
playing_active_fg: bright_green,
|
||||
playing_inactive_bg: Color::Rgb(28, 36, 14),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(14, 28, 26),
|
||||
active_fg: cyan,
|
||||
content_bg: Color::Rgb(21, 35, 33),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(22, 36, 28),
|
||||
active_in_range_bg: Color::Rgb(16, 28, 20),
|
||||
link_bright: [
|
||||
(64, 192, 64),
|
||||
(80, 168, 144),
|
||||
(160, 160, 64),
|
||||
(80, 128, 160),
|
||||
(192, 80, 64),
|
||||
],
|
||||
link_dim: [
|
||||
(14, 38, 14),
|
||||
(16, 34, 28),
|
||||
(32, 32, 14),
|
||||
(16, 26, 32),
|
||||
(38, 16, 14),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(26, 22, 30),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(16, 26, 32),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(14, 28, 26),
|
||||
pattern_fg: cyan,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: green,
|
||||
border_accent: bright_green,
|
||||
border_warn: yellow,
|
||||
border_dim: fg_muted,
|
||||
confirm: red,
|
||||
rename: green,
|
||||
input: cyan,
|
||||
editor: green,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(40, 16, 14),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(14, 32, 14),
|
||||
success_fg: bright_green,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(14, 32, 14),
|
||||
playing_fg: bright_green,
|
||||
staged_play_bg: Color::Rgb(26, 22, 30),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(40, 16, 14),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(14, 28, 26),
|
||||
edit_fg: cyan,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(10, 14, 10),
|
||||
muted_fg: fg_muted,
|
||||
soloed_bg: Color::Rgb(30, 30, 14),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: bright_green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(14, 24, 14),
|
||||
selected_bg: Color::Rgb(20, 40, 16),
|
||||
emit: (fg, Color::Rgb(20, 34, 20)),
|
||||
number: (yellow, Color::Rgb(30, 30, 12)),
|
||||
string: (bright_green, Color::Rgb(16, 32, 16)),
|
||||
comment: (fg_muted, bg),
|
||||
keyword: (red, Color::Rgb(38, 18, 14)),
|
||||
stack_op: (blue, Color::Rgb(16, 26, 32)),
|
||||
operator: (green, Color::Rgb(14, 36, 14)),
|
||||
sound: (cyan, Color::Rgb(16, 30, 28)),
|
||||
param: (purple, Color::Rgb(26, 22, 28)),
|
||||
context: (orange, Color::Rgb(34, 26, 12)),
|
||||
note: (bright_green, Color::Rgb(16, 34, 16)),
|
||||
interval: (Color::Rgb(100, 180, 100), Color::Rgb(18, 34, 18)),
|
||||
variable: (purple, Color::Rgb(26, 22, 28)),
|
||||
vary: (yellow, Color::Rgb(30, 30, 12)),
|
||||
generator: (cyan, Color::Rgb(16, 30, 26)),
|
||||
user_defined: (orange, Color::Rgb(30, 22, 10)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: green,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: green,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(18, 38, 18),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(24, 48, 24),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: green,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: green,
|
||||
selected: bright_green,
|
||||
file: fg,
|
||||
focused_border: green,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: blue,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: green,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: green,
|
||||
inactive: fg_muted,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: green,
|
||||
h2: cyan,
|
||||
h3: purple,
|
||||
code: bright_green,
|
||||
code_border: Color::Rgb(28, 42, 28),
|
||||
link: cyan,
|
||||
link_url: Color::Rgb(56, 72, 52),
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: green,
|
||||
header_focused: bright_green,
|
||||
divider: Color::Rgb(24, 36, 24),
|
||||
scroll_indicator: Color::Rgb(36, 52, 36),
|
||||
label: Color::Rgb(100, 120, 92),
|
||||
label_focused: Color::Rgb(140, 160, 130),
|
||||
label_dim: Color::Rgb(68, 84, 60),
|
||||
value: Color::Rgb(180, 196, 172),
|
||||
focused: bright_green,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(36, 52, 36),
|
||||
path: Color::Rgb(100, 120, 92),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(24, 36, 24),
|
||||
hint_active: green,
|
||||
hint_inactive: Color::Rgb(24, 36, 24),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: bright_green,
|
||||
word_bg: Color::Rgb(14, 24, 14),
|
||||
alias: fg_muted,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(100, 120, 92),
|
||||
category_focused: bright_green,
|
||||
category_selected: green,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(36, 52, 36),
|
||||
border_focused: bright_green,
|
||||
border_normal: Color::Rgb(24, 36, 24),
|
||||
header_desc: Color::Rgb(120, 140, 112),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: green,
|
||||
author: cyan,
|
||||
link: bright_green,
|
||||
license: yellow,
|
||||
prompt: Color::Rgb(100, 120, 92),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (64, 192, 64),
|
||||
mid_rgb: (160, 160, 64),
|
||||
high_rgb: (192, 80, 64),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(64, 192, 64),
|
||||
(96, 224, 96),
|
||||
(80, 168, 144),
|
||||
(160, 160, 64),
|
||||
(48, 128, 48),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: red,
|
||||
button_selected_bg: green,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,282 @@
|
||||
//! Ember palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(10, 8, 8);
|
||||
let surface = Color::Rgb(20, 16, 16);
|
||||
let surface2 = Color::Rgb(26, 20, 20);
|
||||
let border = Color::Rgb(42, 32, 32);
|
||||
let fg = Color::Rgb(232, 221, 208);
|
||||
let fg_dim = Color::Rgb(160, 144, 128);
|
||||
let fg_muted = Color::Rgb(96, 80, 64);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (10, 8, 8),
|
||||
surface: (20, 16, 16),
|
||||
surface2: (42, 32, 32),
|
||||
fg: (232, 221, 208),
|
||||
fg_dim: (160, 144, 128),
|
||||
fg_muted: (96, 80, 64),
|
||||
accent: (224, 128, 48), // orange
|
||||
red: (224, 80, 64),
|
||||
green: (128, 160, 80),
|
||||
yellow: (208, 160, 48),
|
||||
blue: (96, 128, 176),
|
||||
purple: (160, 112, 144),
|
||||
cyan: (112, 160, 160),
|
||||
orange: (224, 128, 48),
|
||||
tempo_color: (160, 112, 144),
|
||||
bank_color: (96, 128, 176),
|
||||
pattern_color: (112, 160, 160),
|
||||
title_accent: (224, 128, 48),
|
||||
title_author: (224, 80, 64),
|
||||
secondary: (160, 112, 144),
|
||||
link_bright: [
|
||||
(224, 128, 48), (224, 80, 64), (208, 160, 48),
|
||||
(112, 160, 160), (128, 160, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(45, 28, 14), (45, 18, 14), (42, 32, 12),
|
||||
(22, 32, 32), (26, 32, 18),
|
||||
],
|
||||
sparkle: [
|
||||
(224, 128, 48), (224, 80, 64), (208, 160, 48),
|
||||
(112, 160, 160), (128, 160, 80),
|
||||
],
|
||||
meter: [(128, 160, 80), (208, 160, 48), (224, 80, 64)],
|
||||
let red = Color::Rgb(224, 80, 64);
|
||||
let orange = Color::Rgb(224, 128, 48);
|
||||
let yellow = Color::Rgb(208, 160, 48);
|
||||
let green = Color::Rgb(128, 160, 80);
|
||||
let cyan = Color::Rgb(112, 160, 160);
|
||||
let purple = Color::Rgb(160, 112, 144);
|
||||
let blue = Color::Rgb(96, 128, 176);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (10, 8, 8),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: orange,
|
||||
unfocused: fg_muted,
|
||||
accent: orange,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(20, 28, 16),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(40, 16, 14),
|
||||
stopped_fg: red,
|
||||
fill_on: orange,
|
||||
fill_off: fg_muted,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: orange,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(50, 35, 20),
|
||||
selected_fg: orange,
|
||||
in_range_bg: Color::Rgb(35, 25, 18),
|
||||
in_range_fg: fg,
|
||||
cursor: orange,
|
||||
selected: Color::Rgb(50, 35, 20),
|
||||
in_range: Color::Rgb(35, 25, 18),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(45, 25, 15),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(40, 32, 12),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(20, 30, 30),
|
||||
active_fg: cyan,
|
||||
content_bg: Color::Rgb(27, 37, 37),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(40, 30, 35),
|
||||
active_in_range_bg: Color::Rgb(30, 25, 25),
|
||||
link_bright: [
|
||||
(224, 128, 48),
|
||||
(224, 80, 64),
|
||||
(208, 160, 48),
|
||||
(112, 160, 160),
|
||||
(128, 160, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(45, 28, 14),
|
||||
(45, 18, 14),
|
||||
(42, 32, 12),
|
||||
(22, 32, 32),
|
||||
(26, 32, 18),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(40, 28, 30),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(22, 30, 40),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(22, 34, 34),
|
||||
pattern_fg: cyan,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: orange,
|
||||
border_accent: red,
|
||||
border_warn: yellow,
|
||||
border_dim: fg_muted,
|
||||
confirm: red,
|
||||
rename: orange,
|
||||
input: cyan,
|
||||
editor: orange,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(50, 16, 14),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(20, 35, 18),
|
||||
success_fg: green,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(20, 35, 18),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(35, 25, 30),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(45, 18, 16),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(20, 32, 32),
|
||||
edit_fg: cyan,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(18, 14, 14),
|
||||
muted_fg: fg_muted,
|
||||
soloed_bg: Color::Rgb(40, 32, 12),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(25, 18, 14),
|
||||
selected_bg: Color::Rgb(45, 30, 15),
|
||||
emit: (fg, Color::Rgb(45, 20, 18)),
|
||||
number: (yellow, Color::Rgb(40, 30, 12)),
|
||||
string: (green, Color::Rgb(25, 30, 18)),
|
||||
comment: (fg_muted, bg),
|
||||
keyword: (red, Color::Rgb(42, 18, 15)),
|
||||
stack_op: (blue, Color::Rgb(22, 28, 38)),
|
||||
operator: (orange, Color::Rgb(42, 26, 12)),
|
||||
sound: (cyan, Color::Rgb(22, 32, 32)),
|
||||
param: (purple, Color::Rgb(32, 24, 28)),
|
||||
context: (orange, Color::Rgb(42, 26, 12)),
|
||||
note: (green, Color::Rgb(25, 30, 18)),
|
||||
interval: (Color::Rgb(150, 180, 100), Color::Rgb(28, 34, 20)),
|
||||
variable: (purple, Color::Rgb(32, 24, 28)),
|
||||
vary: (yellow, Color::Rgb(40, 30, 12)),
|
||||
generator: (cyan, Color::Rgb(22, 32, 30)),
|
||||
user_defined: (purple, Color::Rgb(28, 20, 26)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(45, 30, 20),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(50, 35, 25),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: orange,
|
||||
selected: red,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: blue,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: orange,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: fg_muted,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: orange,
|
||||
h2: red,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(50, 38, 38),
|
||||
link: cyan,
|
||||
link_url: Color::Rgb(80, 68, 56),
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: orange,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(38, 30, 30),
|
||||
scroll_indicator: Color::Rgb(55, 42, 42),
|
||||
label: Color::Rgb(130, 115, 100),
|
||||
label_focused: Color::Rgb(170, 155, 140),
|
||||
label_dim: Color::Rgb(90, 76, 64),
|
||||
value: Color::Rgb(200, 188, 175),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(55, 42, 42),
|
||||
path: Color::Rgb(130, 115, 100),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(38, 30, 30),
|
||||
hint_active: orange,
|
||||
hint_inactive: Color::Rgb(38, 30, 30),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(22, 28, 22),
|
||||
alias: fg_muted,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 115, 100),
|
||||
category_focused: yellow,
|
||||
category_selected: orange,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(55, 42, 42),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(38, 30, 30),
|
||||
header_desc: Color::Rgb(145, 130, 115),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: orange,
|
||||
author: red,
|
||||
link: cyan,
|
||||
license: yellow,
|
||||
prompt: Color::Rgb(130, 115, 100),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (128, 160, 80),
|
||||
mid_rgb: (208, 160, 48),
|
||||
high_rgb: (224, 80, 64),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(224, 128, 48),
|
||||
(224, 80, 64),
|
||||
(208, 160, 48),
|
||||
(112, 160, 160),
|
||||
(128, 160, 80),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: red,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
//! Everforest palette.
|
||||
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (45, 53, 59),
|
||||
surface: (52, 62, 68),
|
||||
surface2: (68, 80, 86),
|
||||
fg: (211, 198, 170),
|
||||
fg_dim: (135, 131, 116),
|
||||
fg_muted: (80, 80, 68),
|
||||
accent: (167, 192, 128),
|
||||
red: (230, 126, 128),
|
||||
green: (167, 192, 128),
|
||||
yellow: (219, 188, 127),
|
||||
blue: (127, 187, 179),
|
||||
purple: (214, 153, 182),
|
||||
cyan: (131, 192, 146),
|
||||
orange: (230, 152, 117),
|
||||
tempo_color: (214, 153, 182),
|
||||
bank_color: (127, 187, 179),
|
||||
pattern_color: (131, 192, 146),
|
||||
title_accent: (167, 192, 128),
|
||||
title_author: (127, 187, 179),
|
||||
secondary: (230, 152, 117),
|
||||
link_bright: [
|
||||
(167, 192, 128), (214, 153, 182), (230, 152, 117),
|
||||
(127, 187, 179), (219, 188, 127),
|
||||
],
|
||||
link_dim: [
|
||||
(56, 66, 46), (70, 52, 62), (72, 52, 42),
|
||||
(44, 64, 60), (70, 62, 44),
|
||||
],
|
||||
sparkle: [
|
||||
(167, 192, 128), (230, 152, 117), (131, 192, 146),
|
||||
(214, 153, 182), (219, 188, 127),
|
||||
],
|
||||
meter: [(148, 172, 110), (200, 170, 108), (210, 108, 110)],
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,282 @@
|
||||
//! Fairyfloss palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(90, 84, 117);
|
||||
let bg_light = Color::Rgb(113, 103, 153);
|
||||
let bg_lighter = Color::Rgb(130, 120, 165);
|
||||
let fg = Color::Rgb(248, 248, 240);
|
||||
let fg_dim = Color::Rgb(197, 163, 255);
|
||||
let muted = Color::Rgb(168, 164, 177);
|
||||
let dark = Color::Rgb(55, 51, 72);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (90, 84, 117),
|
||||
surface: (113, 103, 153),
|
||||
surface2: (130, 120, 165),
|
||||
fg: (248, 248, 240),
|
||||
fg_dim: (197, 163, 255),
|
||||
fg_muted: (168, 164, 177),
|
||||
accent: (255, 184, 209), // pink
|
||||
red: (255, 133, 127), // coral
|
||||
green: (194, 255, 223), // mint
|
||||
yellow: (255, 243, 82),
|
||||
blue: (197, 163, 255), // lavender
|
||||
purple: (174, 129, 255),
|
||||
cyan: (194, 255, 223), // mint
|
||||
orange: (255, 133, 127), // coral
|
||||
tempo_color: (255, 184, 209),
|
||||
bank_color: (194, 255, 223),
|
||||
pattern_color: (174, 129, 255),
|
||||
title_accent: (255, 184, 209),
|
||||
title_author: (194, 255, 223),
|
||||
secondary: (255, 133, 127),
|
||||
link_bright: [
|
||||
(255, 184, 209), (174, 129, 255), (255, 133, 127),
|
||||
(194, 255, 223), (255, 243, 82),
|
||||
],
|
||||
link_dim: [
|
||||
(100, 75, 90), (85, 70, 105), (100, 65, 65),
|
||||
(75, 100, 95), (100, 95, 55),
|
||||
],
|
||||
sparkle: [
|
||||
(194, 255, 223), (255, 133, 127), (255, 243, 82),
|
||||
(255, 184, 209), (174, 129, 255),
|
||||
],
|
||||
meter: [(194, 255, 223), (255, 243, 82), (255, 133, 127)],
|
||||
let purple = Color::Rgb(174, 129, 255);
|
||||
let pink = Color::Rgb(255, 184, 209);
|
||||
let coral = Color::Rgb(255, 133, 127);
|
||||
let yellow = Color::Rgb(255, 243, 82);
|
||||
let gold = Color::Rgb(230, 192, 0);
|
||||
let mint = Color::Rgb(194, 255, 223);
|
||||
let lavender = Color::Rgb(197, 163, 255);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (90, 84, 117),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: muted,
|
||||
border: bg_lighter,
|
||||
header: mint,
|
||||
unfocused: muted,
|
||||
accent: pink,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(70, 95, 85),
|
||||
playing_fg: mint,
|
||||
stopped_bg: Color::Rgb(100, 70, 85),
|
||||
stopped_fg: coral,
|
||||
fill_on: mint,
|
||||
fill_off: muted,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: pink,
|
||||
cursor_fg: dark,
|
||||
selected_bg: Color::Rgb(120, 90, 130),
|
||||
selected_fg: pink,
|
||||
in_range_bg: Color::Rgb(100, 95, 125),
|
||||
in_range_fg: fg,
|
||||
cursor: pink,
|
||||
selected: Color::Rgb(120, 90, 130),
|
||||
in_range: Color::Rgb(100, 95, 125),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(100, 85, 60),
|
||||
playing_active_fg: gold,
|
||||
playing_inactive_bg: Color::Rgb(95, 90, 70),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(70, 100, 100),
|
||||
active_fg: mint,
|
||||
content_bg: Color::Rgb(77, 107, 107),
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(120, 90, 130),
|
||||
active_in_range_bg: Color::Rgb(100, 95, 125),
|
||||
link_bright: [
|
||||
(255, 184, 209),
|
||||
(174, 129, 255),
|
||||
(255, 133, 127),
|
||||
(194, 255, 223),
|
||||
(255, 243, 82),
|
||||
],
|
||||
link_dim: [
|
||||
(100, 75, 90),
|
||||
(85, 70, 105),
|
||||
(100, 65, 65),
|
||||
(75, 100, 95),
|
||||
(100, 95, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(100, 75, 95),
|
||||
tempo_fg: pink,
|
||||
bank_bg: Color::Rgb(70, 95, 95),
|
||||
bank_fg: mint,
|
||||
pattern_bg: Color::Rgb(85, 75, 110),
|
||||
pattern_fg: purple,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: mint,
|
||||
border_accent: pink,
|
||||
border_warn: coral,
|
||||
border_dim: muted,
|
||||
confirm: coral,
|
||||
rename: purple,
|
||||
input: mint,
|
||||
editor: mint,
|
||||
preview: muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(100, 65, 70),
|
||||
error_fg: coral,
|
||||
success_bg: Color::Rgb(65, 95, 85),
|
||||
success_fg: mint,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(65, 95, 85),
|
||||
playing_fg: mint,
|
||||
staged_play_bg: Color::Rgb(95, 80, 120),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(105, 70, 85),
|
||||
staged_stop_fg: pink,
|
||||
edit_bg: Color::Rgb(70, 95, 100),
|
||||
edit_fg: mint,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(75, 70, 95),
|
||||
muted_fg: muted,
|
||||
soloed_bg: Color::Rgb(100, 95, 65),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: coral,
|
||||
connected: mint,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: dark,
|
||||
executed_bg: Color::Rgb(80, 75, 100),
|
||||
selected_bg: Color::Rgb(110, 100, 70),
|
||||
emit: (fg, Color::Rgb(110, 80, 100)),
|
||||
number: (purple, Color::Rgb(85, 75, 110)),
|
||||
string: (yellow, Color::Rgb(100, 95, 60)),
|
||||
comment: (muted, dark),
|
||||
keyword: (pink, Color::Rgb(105, 75, 90)),
|
||||
stack_op: (mint, Color::Rgb(70, 100, 95)),
|
||||
operator: (pink, Color::Rgb(105, 75, 90)),
|
||||
sound: (mint, Color::Rgb(70, 100, 95)),
|
||||
param: (coral, Color::Rgb(105, 70, 70)),
|
||||
context: (coral, Color::Rgb(105, 70, 70)),
|
||||
note: (lavender, Color::Rgb(85, 75, 110)),
|
||||
interval: (Color::Rgb(220, 190, 255), Color::Rgb(85, 75, 100)),
|
||||
variable: (lavender, Color::Rgb(85, 75, 110)),
|
||||
vary: (yellow, Color::Rgb(100, 95, 60)),
|
||||
generator: (mint, Color::Rgb(70, 95, 95)),
|
||||
user_defined: (coral, Color::Rgb(100, 75, 75)),
|
||||
default: (fg_dim, dark),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: dark,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: coral,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: coral,
|
||||
text: muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(110, 85, 120),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(105, 95, 125),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: coral,
|
||||
completion_example: mint,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: mint,
|
||||
project_file: purple,
|
||||
selected: coral,
|
||||
file: fg,
|
||||
focused_border: coral,
|
||||
unfocused_border: muted,
|
||||
root: fg,
|
||||
file_icon: muted,
|
||||
folder_icon: mint,
|
||||
empty_text: muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: mint,
|
||||
cursor: fg,
|
||||
hint: muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: coral,
|
||||
inactive: muted,
|
||||
match_bg: yellow,
|
||||
match_fg: dark,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: mint,
|
||||
h2: coral,
|
||||
h3: purple,
|
||||
code: lavender,
|
||||
code_border: Color::Rgb(120, 115, 140),
|
||||
link: pink,
|
||||
link_url: Color::Rgb(150, 145, 165),
|
||||
quote: muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: mint,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(110, 105, 130),
|
||||
scroll_indicator: Color::Rgb(125, 120, 145),
|
||||
label: Color::Rgb(175, 170, 190),
|
||||
label_focused: Color::Rgb(210, 205, 225),
|
||||
label_dim: Color::Rgb(145, 140, 160),
|
||||
value: Color::Rgb(230, 225, 240),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(125, 120, 145),
|
||||
path: Color::Rgb(175, 170, 190),
|
||||
border_magenta: pink,
|
||||
border_green: mint,
|
||||
border_cyan: lavender,
|
||||
separator: Color::Rgb(110, 105, 130),
|
||||
hint_active: Color::Rgb(240, 230, 120),
|
||||
hint_inactive: Color::Rgb(110, 105, 130),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: lavender,
|
||||
word_bg: Color::Rgb(75, 85, 105),
|
||||
alias: muted,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(175, 170, 190),
|
||||
category_focused: yellow,
|
||||
category_selected: mint,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(125, 120, 145),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(110, 105, 130),
|
||||
header_desc: Color::Rgb(195, 190, 210),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: pink,
|
||||
author: mint,
|
||||
link: lavender,
|
||||
license: coral,
|
||||
prompt: Color::Rgb(195, 190, 210),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: mint,
|
||||
mid: yellow,
|
||||
high: coral,
|
||||
low_rgb: (194, 255, 223),
|
||||
mid_rgb: (255, 243, 82),
|
||||
high_rgb: (255, 133, 127),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(194, 255, 223),
|
||||
(255, 133, 127),
|
||||
(255, 243, 82),
|
||||
(255, 184, 209),
|
||||
(174, 129, 255),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: coral,
|
||||
button_selected_bg: coral,
|
||||
button_selected_fg: dark,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
//! Fauve palette.
|
||||
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (28, 22, 18),
|
||||
surface: (42, 33, 26),
|
||||
surface2: (58, 46, 36),
|
||||
fg: (240, 228, 210),
|
||||
fg_dim: (170, 150, 130),
|
||||
fg_muted: (100, 82, 66),
|
||||
accent: (230, 60, 20),
|
||||
red: (220, 38, 32),
|
||||
green: (30, 170, 80),
|
||||
yellow: (255, 210, 0),
|
||||
blue: (20, 80, 200),
|
||||
purple: (170, 40, 150),
|
||||
cyan: (0, 150, 180),
|
||||
orange: (240, 120, 0),
|
||||
tempo_color: (230, 60, 20),
|
||||
bank_color: (20, 80, 200),
|
||||
pattern_color: (0, 150, 180),
|
||||
title_accent: (230, 60, 20),
|
||||
title_author: (20, 80, 200),
|
||||
secondary: (170, 40, 150),
|
||||
link_bright: [
|
||||
(230, 60, 20), (20, 80, 200), (240, 120, 0),
|
||||
(0, 150, 180), (30, 170, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(72, 24, 10), (10, 28, 65), (76, 40, 6),
|
||||
(6, 48, 58), (14, 54, 28),
|
||||
],
|
||||
sparkle: [
|
||||
(230, 60, 20), (255, 210, 0), (30, 170, 80),
|
||||
(20, 80, 200), (170, 40, 150),
|
||||
],
|
||||
meter: [(26, 152, 72), (235, 190, 0), (200, 34, 28)],
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,286 @@
|
||||
//! Georges palette (C64 colors on black).
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
// C64 palette on pure black
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(0, 0, 0);
|
||||
let surface = Color::Rgb(16, 16, 16);
|
||||
let surface2 = Color::Rgb(24, 24, 24);
|
||||
let border = Color::Rgb(51, 51, 51);
|
||||
let fg = Color::Rgb(187, 187, 187);
|
||||
let fg_dim = Color::Rgb(119, 119, 119);
|
||||
let fg_muted = Color::Rgb(51, 51, 51);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (0, 0, 0),
|
||||
surface: (16, 16, 16),
|
||||
surface2: (24, 24, 24),
|
||||
fg: (187, 187, 187),
|
||||
fg_dim: (119, 119, 119),
|
||||
fg_muted: (51, 51, 51),
|
||||
accent: (0, 136, 255), // lightblue
|
||||
red: (255, 119, 119), // lightred
|
||||
green: (0, 204, 85),
|
||||
yellow: (238, 238, 119),
|
||||
blue: (0, 136, 255), // lightblue
|
||||
purple: (204, 68, 204), // violet
|
||||
cyan: (170, 255, 238),
|
||||
orange: (221, 136, 85),
|
||||
tempo_color: (204, 68, 204),
|
||||
bank_color: (0, 136, 255),
|
||||
pattern_color: (0, 204, 85),
|
||||
title_accent: (0, 136, 255),
|
||||
title_author: (0, 204, 85),
|
||||
secondary: (221, 136, 85),
|
||||
link_bright: [
|
||||
(0, 136, 255), (0, 204, 85), (238, 238, 119),
|
||||
(204, 68, 204), (170, 255, 238),
|
||||
],
|
||||
link_dim: [
|
||||
(0, 20, 40), (0, 30, 12), (34, 34, 16),
|
||||
(30, 10, 30), (24, 36, 34),
|
||||
],
|
||||
sparkle: [
|
||||
(0, 136, 255), (170, 255, 238), (0, 204, 85),
|
||||
(238, 238, 119), (204, 68, 204),
|
||||
],
|
||||
meter: [(0, 204, 85), (238, 238, 119), (255, 119, 119)],
|
||||
let white = Color::Rgb(255, 255, 255);
|
||||
let lightred = Color::Rgb(255, 119, 119);
|
||||
let cyan = Color::Rgb(170, 255, 238);
|
||||
let violet = Color::Rgb(204, 68, 204);
|
||||
let green = Color::Rgb(0, 204, 85);
|
||||
let lightgreen = Color::Rgb(170, 255, 102);
|
||||
let lightblue = Color::Rgb(0, 136, 255);
|
||||
let yellow = Color::Rgb(238, 238, 119);
|
||||
let orange = Color::Rgb(221, 136, 85);
|
||||
let brown = Color::Rgb(102, 68, 0);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (0, 0, 0),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: lightblue,
|
||||
unfocused: fg_muted,
|
||||
accent: lightblue,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(0, 24, 10),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(28, 0, 0),
|
||||
stopped_fg: lightred,
|
||||
fill_on: lightblue,
|
||||
fill_off: brown,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: lightblue,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(0, 20, 40),
|
||||
selected_fg: cyan,
|
||||
in_range_bg: Color::Rgb(0, 14, 28),
|
||||
in_range_fg: fg,
|
||||
cursor: lightblue,
|
||||
selected: Color::Rgb(0, 20, 40),
|
||||
in_range: Color::Rgb(0, 14, 28),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(0, 30, 12),
|
||||
playing_active_fg: lightgreen,
|
||||
playing_inactive_bg: Color::Rgb(30, 30, 14),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(0, 16, 32),
|
||||
active_fg: lightblue,
|
||||
content_bg: Color::Rgb(7, 23, 39),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(10, 20, 36),
|
||||
active_in_range_bg: Color::Rgb(6, 14, 28),
|
||||
link_bright: [
|
||||
(0, 136, 255),
|
||||
(0, 204, 85),
|
||||
(238, 238, 119),
|
||||
(204, 68, 204),
|
||||
(170, 255, 238),
|
||||
],
|
||||
link_dim: [
|
||||
(0, 20, 40),
|
||||
(0, 30, 12),
|
||||
(34, 34, 16),
|
||||
(30, 10, 30),
|
||||
(24, 36, 34),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(28, 10, 28),
|
||||
tempo_fg: violet,
|
||||
bank_bg: Color::Rgb(0, 0, 24),
|
||||
bank_fg: lightblue,
|
||||
pattern_bg: Color::Rgb(0, 24, 10),
|
||||
pattern_fg: green,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: lightblue,
|
||||
border_accent: cyan,
|
||||
border_warn: yellow,
|
||||
border_dim: fg_muted,
|
||||
confirm: lightred,
|
||||
rename: lightblue,
|
||||
input: cyan,
|
||||
editor: lightblue,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(28, 0, 0),
|
||||
error_fg: lightred,
|
||||
success_bg: Color::Rgb(0, 24, 10),
|
||||
success_fg: green,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(0, 24, 10),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(28, 10, 28),
|
||||
staged_play_fg: violet,
|
||||
staged_stop_bg: Color::Rgb(28, 0, 0),
|
||||
staged_stop_fg: lightred,
|
||||
edit_bg: Color::Rgb(0, 16, 32),
|
||||
edit_fg: lightblue,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(8, 8, 8),
|
||||
muted_fg: fg_muted,
|
||||
soloed_bg: Color::Rgb(30, 30, 14),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: lightred,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(0, 12, 24),
|
||||
selected_bg: Color::Rgb(0, 20, 40),
|
||||
emit: (white, Color::Rgb(20, 20, 20)),
|
||||
number: (yellow, Color::Rgb(30, 30, 14)),
|
||||
string: (lightgreen, Color::Rgb(18, 30, 12)),
|
||||
comment: (fg_muted, bg),
|
||||
keyword: (lightred, Color::Rgb(30, 14, 14)),
|
||||
stack_op: (lightblue, Color::Rgb(0, 16, 32)),
|
||||
operator: (cyan, Color::Rgb(20, 30, 28)),
|
||||
sound: (green, Color::Rgb(0, 24, 10)),
|
||||
param: (violet, Color::Rgb(24, 8, 24)),
|
||||
context: (orange, Color::Rgb(28, 16, 10)),
|
||||
note: (lightgreen, Color::Rgb(18, 30, 12)),
|
||||
interval: (Color::Rgb(130, 210, 80), Color::Rgb(14, 26, 8)),
|
||||
variable: (violet, Color::Rgb(24, 8, 24)),
|
||||
vary: (yellow, Color::Rgb(30, 30, 14)),
|
||||
generator: (cyan, Color::Rgb(20, 30, 28)),
|
||||
user_defined: (orange, Color::Rgb(30, 20, 10)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: lightblue,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: lightblue,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(0, 20, 40),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(0, 24, 48),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: lightblue,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: lightblue,
|
||||
project_file: green,
|
||||
selected: cyan,
|
||||
file: fg,
|
||||
focused_border: lightblue,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: lightblue,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: lightblue,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: lightblue,
|
||||
inactive: fg_muted,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: lightblue,
|
||||
h2: green,
|
||||
h3: violet,
|
||||
code: lightgreen,
|
||||
code_border: Color::Rgb(36, 36, 36),
|
||||
link: cyan,
|
||||
link_url: brown,
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: lightblue,
|
||||
header_focused: cyan,
|
||||
divider: Color::Rgb(28, 28, 28),
|
||||
scroll_indicator: Color::Rgb(51, 51, 51),
|
||||
label: fg_dim,
|
||||
label_focused: fg,
|
||||
label_dim: fg_muted,
|
||||
value: fg,
|
||||
focused: cyan,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(36, 36, 36),
|
||||
path: fg_dim,
|
||||
border_magenta: violet,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(28, 28, 28),
|
||||
hint_active: lightblue,
|
||||
hint_inactive: Color::Rgb(28, 28, 28),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: lightgreen,
|
||||
word_bg: Color::Rgb(10, 18, 6),
|
||||
alias: fg_muted,
|
||||
stack_sig: violet,
|
||||
description: fg,
|
||||
example: fg_dim,
|
||||
category_focused: cyan,
|
||||
category_selected: lightblue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(36, 36, 36),
|
||||
border_focused: cyan,
|
||||
border_normal: Color::Rgb(28, 28, 28),
|
||||
header_desc: fg_dim,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: lightblue,
|
||||
author: green,
|
||||
link: cyan,
|
||||
license: yellow,
|
||||
prompt: fg_dim,
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: lightred,
|
||||
low_rgb: (0, 204, 85),
|
||||
mid_rgb: (238, 238, 119),
|
||||
high_rgb: (255, 119, 119),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(0, 136, 255),
|
||||
(170, 255, 238),
|
||||
(0, 204, 85),
|
||||
(238, 238, 119),
|
||||
(204, 68, 204),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: lightred,
|
||||
button_selected_bg: lightblue,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,283 @@
|
||||
//! Gruvbox Dark palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg0 = Color::Rgb(40, 40, 40);
|
||||
let bg1 = Color::Rgb(60, 56, 54);
|
||||
let bg2 = Color::Rgb(80, 73, 69);
|
||||
let fg = Color::Rgb(235, 219, 178);
|
||||
let fg2 = Color::Rgb(213, 196, 161);
|
||||
let fg3 = Color::Rgb(189, 174, 147);
|
||||
let fg4 = Color::Rgb(168, 153, 132);
|
||||
let red = Color::Rgb(251, 73, 52);
|
||||
let green = Color::Rgb(184, 187, 38);
|
||||
let yellow = Color::Rgb(250, 189, 47);
|
||||
let blue = Color::Rgb(131, 165, 152);
|
||||
let purple = Color::Rgb(211, 134, 155);
|
||||
let aqua = Color::Rgb(142, 192, 124);
|
||||
let orange = Color::Rgb(254, 128, 25);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (40, 40, 40),
|
||||
surface: (60, 56, 54),
|
||||
surface2: (80, 73, 69),
|
||||
fg: (235, 219, 178),
|
||||
fg_dim: (189, 174, 147),
|
||||
fg_muted: (168, 153, 132),
|
||||
accent: (254, 128, 25), // orange
|
||||
red: (251, 73, 52),
|
||||
green: (184, 187, 38),
|
||||
yellow: (250, 189, 47),
|
||||
blue: (131, 165, 152),
|
||||
purple: (211, 134, 155),
|
||||
cyan: (142, 192, 124), // aqua
|
||||
orange: (254, 128, 25),
|
||||
tempo_color: (254, 128, 25),
|
||||
bank_color: (131, 165, 152),
|
||||
pattern_color: (142, 192, 124),
|
||||
title_accent: (254, 128, 25),
|
||||
title_author: (250, 189, 47),
|
||||
secondary: (254, 128, 25),
|
||||
link_bright: [
|
||||
(254, 128, 25), (211, 134, 155), (250, 189, 47),
|
||||
(131, 165, 152), (184, 187, 38),
|
||||
],
|
||||
link_dim: [
|
||||
(85, 55, 35), (75, 55, 65), (80, 70, 40),
|
||||
(50, 60, 60), (60, 65, 35),
|
||||
],
|
||||
sparkle: [
|
||||
(250, 189, 47), (254, 128, 25), (184, 187, 38),
|
||||
(211, 134, 155), (131, 165, 152),
|
||||
],
|
||||
meter: [(170, 175, 35), (235, 180, 45), (240, 70, 50)],
|
||||
let darker_bg = Color::Rgb(29, 32, 33);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: bg0,
|
||||
bg_rgb: (40, 40, 40),
|
||||
text_primary: fg,
|
||||
text_muted: fg3,
|
||||
text_dim: fg4,
|
||||
border: bg2,
|
||||
header: yellow,
|
||||
unfocused: fg4,
|
||||
accent: orange,
|
||||
surface: bg1,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 60, 45),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(65, 45, 45),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: fg4,
|
||||
fill_bg: bg1,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: orange,
|
||||
cursor_fg: bg0,
|
||||
selected_bg: Color::Rgb(80, 70, 55),
|
||||
selected_fg: yellow,
|
||||
in_range_bg: Color::Rgb(65, 60, 50),
|
||||
in_range_fg: fg2,
|
||||
cursor: orange,
|
||||
selected: Color::Rgb(80, 70, 55),
|
||||
in_range: Color::Rgb(65, 60, 50),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(90, 65, 50),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 45),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(50, 65, 55),
|
||||
active_fg: aqua,
|
||||
content_bg: Color::Rgb(57, 72, 62),
|
||||
inactive_bg: bg1,
|
||||
inactive_fg: fg3,
|
||||
active_selected_bg: Color::Rgb(85, 70, 60),
|
||||
active_in_range_bg: Color::Rgb(70, 65, 55),
|
||||
link_bright: [
|
||||
(254, 128, 25),
|
||||
(211, 134, 155),
|
||||
(250, 189, 47),
|
||||
(131, 165, 152),
|
||||
(184, 187, 38),
|
||||
],
|
||||
link_dim: [
|
||||
(85, 55, 35),
|
||||
(75, 55, 65),
|
||||
(80, 70, 40),
|
||||
(50, 60, 60),
|
||||
(60, 65, 35),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(75, 55, 40),
|
||||
tempo_fg: orange,
|
||||
bank_bg: Color::Rgb(50, 60, 60),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(50, 65, 50),
|
||||
pattern_fg: aqua,
|
||||
stats_bg: bg1,
|
||||
stats_fg: fg3,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: yellow,
|
||||
border_accent: orange,
|
||||
border_warn: red,
|
||||
border_dim: fg4,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: yellow,
|
||||
preview: fg4,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(70, 45, 45),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(50, 65, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg1,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 65, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 60),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(75, 50, 50),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(50, 65, 55),
|
||||
edit_fg: aqua,
|
||||
hover_bg: bg2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(50, 50, 55),
|
||||
muted_fg: fg4,
|
||||
soloed_bg: Color::Rgb(70, 65, 40),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 45),
|
||||
selected_bg: Color::Rgb(85, 70, 45),
|
||||
emit: (fg, Color::Rgb(80, 55, 50)),
|
||||
number: (orange, Color::Rgb(70, 50, 40)),
|
||||
string: (green, Color::Rgb(50, 60, 40)),
|
||||
comment: (fg4, darker_bg),
|
||||
keyword: (red, Color::Rgb(70, 45, 45)),
|
||||
stack_op: (blue, Color::Rgb(50, 55, 60)),
|
||||
operator: (yellow, Color::Rgb(70, 65, 40)),
|
||||
sound: (aqua, Color::Rgb(45, 60, 50)),
|
||||
param: (purple, Color::Rgb(65, 50, 55)),
|
||||
context: (orange, Color::Rgb(70, 50, 40)),
|
||||
note: (green, Color::Rgb(50, 60, 40)),
|
||||
interval: (Color::Rgb(170, 200, 100), Color::Rgb(55, 65, 40)),
|
||||
variable: (purple, Color::Rgb(65, 50, 55)),
|
||||
vary: (yellow, Color::Rgb(70, 65, 40)),
|
||||
generator: (aqua, Color::Rgb(45, 60, 50)),
|
||||
user_defined: (orange, Color::Rgb(60, 45, 30)),
|
||||
default: (fg3, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg0,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg3,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: fg4,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg0 },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(80, 65, 50),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg1,
|
||||
unselected_fg: fg4,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg0,
|
||||
selection_bg: Color::Rgb(70, 65, 55),
|
||||
completion_bg: bg1,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: aqua,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: fg4,
|
||||
root: fg,
|
||||
file_icon: fg4,
|
||||
folder_icon: blue,
|
||||
empty_text: fg4,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: fg4,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: fg4,
|
||||
match_bg: yellow,
|
||||
match_fg: bg0,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(80, 75, 70),
|
||||
link: aqua,
|
||||
link_url: Color::Rgb(120, 115, 105),
|
||||
quote: fg4,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(75, 70, 65),
|
||||
scroll_indicator: Color::Rgb(90, 85, 80),
|
||||
label: Color::Rgb(145, 135, 125),
|
||||
label_focused: Color::Rgb(175, 165, 155),
|
||||
label_dim: Color::Rgb(115, 105, 95),
|
||||
value: Color::Rgb(200, 190, 175),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(90, 85, 80),
|
||||
path: Color::Rgb(145, 135, 125),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: aqua,
|
||||
separator: Color::Rgb(75, 70, 65),
|
||||
hint_active: Color::Rgb(220, 180, 80),
|
||||
hint_inactive: Color::Rgb(75, 70, 65),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 60, 55),
|
||||
alias: fg4,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(145, 135, 125),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(90, 85, 80),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(75, 70, 65),
|
||||
header_desc: Color::Rgb(165, 155, 145),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: orange,
|
||||
author: yellow,
|
||||
link: aqua,
|
||||
license: purple,
|
||||
prompt: Color::Rgb(165, 155, 145),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (170, 175, 35),
|
||||
mid_rgb: (235, 180, 45),
|
||||
high_rgb: (240, 70, 50),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(250, 189, 47),
|
||||
(254, 128, 25),
|
||||
(184, 187, 38),
|
||||
(211, 134, 155),
|
||||
(131, 165, 152),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,278 @@
|
||||
//! Hot Dog Stand palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let red = Color::Rgb(255, 0, 0);
|
||||
let dark_red = Color::Rgb(215, 0, 0);
|
||||
let darker_red = Color::Rgb(175, 0, 0);
|
||||
let yellow = Color::Rgb(255, 255, 0);
|
||||
let light_yellow = Color::Rgb(255, 255, 95);
|
||||
let gold = Color::Rgb(255, 215, 0);
|
||||
let black = Color::Rgb(0, 0, 0);
|
||||
let white = Color::Rgb(255, 255, 255);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (255, 0, 0),
|
||||
surface: (215, 0, 0),
|
||||
surface2: (175, 0, 0),
|
||||
fg: (255, 255, 0),
|
||||
fg_dim: (255, 255, 95),
|
||||
fg_muted: (255, 215, 0),
|
||||
accent: (255, 255, 0),
|
||||
red: (255, 255, 255),
|
||||
green: (255, 255, 0),
|
||||
yellow: (255, 215, 0),
|
||||
blue: (255, 255, 0),
|
||||
purple: (255, 255, 255),
|
||||
cyan: (255, 255, 0),
|
||||
orange: (255, 215, 0),
|
||||
tempo_color: (255, 255, 0),
|
||||
bank_color: (255, 255, 0),
|
||||
pattern_color: (255, 255, 0),
|
||||
title_accent: (255, 255, 0),
|
||||
title_author: (255, 255, 255),
|
||||
secondary: (255, 215, 0),
|
||||
link_bright: [
|
||||
(255, 255, 0), (255, 255, 255), (255, 215, 0),
|
||||
(255, 255, 95), (255, 255, 0),
|
||||
],
|
||||
link_dim: [
|
||||
(140, 140, 0), (140, 140, 140), (140, 120, 0),
|
||||
(140, 140, 60), (140, 140, 0),
|
||||
],
|
||||
sparkle: [
|
||||
(255, 255, 0), (255, 255, 255), (255, 215, 0),
|
||||
(255, 255, 95), (255, 255, 0),
|
||||
],
|
||||
meter: [(255, 255, 0), (255, 215, 0), (255, 255, 255)],
|
||||
let dim_yellow = Color::Rgb(180, 180, 0);
|
||||
let muted_red = Color::Rgb(140, 40, 40);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: red,
|
||||
bg_rgb: (255, 0, 0),
|
||||
text_primary: yellow,
|
||||
text_muted: light_yellow,
|
||||
text_dim: gold,
|
||||
border: yellow,
|
||||
header: yellow,
|
||||
unfocused: gold,
|
||||
accent: yellow,
|
||||
surface: dark_red,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(180, 180, 0),
|
||||
playing_fg: black,
|
||||
stopped_bg: darker_red,
|
||||
stopped_fg: yellow,
|
||||
fill_on: yellow,
|
||||
fill_off: gold,
|
||||
fill_bg: dark_red,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: yellow,
|
||||
cursor_fg: red,
|
||||
selected_bg: Color::Rgb(200, 200, 0),
|
||||
selected_fg: black,
|
||||
in_range_bg: Color::Rgb(170, 100, 0),
|
||||
in_range_fg: yellow,
|
||||
cursor: yellow,
|
||||
selected: Color::Rgb(200, 200, 0),
|
||||
in_range: Color::Rgb(170, 100, 0),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(200, 200, 0),
|
||||
playing_active_fg: black,
|
||||
playing_inactive_bg: Color::Rgb(180, 180, 0),
|
||||
playing_inactive_fg: black,
|
||||
active_bg: Color::Rgb(200, 50, 50),
|
||||
active_fg: yellow,
|
||||
content_bg: Color::Rgb(210, 60, 60),
|
||||
inactive_bg: dark_red,
|
||||
inactive_fg: gold,
|
||||
active_selected_bg: Color::Rgb(200, 200, 0),
|
||||
active_in_range_bg: Color::Rgb(170, 100, 0),
|
||||
link_bright: [
|
||||
(255, 255, 0),
|
||||
(255, 255, 255),
|
||||
(255, 215, 0),
|
||||
(255, 255, 95),
|
||||
(255, 255, 0),
|
||||
],
|
||||
link_dim: [
|
||||
(140, 140, 0),
|
||||
(140, 140, 140),
|
||||
(140, 120, 0),
|
||||
(140, 140, 60),
|
||||
(140, 140, 0),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(180, 180, 0),
|
||||
tempo_fg: black,
|
||||
bank_bg: darker_red,
|
||||
bank_fg: yellow,
|
||||
pattern_bg: Color::Rgb(200, 200, 0),
|
||||
pattern_fg: black,
|
||||
stats_bg: dark_red,
|
||||
stats_fg: yellow,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: yellow,
|
||||
border_accent: white,
|
||||
border_warn: gold,
|
||||
border_dim: dim_yellow,
|
||||
confirm: gold,
|
||||
rename: light_yellow,
|
||||
input: yellow,
|
||||
editor: yellow,
|
||||
preview: gold,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: black,
|
||||
error_fg: yellow,
|
||||
success_bg: Color::Rgb(180, 180, 0),
|
||||
success_fg: black,
|
||||
info_bg: dark_red,
|
||||
info_fg: yellow,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(180, 180, 0),
|
||||
playing_fg: black,
|
||||
staged_play_bg: Color::Rgb(200, 200, 0),
|
||||
staged_play_fg: black,
|
||||
staged_stop_bg: darker_red,
|
||||
staged_stop_fg: yellow,
|
||||
edit_bg: Color::Rgb(200, 50, 50),
|
||||
edit_fg: yellow,
|
||||
hover_bg: Color::Rgb(230, 50, 50),
|
||||
hover_fg: yellow,
|
||||
muted_bg: darker_red,
|
||||
muted_fg: dim_yellow,
|
||||
soloed_bg: Color::Rgb(200, 200, 0),
|
||||
soloed_fg: black,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: white,
|
||||
connected: yellow,
|
||||
listening: gold,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_red,
|
||||
executed_bg: Color::Rgb(200, 50, 50),
|
||||
selected_bg: Color::Rgb(180, 180, 0),
|
||||
emit: (yellow, muted_red),
|
||||
number: (white, muted_red),
|
||||
string: (gold, muted_red),
|
||||
comment: (dim_yellow, darker_red),
|
||||
keyword: (light_yellow, muted_red),
|
||||
stack_op: (yellow, muted_red),
|
||||
operator: (light_yellow, muted_red),
|
||||
sound: (yellow, muted_red),
|
||||
param: (gold, muted_red),
|
||||
context: (gold, muted_red),
|
||||
note: (white, muted_red),
|
||||
interval: (Color::Rgb(255, 240, 150), muted_red),
|
||||
variable: (white, muted_red),
|
||||
vary: (gold, muted_red),
|
||||
generator: (yellow, muted_red),
|
||||
user_defined: (gold, Color::Rgb(100, 70, 0)),
|
||||
default: (light_yellow, darker_red),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_red,
|
||||
row_odd: red,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: gold,
|
||||
value: light_yellow,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: white,
|
||||
text: gold,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: yellow, fg: red },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(200, 200, 0),
|
||||
selected_fg: black,
|
||||
unselected_bg: dark_red,
|
||||
unselected_fg: gold,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: yellow,
|
||||
cursor_fg: red,
|
||||
selection_bg: Color::Rgb(180, 180, 0),
|
||||
completion_bg: dark_red,
|
||||
completion_fg: yellow,
|
||||
completion_selected: white,
|
||||
completion_example: gold,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: yellow,
|
||||
project_file: white,
|
||||
selected: gold,
|
||||
file: light_yellow,
|
||||
focused_border: white,
|
||||
unfocused_border: gold,
|
||||
root: yellow,
|
||||
file_icon: gold,
|
||||
folder_icon: yellow,
|
||||
empty_text: gold,
|
||||
},
|
||||
input: InputColors {
|
||||
text: yellow,
|
||||
cursor: white,
|
||||
hint: gold,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: white,
|
||||
inactive: gold,
|
||||
match_bg: yellow,
|
||||
match_fg: red,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: yellow,
|
||||
h2: white,
|
||||
h3: gold,
|
||||
code: light_yellow,
|
||||
code_border: dim_yellow,
|
||||
link: white,
|
||||
link_url: gold,
|
||||
quote: dim_yellow,
|
||||
text: yellow,
|
||||
list: yellow,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: yellow,
|
||||
header_focused: white,
|
||||
divider: dim_yellow,
|
||||
scroll_indicator: gold,
|
||||
label: light_yellow,
|
||||
label_focused: white,
|
||||
label_dim: dim_yellow,
|
||||
value: yellow,
|
||||
focused: white,
|
||||
normal: yellow,
|
||||
dim: dim_yellow,
|
||||
path: gold,
|
||||
border_magenta: gold,
|
||||
border_green: yellow,
|
||||
border_cyan: white,
|
||||
separator: dim_yellow,
|
||||
hint_active: white,
|
||||
hint_inactive: dim_yellow,
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: yellow,
|
||||
word_bg: darker_red,
|
||||
alias: gold,
|
||||
stack_sig: white,
|
||||
description: yellow,
|
||||
example: gold,
|
||||
category_focused: white,
|
||||
category_selected: yellow,
|
||||
category_normal: light_yellow,
|
||||
category_dimmed: dim_yellow,
|
||||
border_focused: white,
|
||||
border_normal: dim_yellow,
|
||||
header_desc: gold,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: yellow,
|
||||
author: white,
|
||||
link: gold,
|
||||
license: light_yellow,
|
||||
prompt: gold,
|
||||
subtitle: yellow,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: yellow,
|
||||
mid: gold,
|
||||
high: white,
|
||||
low_rgb: (255, 255, 0),
|
||||
mid_rgb: (255, 215, 0),
|
||||
high_rgb: (255, 255, 255),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(255, 255, 0),
|
||||
(255, 255, 255),
|
||||
(255, 215, 0),
|
||||
(255, 255, 95),
|
||||
(255, 255, 0),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: white,
|
||||
button_selected_bg: yellow,
|
||||
button_selected_fg: red,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
//! Iceberg palette.
|
||||
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (22, 24, 33),
|
||||
surface: (30, 33, 46),
|
||||
surface2: (45, 48, 64),
|
||||
fg: (198, 200, 209),
|
||||
fg_dim: (109, 112, 126),
|
||||
fg_muted: (64, 66, 78),
|
||||
accent: (132, 160, 198),
|
||||
red: (226, 120, 120),
|
||||
green: (180, 190, 130),
|
||||
yellow: (226, 164, 120),
|
||||
blue: (132, 160, 198),
|
||||
purple: (160, 147, 199),
|
||||
cyan: (137, 184, 194),
|
||||
orange: (226, 164, 120),
|
||||
tempo_color: (160, 147, 199),
|
||||
bank_color: (132, 160, 198),
|
||||
pattern_color: (137, 184, 194),
|
||||
title_accent: (132, 160, 198),
|
||||
title_author: (160, 147, 199),
|
||||
secondary: (226, 164, 120),
|
||||
link_bright: [
|
||||
(132, 160, 198), (160, 147, 199), (226, 164, 120),
|
||||
(137, 184, 194), (180, 190, 130),
|
||||
],
|
||||
link_dim: [
|
||||
(45, 55, 70), (55, 50, 68), (70, 55, 42),
|
||||
(46, 62, 66), (58, 62, 44),
|
||||
],
|
||||
sparkle: [
|
||||
(132, 160, 198), (226, 164, 120), (180, 190, 130),
|
||||
(160, 147, 199), (226, 120, 120),
|
||||
],
|
||||
meter: [(160, 175, 115), (210, 150, 105), (200, 105, 105)],
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//! Jaipur palette.
|
||||
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (30, 24, 22),
|
||||
surface: (44, 36, 32),
|
||||
surface2: (60, 48, 42),
|
||||
fg: (238, 222, 200),
|
||||
fg_dim: (165, 145, 125),
|
||||
fg_muted: (95, 78, 65),
|
||||
accent: (210, 90, 100),
|
||||
red: (200, 44, 52),
|
||||
green: (30, 160, 120),
|
||||
yellow: (240, 180, 20),
|
||||
blue: (60, 60, 180),
|
||||
purple: (150, 50, 120),
|
||||
cyan: (0, 155, 155),
|
||||
orange: (220, 120, 50),
|
||||
tempo_color: (210, 90, 100),
|
||||
bank_color: (60, 60, 180),
|
||||
pattern_color: (0, 155, 155),
|
||||
title_accent: (210, 90, 100),
|
||||
title_author: (60, 60, 180),
|
||||
secondary: (220, 120, 50),
|
||||
link_bright: [
|
||||
(210, 90, 100), (60, 60, 180), (220, 120, 50),
|
||||
(0, 155, 155), (30, 160, 120),
|
||||
],
|
||||
link_dim: [
|
||||
(66, 30, 34), (22, 22, 58), (70, 40, 18),
|
||||
(6, 48, 48), (12, 50, 38),
|
||||
],
|
||||
sparkle: [
|
||||
(210, 90, 100), (240, 180, 20), (30, 160, 120),
|
||||
(60, 60, 180), (150, 50, 120),
|
||||
],
|
||||
meter: [(26, 144, 106), (222, 164, 18), (184, 40, 46)],
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,283 @@
|
||||
//! Kanagawa palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(31, 31, 40);
|
||||
let bg_light = Color::Rgb(43, 43, 54);
|
||||
let bg_lighter = Color::Rgb(54, 54, 70);
|
||||
let fg = Color::Rgb(220, 215, 186);
|
||||
let fg_dim = Color::Rgb(160, 158, 140);
|
||||
let comment = Color::Rgb(114, 113, 105);
|
||||
let crystal_blue = Color::Rgb(126, 156, 216);
|
||||
let oni_violet = Color::Rgb(149, 127, 184);
|
||||
let autumn_green = Color::Rgb(118, 148, 106);
|
||||
let autumn_red = Color::Rgb(195, 64, 67);
|
||||
let carp_yellow = Color::Rgb(230, 195, 132);
|
||||
let spring_blue = Color::Rgb(127, 180, 202);
|
||||
let wave_red = Color::Rgb(228, 104, 118);
|
||||
let sakura_pink = Color::Rgb(210, 126, 153);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (31, 31, 40),
|
||||
surface: (43, 43, 54),
|
||||
surface2: (54, 54, 70),
|
||||
fg: (220, 215, 186),
|
||||
fg_dim: (160, 158, 140),
|
||||
fg_muted: (114, 113, 105),
|
||||
accent: (210, 126, 153), // sakura_pink
|
||||
red: (195, 64, 67),
|
||||
green: (118, 148, 106),
|
||||
yellow: (230, 195, 132), // carp_yellow
|
||||
blue: (126, 156, 216), // crystal_blue
|
||||
purple: (149, 127, 184), // oni_violet
|
||||
cyan: (127, 180, 202), // spring_blue
|
||||
orange: (230, 195, 132), // carp_yellow
|
||||
tempo_color: (149, 127, 184),
|
||||
bank_color: (126, 156, 216),
|
||||
pattern_color: (118, 148, 106),
|
||||
title_accent: (210, 126, 153),
|
||||
title_author: (126, 156, 216),
|
||||
secondary: (210, 126, 153),
|
||||
link_bright: [
|
||||
(228, 104, 118), (149, 127, 184), (230, 195, 132),
|
||||
(127, 180, 202), (118, 148, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 50), (55, 50, 70), (70, 60, 50),
|
||||
(45, 60, 70), (45, 55, 45),
|
||||
],
|
||||
sparkle: [
|
||||
(127, 180, 202), (230, 195, 132), (118, 148, 106),
|
||||
(228, 104, 118), (149, 127, 184),
|
||||
],
|
||||
meter: [(118, 148, 106), (230, 195, 132), (228, 104, 118)],
|
||||
let darker_bg = Color::Rgb(26, 26, 34);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (31, 31, 40),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: crystal_blue,
|
||||
unfocused: comment,
|
||||
accent: sakura_pink,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 55, 45),
|
||||
playing_fg: autumn_green,
|
||||
stopped_bg: Color::Rgb(60, 40, 45),
|
||||
stopped_fg: autumn_red,
|
||||
fill_on: autumn_green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: sakura_pink,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(65, 55, 70),
|
||||
selected_fg: sakura_pink,
|
||||
in_range_bg: Color::Rgb(50, 50, 60),
|
||||
in_range_fg: fg,
|
||||
cursor: sakura_pink,
|
||||
selected: Color::Rgb(65, 55, 70),
|
||||
in_range: Color::Rgb(50, 50, 60),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(65, 60, 50),
|
||||
playing_active_fg: carp_yellow,
|
||||
playing_inactive_bg: Color::Rgb(55, 55, 50),
|
||||
playing_inactive_fg: fg_dim,
|
||||
active_bg: Color::Rgb(45, 55, 70),
|
||||
active_fg: crystal_blue,
|
||||
content_bg: Color::Rgb(52, 62, 77),
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(65, 55, 70),
|
||||
active_in_range_bg: Color::Rgb(50, 50, 60),
|
||||
link_bright: [
|
||||
(228, 104, 118),
|
||||
(149, 127, 184),
|
||||
(230, 195, 132),
|
||||
(127, 180, 202),
|
||||
(118, 148, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 50),
|
||||
(55, 50, 70),
|
||||
(70, 60, 50),
|
||||
(45, 60, 70),
|
||||
(45, 55, 45),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(55, 50, 65),
|
||||
tempo_fg: oni_violet,
|
||||
bank_bg: Color::Rgb(45, 55, 70),
|
||||
bank_fg: crystal_blue,
|
||||
pattern_bg: Color::Rgb(45, 55, 45),
|
||||
pattern_fg: autumn_green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: crystal_blue,
|
||||
border_accent: sakura_pink,
|
||||
border_warn: carp_yellow,
|
||||
border_dim: comment,
|
||||
confirm: carp_yellow,
|
||||
rename: oni_violet,
|
||||
input: crystal_blue,
|
||||
editor: crystal_blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 40, 45),
|
||||
error_fg: wave_red,
|
||||
success_bg: Color::Rgb(40, 55, 45),
|
||||
success_fg: autumn_green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(40, 55, 45),
|
||||
playing_fg: autumn_green,
|
||||
staged_play_bg: Color::Rgb(55, 50, 70),
|
||||
staged_play_fg: oni_violet,
|
||||
staged_stop_bg: Color::Rgb(65, 45, 50),
|
||||
staged_stop_fg: wave_red,
|
||||
edit_bg: Color::Rgb(45, 55, 70),
|
||||
edit_fg: crystal_blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(38, 38, 48),
|
||||
muted_fg: comment,
|
||||
soloed_bg: Color::Rgb(60, 55, 45),
|
||||
soloed_fg: carp_yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: autumn_red,
|
||||
connected: autumn_green,
|
||||
listening: carp_yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(45, 45, 55),
|
||||
selected_bg: Color::Rgb(65, 60, 50),
|
||||
emit: (fg, Color::Rgb(60, 50, 60)),
|
||||
number: (oni_violet, Color::Rgb(55, 50, 65)),
|
||||
string: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (sakura_pink, Color::Rgb(60, 50, 55)),
|
||||
stack_op: (spring_blue, Color::Rgb(45, 55, 65)),
|
||||
operator: (wave_red, Color::Rgb(60, 45, 50)),
|
||||
sound: (crystal_blue, Color::Rgb(45, 55, 70)),
|
||||
param: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
context: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
note: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
interval: (Color::Rgb(150, 180, 130), Color::Rgb(45, 55, 45)),
|
||||
variable: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
vary: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
generator: (spring_blue, Color::Rgb(45, 55, 65)),
|
||||
user_defined: (sakura_pink, Color::Rgb(55, 40, 50)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: carp_yellow,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: carp_yellow,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(55, 55, 70),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: carp_yellow,
|
||||
completion_example: spring_blue,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: crystal_blue,
|
||||
project_file: oni_violet,
|
||||
selected: carp_yellow,
|
||||
file: fg,
|
||||
focused_border: carp_yellow,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: crystal_blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: crystal_blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: carp_yellow,
|
||||
inactive: comment,
|
||||
match_bg: carp_yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: crystal_blue,
|
||||
h2: carp_yellow,
|
||||
h3: oni_violet,
|
||||
code: autumn_green,
|
||||
code_border: Color::Rgb(65, 65, 80),
|
||||
link: sakura_pink,
|
||||
link_url: Color::Rgb(100, 100, 115),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: crystal_blue,
|
||||
header_focused: carp_yellow,
|
||||
divider: Color::Rgb(60, 60, 75),
|
||||
scroll_indicator: Color::Rgb(75, 75, 92),
|
||||
label: Color::Rgb(140, 138, 125),
|
||||
label_focused: Color::Rgb(170, 168, 155),
|
||||
label_dim: Color::Rgb(110, 108, 100),
|
||||
value: Color::Rgb(200, 195, 175),
|
||||
focused: carp_yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(75, 75, 92),
|
||||
path: Color::Rgb(140, 138, 125),
|
||||
border_magenta: oni_violet,
|
||||
border_green: autumn_green,
|
||||
border_cyan: spring_blue,
|
||||
separator: Color::Rgb(60, 60, 75),
|
||||
hint_active: Color::Rgb(220, 185, 120),
|
||||
hint_inactive: Color::Rgb(60, 60, 75),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: autumn_green,
|
||||
word_bg: Color::Rgb(45, 50, 50),
|
||||
alias: comment,
|
||||
stack_sig: oni_violet,
|
||||
description: fg,
|
||||
example: Color::Rgb(140, 138, 125),
|
||||
category_focused: carp_yellow,
|
||||
category_selected: crystal_blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(75, 75, 92),
|
||||
border_focused: carp_yellow,
|
||||
border_normal: Color::Rgb(60, 60, 75),
|
||||
header_desc: Color::Rgb(160, 158, 145),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: sakura_pink,
|
||||
author: crystal_blue,
|
||||
link: autumn_green,
|
||||
license: carp_yellow,
|
||||
prompt: Color::Rgb(160, 158, 145),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: autumn_green,
|
||||
mid: carp_yellow,
|
||||
high: wave_red,
|
||||
low_rgb: (118, 148, 106),
|
||||
mid_rgb: (230, 195, 132),
|
||||
high_rgb: (228, 104, 118),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(127, 180, 202),
|
||||
(230, 195, 132),
|
||||
(118, 148, 106),
|
||||
(228, 104, 118),
|
||||
(149, 127, 184),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: carp_yellow,
|
||||
button_selected_bg: carp_yellow,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,283 @@
|
||||
//! Letz Light palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(255, 255, 255);
|
||||
let off_white = Color::Rgb(245, 245, 247);
|
||||
let surface = Color::Rgb(235, 235, 240);
|
||||
let border = Color::Rgb(210, 210, 215);
|
||||
let text = Color::Rgb(29, 29, 31);
|
||||
let text_dim = Color::Rgb(110, 110, 115);
|
||||
let text_muted = Color::Rgb(160, 160, 165);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (255, 255, 255),
|
||||
surface: (235, 235, 240),
|
||||
surface2: (210, 210, 215),
|
||||
fg: (29, 29, 31),
|
||||
fg_dim: (110, 110, 115),
|
||||
fg_muted: (160, 160, 165),
|
||||
accent: (0, 112, 243),
|
||||
red: (209, 47, 27),
|
||||
green: (112, 127, 52),
|
||||
yellow: (200, 150, 20),
|
||||
blue: (0, 112, 243),
|
||||
purple: (173, 61, 164), // keyword
|
||||
cyan: (62, 128, 135), // function
|
||||
orange: (120, 73, 42), // preproc
|
||||
tempo_color: (112, 61, 170),
|
||||
bank_color: (0, 112, 243),
|
||||
pattern_color: (62, 128, 135),
|
||||
title_accent: (0, 112, 243),
|
||||
title_author: (112, 61, 170),
|
||||
secondary: (120, 73, 42),
|
||||
link_bright: [
|
||||
(173, 61, 164), (0, 112, 243), (120, 73, 42),
|
||||
(62, 128, 135), (112, 127, 52),
|
||||
],
|
||||
link_dim: [
|
||||
(235, 215, 235), (210, 225, 250), (240, 225, 210),
|
||||
(215, 235, 240), (225, 235, 215),
|
||||
],
|
||||
sparkle: [
|
||||
(0, 112, 243), (173, 61, 164), (112, 127, 52),
|
||||
(62, 128, 135), (120, 73, 42),
|
||||
],
|
||||
meter: [(112, 127, 52), (200, 150, 20), (209, 47, 27)],
|
||||
let keyword = Color::Rgb(173, 61, 164);
|
||||
let string = Color::Rgb(209, 47, 27);
|
||||
let comment = Color::Rgb(112, 127, 52);
|
||||
let number = Color::Rgb(39, 42, 216);
|
||||
let types = Color::Rgb(112, 61, 170);
|
||||
let function = Color::Rgb(62, 128, 135);
|
||||
let preproc = Color::Rgb(120, 73, 42);
|
||||
let accent = Color::Rgb(0, 112, 243);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (255, 255, 255),
|
||||
text_primary: text,
|
||||
text_muted: text_dim,
|
||||
text_dim: text_muted,
|
||||
border,
|
||||
header: accent,
|
||||
unfocused: text_muted,
|
||||
accent,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(220, 240, 220),
|
||||
playing_fg: comment,
|
||||
stopped_bg: Color::Rgb(245, 220, 220),
|
||||
stopped_fg: string,
|
||||
fill_on: comment,
|
||||
fill_off: text_muted,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: accent,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(200, 220, 250),
|
||||
selected_fg: accent,
|
||||
in_range_bg: Color::Rgb(220, 233, 250),
|
||||
in_range_fg: text,
|
||||
cursor: accent,
|
||||
selected: Color::Rgb(200, 220, 250),
|
||||
in_range: Color::Rgb(220, 233, 250),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(250, 225, 210),
|
||||
playing_active_fg: preproc,
|
||||
playing_inactive_bg: Color::Rgb(250, 240, 200),
|
||||
playing_inactive_fg: Color::Rgb(180, 140, 20),
|
||||
active_bg: Color::Rgb(210, 235, 240),
|
||||
active_fg: function,
|
||||
content_bg: Color::Rgb(195, 225, 230),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: text_dim,
|
||||
active_selected_bg: Color::Rgb(210, 215, 245),
|
||||
active_in_range_bg: Color::Rgb(220, 230, 245),
|
||||
link_bright: [
|
||||
(173, 61, 164),
|
||||
(0, 112, 243),
|
||||
(120, 73, 42),
|
||||
(62, 128, 135),
|
||||
(112, 127, 52),
|
||||
],
|
||||
link_dim: [
|
||||
(235, 215, 235),
|
||||
(210, 225, 250),
|
||||
(240, 225, 210),
|
||||
(215, 235, 240),
|
||||
(225, 235, 215),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(225, 215, 240),
|
||||
tempo_fg: types,
|
||||
bank_bg: Color::Rgb(210, 230, 250),
|
||||
bank_fg: accent,
|
||||
pattern_bg: Color::Rgb(210, 235, 235),
|
||||
pattern_fg: function,
|
||||
stats_bg: surface,
|
||||
stats_fg: text_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: accent,
|
||||
border_accent: keyword,
|
||||
border_warn: preproc,
|
||||
border_dim: text_muted,
|
||||
confirm: preproc,
|
||||
rename: keyword,
|
||||
input: accent,
|
||||
editor: accent,
|
||||
preview: text_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(250, 215, 215),
|
||||
error_fg: string,
|
||||
success_bg: Color::Rgb(215, 240, 215),
|
||||
success_fg: comment,
|
||||
info_bg: surface,
|
||||
info_fg: text,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(215, 240, 220),
|
||||
playing_fg: comment,
|
||||
staged_play_bg: Color::Rgb(230, 215, 240),
|
||||
staged_play_fg: keyword,
|
||||
staged_stop_bg: Color::Rgb(245, 215, 220),
|
||||
staged_stop_fg: string,
|
||||
edit_bg: Color::Rgb(210, 235, 240),
|
||||
edit_fg: function,
|
||||
hover_bg: Color::Rgb(240, 240, 242),
|
||||
hover_fg: text,
|
||||
muted_bg: Color::Rgb(230, 230, 235),
|
||||
muted_fg: text_muted,
|
||||
soloed_bg: Color::Rgb(250, 240, 200),
|
||||
soloed_fg: Color::Rgb(170, 130, 10),
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: string,
|
||||
connected: comment,
|
||||
listening: Color::Rgb(180, 140, 20),
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: off_white,
|
||||
executed_bg: Color::Rgb(230, 225, 245),
|
||||
selected_bg: Color::Rgb(250, 240, 210),
|
||||
emit: (text, Color::Rgb(250, 220, 215)),
|
||||
number: (number, Color::Rgb(220, 220, 250)),
|
||||
string: (string, Color::Rgb(250, 225, 220)),
|
||||
comment: (Color::Rgb(130, 145, 75), off_white),
|
||||
keyword: (keyword, Color::Rgb(240, 225, 240)),
|
||||
stack_op: (accent, Color::Rgb(220, 235, 250)),
|
||||
operator: (preproc, Color::Rgb(240, 230, 215)),
|
||||
sound: (function, Color::Rgb(215, 240, 240)),
|
||||
param: (types, Color::Rgb(230, 220, 240)),
|
||||
context: (preproc, Color::Rgb(240, 230, 215)),
|
||||
note: (comment, Color::Rgb(225, 240, 220)),
|
||||
interval: (Color::Rgb(90, 110, 40), Color::Rgb(225, 240, 215)),
|
||||
variable: (keyword, Color::Rgb(240, 225, 240)),
|
||||
vary: (Color::Rgb(180, 140, 20), Color::Rgb(250, 240, 210)),
|
||||
generator: (function, Color::Rgb(215, 240, 235)),
|
||||
user_defined: (preproc, Color::Rgb(240, 230, 215)),
|
||||
default: (text_dim, off_white),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: off_white,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: preproc,
|
||||
value: text_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: accent,
|
||||
text: text_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: text, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(210, 225, 250),
|
||||
selected_fg: text,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: text_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: text,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(200, 220, 250),
|
||||
completion_bg: surface,
|
||||
completion_fg: text,
|
||||
completion_selected: accent,
|
||||
completion_example: function,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: accent,
|
||||
project_file: keyword,
|
||||
selected: preproc,
|
||||
file: text,
|
||||
focused_border: accent,
|
||||
unfocused_border: text_muted,
|
||||
root: text,
|
||||
file_icon: text_muted,
|
||||
folder_icon: accent,
|
||||
empty_text: text_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: accent,
|
||||
cursor: text,
|
||||
hint: text_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: accent,
|
||||
inactive: text_muted,
|
||||
match_bg: Color::Rgb(255, 230, 80),
|
||||
match_fg: text,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: accent,
|
||||
h2: preproc,
|
||||
h3: keyword,
|
||||
code: comment,
|
||||
code_border: Color::Rgb(200, 200, 205),
|
||||
link: function,
|
||||
link_url: Color::Rgb(150, 150, 155),
|
||||
quote: text_muted,
|
||||
text,
|
||||
list: text,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: accent,
|
||||
header_focused: preproc,
|
||||
divider: Color::Rgb(200, 200, 205),
|
||||
scroll_indicator: Color::Rgb(180, 180, 185),
|
||||
label: Color::Rgb(120, 120, 125),
|
||||
label_focused: Color::Rgb(80, 80, 85),
|
||||
label_dim: Color::Rgb(150, 150, 155),
|
||||
value: Color::Rgb(60, 60, 65),
|
||||
focused: preproc,
|
||||
normal: text,
|
||||
dim: Color::Rgb(180, 180, 185),
|
||||
path: Color::Rgb(120, 120, 125),
|
||||
border_magenta: keyword,
|
||||
border_green: comment,
|
||||
border_cyan: function,
|
||||
separator: Color::Rgb(200, 200, 210),
|
||||
hint_active: preproc,
|
||||
hint_inactive: Color::Rgb(200, 200, 210),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: function,
|
||||
word_bg: Color::Rgb(215, 235, 240),
|
||||
alias: text_muted,
|
||||
stack_sig: keyword,
|
||||
description: text,
|
||||
example: Color::Rgb(110, 110, 120),
|
||||
category_focused: preproc,
|
||||
category_selected: accent,
|
||||
category_normal: text,
|
||||
category_dimmed: Color::Rgb(180, 180, 185),
|
||||
border_focused: preproc,
|
||||
border_normal: Color::Rgb(200, 200, 205),
|
||||
header_desc: Color::Rgb(100, 100, 110),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: accent,
|
||||
author: types,
|
||||
link: function,
|
||||
license: preproc,
|
||||
prompt: Color::Rgb(100, 100, 110),
|
||||
subtitle: text,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: comment,
|
||||
mid: Color::Rgb(200, 150, 20),
|
||||
high: string,
|
||||
low_rgb: (112, 127, 52),
|
||||
mid_rgb: (200, 150, 20),
|
||||
high_rgb: (209, 47, 27),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(0, 112, 243),
|
||||
(173, 61, 164),
|
||||
(112, 127, 52),
|
||||
(62, 128, 135),
|
||||
(120, 73, 42),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: accent,
|
||||
button_selected_bg: accent,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,68 @@
|
||||
//! Centralized color definitions for Cagire TUI.
|
||||
//! Supports multiple color schemes with runtime switching.
|
||||
|
||||
pub mod palette;
|
||||
pub mod build;
|
||||
mod catppuccin_latte;
|
||||
mod catppuccin_mocha;
|
||||
mod dracula;
|
||||
mod eden;
|
||||
mod ember;
|
||||
mod everforest;
|
||||
mod georges;
|
||||
mod fairyfloss;
|
||||
mod gruvbox_dark;
|
||||
mod hot_dog_stand;
|
||||
mod iceberg;
|
||||
mod jaipur;
|
||||
mod kanagawa;
|
||||
mod letz_light;
|
||||
mod monochrome_black;
|
||||
mod monochrome_white;
|
||||
mod monokai;
|
||||
mod nord;
|
||||
mod fauve;
|
||||
mod pitch_black;
|
||||
mod tropicalia;
|
||||
mod rose_pine;
|
||||
mod tokyo_night;
|
||||
pub mod transform;
|
||||
|
||||
use ratatui::style::Color;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Entry in the theme registry: id, display label, and palette constructor.
|
||||
pub struct ThemeEntry {
|
||||
pub id: &'static str,
|
||||
pub label: &'static str,
|
||||
pub palette: fn() -> palette::Palette,
|
||||
pub colors: fn() -> ThemeColors,
|
||||
}
|
||||
|
||||
/// All available themes.
|
||||
pub const THEMES: &[ThemeEntry] = &[
|
||||
ThemeEntry { id: "CatppuccinMocha", label: "Catppuccin Mocha", palette: catppuccin_mocha::palette },
|
||||
ThemeEntry { id: "CatppuccinLatte", label: "Catppuccin Latte", palette: catppuccin_latte::palette },
|
||||
ThemeEntry { id: "Nord", label: "Nord", palette: nord::palette },
|
||||
ThemeEntry { id: "Dracula", label: "Dracula", palette: dracula::palette },
|
||||
ThemeEntry { id: "GruvboxDark", label: "Gruvbox Dark", palette: gruvbox_dark::palette },
|
||||
ThemeEntry { id: "Monokai", label: "Monokai", palette: monokai::palette },
|
||||
ThemeEntry { id: "MonochromeBlack", label: "Monochrome (Black)", palette: monochrome_black::palette },
|
||||
ThemeEntry { id: "MonochromeWhite", label: "Monochrome (White)", palette: monochrome_white::palette },
|
||||
ThemeEntry { id: "PitchBlack", label: "Pitch Black", palette: pitch_black::palette },
|
||||
ThemeEntry { id: "TokyoNight", label: "Tokyo Night", palette: tokyo_night::palette },
|
||||
ThemeEntry { id: "RosePine", label: "Rosé Pine", palette: rose_pine::palette },
|
||||
ThemeEntry { id: "Kanagawa", label: "Kanagawa", palette: kanagawa::palette },
|
||||
ThemeEntry { id: "Fairyfloss", label: "Fairyfloss", palette: fairyfloss::palette },
|
||||
ThemeEntry { id: "HotDogStand", label: "Hot Dog Stand", palette: hot_dog_stand::palette },
|
||||
ThemeEntry { id: "LetzLight", label: "Letz Light", palette: letz_light::palette },
|
||||
ThemeEntry { id: "Ember", label: "Ember", palette: ember::palette },
|
||||
ThemeEntry { id: "Eden", label: "Eden", palette: eden::palette },
|
||||
ThemeEntry { id: "Georges", label: "Georges", palette: georges::palette },
|
||||
ThemeEntry { id: "Iceberg", label: "Iceberg", palette: iceberg::palette },
|
||||
ThemeEntry { id: "Everforest", label: "Everforest", palette: everforest::palette },
|
||||
ThemeEntry { id: "Fauve", label: "Fauve", palette: fauve::palette },
|
||||
ThemeEntry { id: "Tropicalia", label: "Tropicalia", palette: tropicalia::palette },
|
||||
ThemeEntry { id: "Jaipur", label: "Jaipur", palette: jaipur::palette },
|
||||
ThemeEntry { id: "CatppuccinMocha", label: "Catppuccin Mocha", colors: catppuccin_mocha::theme },
|
||||
ThemeEntry { id: "CatppuccinLatte", label: "Catppuccin Latte", colors: catppuccin_latte::theme },
|
||||
ThemeEntry { id: "Nord", label: "Nord", colors: nord::theme },
|
||||
ThemeEntry { id: "Dracula", label: "Dracula", colors: dracula::theme },
|
||||
ThemeEntry { id: "GruvboxDark", label: "Gruvbox Dark", colors: gruvbox_dark::theme },
|
||||
ThemeEntry { id: "Monokai", label: "Monokai", colors: monokai::theme },
|
||||
ThemeEntry { id: "MonochromeBlack", label: "Monochrome (Black)", colors: monochrome_black::theme },
|
||||
ThemeEntry { id: "MonochromeWhite", label: "Monochrome (White)", colors: monochrome_white::theme },
|
||||
ThemeEntry { id: "PitchBlack", label: "Pitch Black", colors: pitch_black::theme },
|
||||
ThemeEntry { id: "TokyoNight", label: "Tokyo Night", colors: tokyo_night::theme },
|
||||
ThemeEntry { id: "RosePine", label: "Rosé Pine", colors: rose_pine::theme },
|
||||
ThemeEntry { id: "Kanagawa", label: "Kanagawa", colors: kanagawa::theme },
|
||||
ThemeEntry { id: "Fairyfloss", label: "Fairyfloss", colors: fairyfloss::theme },
|
||||
ThemeEntry { id: "HotDogStand", label: "Hot Dog Stand", colors: hot_dog_stand::theme },
|
||||
ThemeEntry { id: "LetzLight", label: "Letz Light", colors: letz_light::theme },
|
||||
ThemeEntry { id: "Ember", label: "Ember", colors: ember::theme },
|
||||
ThemeEntry { id: "Eden", label: "Eden", colors: eden::theme },
|
||||
ThemeEntry { id: "Georges", label: "Georges", colors: georges::theme },
|
||||
];
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_THEME: RefCell<Rc<ThemeColors>> = RefCell::new(Rc::new(build::build(&(THEMES[0].palette)())));
|
||||
static CURRENT_THEME: RefCell<ThemeColors> = RefCell::new((THEMES[0].colors)());
|
||||
}
|
||||
|
||||
/// Return the current thread-local theme (cheap Rc clone, not a deep copy).
|
||||
pub fn get() -> Rc<ThemeColors> {
|
||||
CURRENT_THEME.with(|t| Rc::clone(&t.borrow()))
|
||||
pub fn get() -> ThemeColors {
|
||||
CURRENT_THEME.with(|t| t.borrow().clone())
|
||||
}
|
||||
|
||||
/// Set the current thread-local theme.
|
||||
pub fn set(theme: ThemeColors) {
|
||||
CURRENT_THEME.with(|t| *t.borrow_mut() = Rc::new(theme));
|
||||
CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
|
||||
}
|
||||
|
||||
/// Complete set of resolved colors for all UI components.
|
||||
#[derive(Clone)]
|
||||
pub struct ThemeColors {
|
||||
pub ui: UiColors,
|
||||
@@ -111,7 +93,6 @@ pub struct ThemeColors {
|
||||
pub confirm: ConfirmColors,
|
||||
}
|
||||
|
||||
/// Core UI colors: background, text, borders.
|
||||
#[derive(Clone)]
|
||||
pub struct UiColors {
|
||||
pub bg: Color,
|
||||
@@ -126,7 +107,6 @@ pub struct UiColors {
|
||||
pub surface: Color,
|
||||
}
|
||||
|
||||
/// Playback status bar colors.
|
||||
#[derive(Clone)]
|
||||
pub struct StatusColors {
|
||||
pub playing_bg: Color,
|
||||
@@ -138,7 +118,6 @@ pub struct StatusColors {
|
||||
pub fill_bg: Color,
|
||||
}
|
||||
|
||||
/// Step grid selection and cursor colors.
|
||||
#[derive(Clone)]
|
||||
pub struct SelectionColors {
|
||||
pub cursor_bg: Color,
|
||||
@@ -152,7 +131,6 @@ pub struct SelectionColors {
|
||||
pub in_range: Color,
|
||||
}
|
||||
|
||||
/// Step tile colors for various states.
|
||||
#[derive(Clone)]
|
||||
pub struct TileColors {
|
||||
pub playing_active_bg: Color,
|
||||
@@ -170,12 +148,10 @@ pub struct TileColors {
|
||||
pub link_dim: [(u8, u8, u8); 5],
|
||||
}
|
||||
|
||||
/// Top header bar segment colors.
|
||||
#[derive(Clone)]
|
||||
pub struct HeaderColors {
|
||||
pub tempo_bg: Color,
|
||||
pub tempo_fg: Color,
|
||||
pub beat_bg: Color,
|
||||
pub bank_bg: Color,
|
||||
pub bank_fg: Color,
|
||||
pub pattern_bg: Color,
|
||||
@@ -184,7 +160,6 @@ pub struct HeaderColors {
|
||||
pub stats_fg: Color,
|
||||
}
|
||||
|
||||
/// Modal dialog border colors.
|
||||
#[derive(Clone)]
|
||||
pub struct ModalColors {
|
||||
pub border: Color,
|
||||
@@ -198,7 +173,6 @@ pub struct ModalColors {
|
||||
pub preview: Color,
|
||||
}
|
||||
|
||||
/// Flash notification colors.
|
||||
#[derive(Clone)]
|
||||
pub struct FlashColors {
|
||||
pub error_bg: Color,
|
||||
@@ -209,7 +183,6 @@ pub struct FlashColors {
|
||||
pub info_fg: Color,
|
||||
}
|
||||
|
||||
/// Pattern list row state colors.
|
||||
#[derive(Clone)]
|
||||
pub struct ListColors {
|
||||
pub playing_bg: Color,
|
||||
@@ -228,7 +201,6 @@ pub struct ListColors {
|
||||
pub soloed_fg: Color,
|
||||
}
|
||||
|
||||
/// Ableton Link status indicator colors.
|
||||
#[derive(Clone)]
|
||||
pub struct LinkStatusColors {
|
||||
pub disabled: Color,
|
||||
@@ -236,7 +208,6 @@ pub struct LinkStatusColors {
|
||||
pub listening: Color,
|
||||
}
|
||||
|
||||
/// Syntax highlighting (fg, bg) pairs per token category.
|
||||
#[derive(Clone)]
|
||||
pub struct SyntaxColors {
|
||||
pub gap_bg: Color,
|
||||
@@ -261,35 +232,30 @@ pub struct SyntaxColors {
|
||||
pub default: (Color, Color),
|
||||
}
|
||||
|
||||
/// Alternating table row colors.
|
||||
#[derive(Clone)]
|
||||
pub struct TableColors {
|
||||
pub row_even: Color,
|
||||
pub row_odd: Color,
|
||||
}
|
||||
|
||||
/// Value display colors.
|
||||
#[derive(Clone)]
|
||||
pub struct ValuesColors {
|
||||
pub tempo: Color,
|
||||
pub value: Color,
|
||||
}
|
||||
|
||||
/// Keyboard hint key/text colors.
|
||||
#[derive(Clone)]
|
||||
pub struct HintColors {
|
||||
pub key: Color,
|
||||
pub text: Color,
|
||||
}
|
||||
|
||||
/// View badge pill colors.
|
||||
#[derive(Clone)]
|
||||
pub struct ViewBadgeColors {
|
||||
pub bg: Color,
|
||||
pub fg: Color,
|
||||
}
|
||||
|
||||
/// Navigation minimap tile colors.
|
||||
#[derive(Clone)]
|
||||
pub struct NavColors {
|
||||
pub selected_bg: Color,
|
||||
@@ -298,7 +264,6 @@ pub struct NavColors {
|
||||
pub unselected_fg: Color,
|
||||
}
|
||||
|
||||
/// Script editor colors.
|
||||
#[derive(Clone)]
|
||||
pub struct EditorWidgetColors {
|
||||
pub cursor_bg: Color,
|
||||
@@ -310,7 +275,6 @@ pub struct EditorWidgetColors {
|
||||
pub completion_example: Color,
|
||||
}
|
||||
|
||||
/// File and sample browser colors.
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserColors {
|
||||
pub directory: Color,
|
||||
@@ -325,7 +289,6 @@ pub struct BrowserColors {
|
||||
pub empty_text: Color,
|
||||
}
|
||||
|
||||
/// Text input field colors.
|
||||
#[derive(Clone)]
|
||||
pub struct InputColors {
|
||||
pub text: Color,
|
||||
@@ -333,7 +296,6 @@ pub struct InputColors {
|
||||
pub hint: Color,
|
||||
}
|
||||
|
||||
/// Search bar and match highlight colors.
|
||||
#[derive(Clone)]
|
||||
pub struct SearchColors {
|
||||
pub active: Color,
|
||||
@@ -342,7 +304,6 @@ pub struct SearchColors {
|
||||
pub match_fg: Color,
|
||||
}
|
||||
|
||||
/// Markdown renderer colors.
|
||||
#[derive(Clone)]
|
||||
pub struct MarkdownColors {
|
||||
pub h1: Color,
|
||||
@@ -357,7 +318,6 @@ pub struct MarkdownColors {
|
||||
pub list: Color,
|
||||
}
|
||||
|
||||
/// Engine view panel colors.
|
||||
#[derive(Clone)]
|
||||
pub struct EngineColors {
|
||||
pub header: Color,
|
||||
@@ -380,7 +340,6 @@ pub struct EngineColors {
|
||||
pub hint_inactive: Color,
|
||||
}
|
||||
|
||||
/// Dictionary view colors.
|
||||
#[derive(Clone)]
|
||||
pub struct DictColors {
|
||||
pub word_name: Color,
|
||||
@@ -398,7 +357,6 @@ pub struct DictColors {
|
||||
pub header_desc: Color,
|
||||
}
|
||||
|
||||
/// Title screen colors.
|
||||
#[derive(Clone)]
|
||||
pub struct TitleColors {
|
||||
pub big_title: Color,
|
||||
@@ -409,7 +367,6 @@ pub struct TitleColors {
|
||||
pub subtitle: Color,
|
||||
}
|
||||
|
||||
/// VU meter and spectrum level colors.
|
||||
#[derive(Clone)]
|
||||
pub struct MeterColors {
|
||||
pub low: Color,
|
||||
@@ -420,13 +377,11 @@ pub struct MeterColors {
|
||||
pub high_rgb: (u8, u8, u8),
|
||||
}
|
||||
|
||||
/// Sparkle particle colors.
|
||||
#[derive(Clone)]
|
||||
pub struct SparkleColors {
|
||||
pub colors: [(u8, u8, u8); 5],
|
||||
}
|
||||
|
||||
/// Confirm dialog colors.
|
||||
#[derive(Clone)]
|
||||
pub struct ConfirmColors {
|
||||
pub border: Color,
|
||||
|
||||
@@ -1,41 +1,280 @@
|
||||
//! Monochrome (black background) palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(0, 0, 0);
|
||||
let surface = Color::Rgb(18, 18, 18);
|
||||
let surface2 = Color::Rgb(30, 30, 30);
|
||||
let border = Color::Rgb(60, 60, 60);
|
||||
let fg = Color::Rgb(255, 255, 255);
|
||||
let fg_dim = Color::Rgb(180, 180, 180);
|
||||
let fg_muted = Color::Rgb(120, 120, 120);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (0, 0, 0),
|
||||
surface: (18, 18, 18),
|
||||
surface2: (30, 30, 30),
|
||||
fg: (255, 255, 255),
|
||||
fg_dim: (180, 180, 180),
|
||||
fg_muted: (120, 120, 120),
|
||||
accent: (255, 255, 255),
|
||||
red: (180, 180, 180),
|
||||
green: (255, 255, 255),
|
||||
yellow: (180, 180, 180),
|
||||
blue: (180, 180, 180),
|
||||
purple: (180, 180, 180),
|
||||
cyan: (255, 255, 255),
|
||||
orange: (255, 255, 255),
|
||||
tempo_color: (255, 255, 255),
|
||||
bank_color: (180, 180, 180),
|
||||
pattern_color: (180, 180, 180),
|
||||
title_accent: (255, 255, 255),
|
||||
title_author: (180, 180, 180),
|
||||
secondary: (120, 120, 120),
|
||||
link_bright: [
|
||||
(255, 255, 255), (200, 200, 200), (160, 160, 160),
|
||||
(220, 220, 220), (180, 180, 180),
|
||||
],
|
||||
link_dim: [
|
||||
(60, 60, 60), (50, 50, 50), (45, 45, 45),
|
||||
(55, 55, 55), (48, 48, 48),
|
||||
],
|
||||
sparkle: [
|
||||
(255, 255, 255), (200, 200, 200), (160, 160, 160),
|
||||
(220, 220, 220), (180, 180, 180),
|
||||
],
|
||||
meter: [(120, 120, 120), (180, 180, 180), (255, 255, 255)],
|
||||
let bright = Color::Rgb(255, 255, 255);
|
||||
let medium = Color::Rgb(180, 180, 180);
|
||||
let dim = Color::Rgb(120, 120, 120);
|
||||
let dark = Color::Rgb(80, 80, 80);
|
||||
let darker = Color::Rgb(50, 50, 50);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (0, 0, 0),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: bright,
|
||||
unfocused: fg_muted,
|
||||
accent: bright,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 40, 40),
|
||||
playing_fg: bright,
|
||||
stopped_bg: Color::Rgb(25, 25, 25),
|
||||
stopped_fg: medium,
|
||||
fill_on: bright,
|
||||
fill_off: dark,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: bright,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(60, 60, 60),
|
||||
selected_fg: bright,
|
||||
in_range_bg: Color::Rgb(40, 40, 40),
|
||||
in_range_fg: fg,
|
||||
cursor: bright,
|
||||
selected: Color::Rgb(60, 60, 60),
|
||||
in_range: Color::Rgb(40, 40, 40),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(70, 70, 70),
|
||||
playing_active_fg: bright,
|
||||
playing_inactive_bg: Color::Rgb(50, 50, 50),
|
||||
playing_inactive_fg: medium,
|
||||
active_bg: Color::Rgb(45, 45, 45),
|
||||
active_fg: bright,
|
||||
content_bg: Color::Rgb(55, 55, 55),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(80, 80, 80),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 55),
|
||||
link_bright: [
|
||||
(255, 255, 255),
|
||||
(200, 200, 200),
|
||||
(160, 160, 160),
|
||||
(220, 220, 220),
|
||||
(180, 180, 180),
|
||||
],
|
||||
link_dim: [
|
||||
(60, 60, 60),
|
||||
(50, 50, 50),
|
||||
(45, 45, 45),
|
||||
(55, 55, 55),
|
||||
(48, 48, 48),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 50, 50),
|
||||
tempo_fg: bright,
|
||||
bank_bg: Color::Rgb(40, 40, 40),
|
||||
bank_fg: medium,
|
||||
pattern_bg: Color::Rgb(35, 35, 35),
|
||||
pattern_fg: medium,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: bright,
|
||||
border_accent: medium,
|
||||
border_warn: fg_dim,
|
||||
border_dim: fg_muted,
|
||||
confirm: medium,
|
||||
rename: medium,
|
||||
input: bright,
|
||||
editor: bright,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 60, 60),
|
||||
error_fg: bright,
|
||||
success_bg: Color::Rgb(50, 50, 50),
|
||||
success_fg: bright,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 50, 50),
|
||||
playing_fg: bright,
|
||||
staged_play_bg: Color::Rgb(45, 45, 45),
|
||||
staged_play_fg: medium,
|
||||
staged_stop_bg: Color::Rgb(35, 35, 35),
|
||||
staged_stop_fg: dim,
|
||||
edit_bg: Color::Rgb(40, 40, 40),
|
||||
edit_fg: bright,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(22, 22, 22),
|
||||
muted_fg: dark,
|
||||
soloed_bg: Color::Rgb(60, 60, 60),
|
||||
soloed_fg: bright,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: dim,
|
||||
connected: bright,
|
||||
listening: medium,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(35, 35, 35),
|
||||
selected_bg: Color::Rgb(55, 55, 55),
|
||||
emit: (bright, Color::Rgb(45, 45, 45)),
|
||||
number: (medium, Color::Rgb(35, 35, 35)),
|
||||
string: (bright, Color::Rgb(40, 40, 40)),
|
||||
comment: (dark, bg),
|
||||
keyword: (bright, Color::Rgb(50, 50, 50)),
|
||||
stack_op: (medium, Color::Rgb(30, 30, 30)),
|
||||
operator: (medium, Color::Rgb(35, 35, 35)),
|
||||
sound: (bright, Color::Rgb(45, 45, 45)),
|
||||
param: (medium, Color::Rgb(35, 35, 35)),
|
||||
context: (medium, Color::Rgb(30, 30, 30)),
|
||||
note: (bright, Color::Rgb(40, 40, 40)),
|
||||
interval: (medium, Color::Rgb(35, 35, 35)),
|
||||
variable: (medium, Color::Rgb(30, 30, 30)),
|
||||
vary: (dim, Color::Rgb(25, 25, 25)),
|
||||
generator: (bright, Color::Rgb(45, 45, 45)),
|
||||
user_defined: (medium, Color::Rgb(35, 35, 35)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: bright,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: bright,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 60, 60),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(60, 60, 60),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: bright,
|
||||
completion_example: medium,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: medium,
|
||||
project_file: bright,
|
||||
selected: bright,
|
||||
file: fg,
|
||||
focused_border: bright,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: medium,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: bright,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: bright,
|
||||
inactive: fg_muted,
|
||||
match_bg: bright,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: bright,
|
||||
h2: medium,
|
||||
h3: dim,
|
||||
code: medium,
|
||||
code_border: Color::Rgb(60, 60, 60),
|
||||
link: bright,
|
||||
link_url: dim,
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: bright,
|
||||
header_focused: bright,
|
||||
divider: Color::Rgb(50, 50, 50),
|
||||
scroll_indicator: Color::Rgb(70, 70, 70),
|
||||
label: dim,
|
||||
label_focused: medium,
|
||||
label_dim: dark,
|
||||
value: fg,
|
||||
focused: bright,
|
||||
normal: fg,
|
||||
dim: dark,
|
||||
path: dim,
|
||||
border_magenta: medium,
|
||||
border_green: medium,
|
||||
border_cyan: medium,
|
||||
separator: Color::Rgb(50, 50, 50),
|
||||
hint_active: bright,
|
||||
hint_inactive: darker,
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: bright,
|
||||
word_bg: Color::Rgb(30, 30, 30),
|
||||
alias: fg_muted,
|
||||
stack_sig: medium,
|
||||
description: fg,
|
||||
example: dim,
|
||||
category_focused: bright,
|
||||
category_selected: medium,
|
||||
category_normal: fg,
|
||||
category_dimmed: dark,
|
||||
border_focused: bright,
|
||||
border_normal: darker,
|
||||
header_desc: dim,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: bright,
|
||||
author: medium,
|
||||
link: medium,
|
||||
license: dim,
|
||||
prompt: dim,
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: dim,
|
||||
mid: medium,
|
||||
high: bright,
|
||||
low_rgb: (120, 120, 120),
|
||||
mid_rgb: (180, 180, 180),
|
||||
high_rgb: (255, 255, 255),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(255, 255, 255),
|
||||
(200, 200, 200),
|
||||
(160, 160, 160),
|
||||
(220, 220, 220),
|
||||
(180, 180, 180),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: bright,
|
||||
button_selected_bg: bright,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,280 @@
|
||||
//! Monochrome (white background) palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(255, 255, 255);
|
||||
let surface = Color::Rgb(240, 240, 240);
|
||||
let surface2 = Color::Rgb(225, 225, 225);
|
||||
let border = Color::Rgb(180, 180, 180);
|
||||
let fg = Color::Rgb(0, 0, 0);
|
||||
let fg_dim = Color::Rgb(80, 80, 80);
|
||||
let fg_muted = Color::Rgb(140, 140, 140);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (255, 255, 255),
|
||||
surface: (240, 240, 240),
|
||||
surface2: (225, 225, 225),
|
||||
fg: (0, 0, 0),
|
||||
fg_dim: (80, 80, 80),
|
||||
fg_muted: (140, 140, 140),
|
||||
accent: (0, 0, 0),
|
||||
red: (140, 140, 140),
|
||||
green: (0, 0, 0),
|
||||
yellow: (80, 80, 80),
|
||||
blue: (80, 80, 80),
|
||||
purple: (80, 80, 80),
|
||||
cyan: (0, 0, 0),
|
||||
orange: (0, 0, 0),
|
||||
tempo_color: (0, 0, 0),
|
||||
bank_color: (80, 80, 80),
|
||||
pattern_color: (80, 80, 80),
|
||||
title_accent: (0, 0, 0),
|
||||
title_author: (80, 80, 80),
|
||||
secondary: (140, 140, 140),
|
||||
link_bright: [
|
||||
(0, 0, 0), (60, 60, 60), (100, 100, 100),
|
||||
(40, 40, 40), (80, 80, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(200, 200, 200), (210, 210, 210), (215, 215, 215),
|
||||
(205, 205, 205), (212, 212, 212),
|
||||
],
|
||||
sparkle: [
|
||||
(0, 0, 0), (60, 60, 60), (100, 100, 100),
|
||||
(40, 40, 40), (80, 80, 80),
|
||||
],
|
||||
meter: [(140, 140, 140), (80, 80, 80), (0, 0, 0)],
|
||||
let dark = Color::Rgb(0, 0, 0);
|
||||
let medium = Color::Rgb(80, 80, 80);
|
||||
let dim = Color::Rgb(140, 140, 140);
|
||||
let light = Color::Rgb(180, 180, 180);
|
||||
let lighter = Color::Rgb(210, 210, 210);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (255, 255, 255),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: dark,
|
||||
unfocused: fg_muted,
|
||||
accent: dark,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(210, 210, 210),
|
||||
playing_fg: dark,
|
||||
stopped_bg: Color::Rgb(230, 230, 230),
|
||||
stopped_fg: medium,
|
||||
fill_on: dark,
|
||||
fill_off: light,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: dark,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
selected_fg: dark,
|
||||
in_range_bg: Color::Rgb(220, 220, 220),
|
||||
in_range_fg: fg,
|
||||
cursor: dark,
|
||||
selected: Color::Rgb(200, 200, 200),
|
||||
in_range: Color::Rgb(220, 220, 220),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(180, 180, 180),
|
||||
playing_active_fg: dark,
|
||||
playing_inactive_bg: Color::Rgb(200, 200, 200),
|
||||
playing_inactive_fg: medium,
|
||||
active_bg: Color::Rgb(210, 210, 210),
|
||||
active_fg: dark,
|
||||
content_bg: Color::Rgb(195, 195, 195),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(170, 170, 170),
|
||||
active_in_range_bg: Color::Rgb(195, 195, 195),
|
||||
link_bright: [
|
||||
(0, 0, 0),
|
||||
(60, 60, 60),
|
||||
(100, 100, 100),
|
||||
(40, 40, 40),
|
||||
(80, 80, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(200, 200, 200),
|
||||
(210, 210, 210),
|
||||
(215, 215, 215),
|
||||
(205, 205, 205),
|
||||
(212, 212, 212),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(200, 200, 200),
|
||||
tempo_fg: dark,
|
||||
bank_bg: Color::Rgb(215, 215, 215),
|
||||
bank_fg: medium,
|
||||
pattern_bg: Color::Rgb(220, 220, 220),
|
||||
pattern_fg: medium,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: dark,
|
||||
border_accent: medium,
|
||||
border_warn: fg_dim,
|
||||
border_dim: fg_muted,
|
||||
confirm: medium,
|
||||
rename: medium,
|
||||
input: dark,
|
||||
editor: dark,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(200, 200, 200),
|
||||
error_fg: dark,
|
||||
success_bg: Color::Rgb(210, 210, 210),
|
||||
success_fg: dark,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(200, 200, 200),
|
||||
playing_fg: dark,
|
||||
staged_play_bg: Color::Rgb(210, 210, 210),
|
||||
staged_play_fg: medium,
|
||||
staged_stop_bg: Color::Rgb(220, 220, 220),
|
||||
staged_stop_fg: dim,
|
||||
edit_bg: Color::Rgb(215, 215, 215),
|
||||
edit_fg: dark,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(235, 235, 235),
|
||||
muted_fg: light,
|
||||
soloed_bg: Color::Rgb(190, 190, 190),
|
||||
soloed_fg: dark,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: dim,
|
||||
connected: dark,
|
||||
listening: medium,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(220, 220, 220),
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
emit: (dark, Color::Rgb(215, 215, 215)),
|
||||
number: (medium, Color::Rgb(225, 225, 225)),
|
||||
string: (dark, Color::Rgb(220, 220, 220)),
|
||||
comment: (light, bg),
|
||||
keyword: (dark, Color::Rgb(205, 205, 205)),
|
||||
stack_op: (medium, Color::Rgb(230, 230, 230)),
|
||||
operator: (medium, Color::Rgb(225, 225, 225)),
|
||||
sound: (dark, Color::Rgb(215, 215, 215)),
|
||||
param: (medium, Color::Rgb(225, 225, 225)),
|
||||
context: (medium, Color::Rgb(230, 230, 230)),
|
||||
note: (dark, Color::Rgb(220, 220, 220)),
|
||||
interval: (medium, Color::Rgb(225, 225, 225)),
|
||||
variable: (medium, Color::Rgb(230, 230, 230)),
|
||||
vary: (dim, Color::Rgb(235, 235, 235)),
|
||||
generator: (dark, Color::Rgb(215, 215, 215)),
|
||||
user_defined: (medium, Color::Rgb(225, 225, 225)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: dark,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: dark,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(200, 200, 200),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: dark,
|
||||
completion_example: medium,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: medium,
|
||||
project_file: dark,
|
||||
selected: dark,
|
||||
file: fg,
|
||||
focused_border: dark,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: medium,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: dark,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: dark,
|
||||
inactive: fg_muted,
|
||||
match_bg: dark,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: dark,
|
||||
h2: medium,
|
||||
h3: dim,
|
||||
code: medium,
|
||||
code_border: Color::Rgb(200, 200, 200),
|
||||
link: dark,
|
||||
link_url: dim,
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: dark,
|
||||
header_focused: dark,
|
||||
divider: Color::Rgb(210, 210, 210),
|
||||
scroll_indicator: Color::Rgb(180, 180, 180),
|
||||
label: dim,
|
||||
label_focused: medium,
|
||||
label_dim: light,
|
||||
value: fg,
|
||||
focused: dark,
|
||||
normal: fg,
|
||||
dim: light,
|
||||
path: dim,
|
||||
border_magenta: medium,
|
||||
border_green: medium,
|
||||
border_cyan: medium,
|
||||
separator: Color::Rgb(210, 210, 210),
|
||||
hint_active: dark,
|
||||
hint_inactive: lighter,
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: dark,
|
||||
word_bg: Color::Rgb(230, 230, 230),
|
||||
alias: fg_muted,
|
||||
stack_sig: medium,
|
||||
description: fg,
|
||||
example: dim,
|
||||
category_focused: dark,
|
||||
category_selected: medium,
|
||||
category_normal: fg,
|
||||
category_dimmed: light,
|
||||
border_focused: dark,
|
||||
border_normal: lighter,
|
||||
header_desc: dim,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: dark,
|
||||
author: medium,
|
||||
link: medium,
|
||||
license: dim,
|
||||
prompt: dim,
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: dim,
|
||||
mid: medium,
|
||||
high: dark,
|
||||
low_rgb: (140, 140, 140),
|
||||
mid_rgb: (80, 80, 80),
|
||||
high_rgb: (0, 0, 0),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(0, 0, 0),
|
||||
(60, 60, 60),
|
||||
(100, 100, 100),
|
||||
(40, 40, 40),
|
||||
(80, 80, 80),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: dark,
|
||||
button_selected_bg: dark,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,281 @@
|
||||
//! Monokai palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(39, 40, 34);
|
||||
let bg_light = Color::Rgb(53, 54, 47);
|
||||
let bg_lighter = Color::Rgb(70, 71, 62);
|
||||
let fg = Color::Rgb(248, 248, 242);
|
||||
let fg_dim = Color::Rgb(190, 190, 180);
|
||||
let comment = Color::Rgb(117, 113, 94);
|
||||
let pink = Color::Rgb(249, 38, 114);
|
||||
let green = Color::Rgb(166, 226, 46);
|
||||
let yellow = Color::Rgb(230, 219, 116);
|
||||
let blue = Color::Rgb(102, 217, 239);
|
||||
let purple = Color::Rgb(174, 129, 255);
|
||||
let orange = Color::Rgb(253, 151, 31);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (39, 40, 34),
|
||||
surface: (53, 54, 47),
|
||||
surface2: (70, 71, 62),
|
||||
fg: (248, 248, 242),
|
||||
fg_dim: (190, 190, 180),
|
||||
fg_muted: (117, 113, 94),
|
||||
accent: (249, 38, 114), // pink
|
||||
red: (249, 38, 114),
|
||||
green: (166, 226, 46),
|
||||
yellow: (230, 219, 116),
|
||||
blue: (102, 217, 239),
|
||||
purple: (174, 129, 255),
|
||||
cyan: (102, 217, 239),
|
||||
orange: (253, 151, 31),
|
||||
tempo_color: (249, 38, 114),
|
||||
bank_color: (102, 217, 239),
|
||||
pattern_color: (166, 226, 46),
|
||||
title_accent: (249, 38, 114),
|
||||
title_author: (102, 217, 239),
|
||||
secondary: (253, 151, 31),
|
||||
link_bright: [
|
||||
(249, 38, 114), (174, 129, 255), (253, 151, 31),
|
||||
(102, 217, 239), (166, 226, 46),
|
||||
],
|
||||
link_dim: [
|
||||
(90, 40, 60), (70, 55, 90), (85, 60, 35),
|
||||
(50, 75, 85), (60, 80, 40),
|
||||
],
|
||||
sparkle: [
|
||||
(102, 217, 239), (253, 151, 31), (166, 226, 46),
|
||||
(249, 38, 114), (174, 129, 255),
|
||||
],
|
||||
meter: [(155, 215, 45), (220, 210, 105), (240, 50, 110)],
|
||||
let darker_bg = Color::Rgb(30, 31, 26);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (39, 40, 34),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: blue,
|
||||
unfocused: comment,
|
||||
accent: pink,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 65, 40),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(70, 40, 55),
|
||||
stopped_fg: pink,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: pink,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(85, 70, 80),
|
||||
selected_fg: pink,
|
||||
in_range_bg: Color::Rgb(70, 65, 70),
|
||||
in_range_fg: fg,
|
||||
cursor: pink,
|
||||
selected: Color::Rgb(85, 70, 80),
|
||||
in_range: Color::Rgb(70, 65, 70),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(90, 65, 45),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 50),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(55, 75, 70),
|
||||
active_fg: blue,
|
||||
content_bg: Color::Rgb(62, 82, 77),
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(85, 65, 80),
|
||||
active_in_range_bg: Color::Rgb(70, 65, 70),
|
||||
link_bright: [
|
||||
(249, 38, 114),
|
||||
(174, 129, 255),
|
||||
(253, 151, 31),
|
||||
(102, 217, 239),
|
||||
(166, 226, 46),
|
||||
],
|
||||
link_dim: [
|
||||
(90, 40, 60),
|
||||
(70, 55, 90),
|
||||
(85, 60, 35),
|
||||
(50, 75, 85),
|
||||
(60, 80, 40),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(75, 50, 65),
|
||||
tempo_fg: pink,
|
||||
bank_bg: Color::Rgb(50, 70, 75),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(55, 75, 50),
|
||||
pattern_fg: green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: blue,
|
||||
border_accent: pink,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(75, 40, 55),
|
||||
error_fg: pink,
|
||||
success_bg: Color::Rgb(50, 70, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 70, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 80),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(80, 45, 60),
|
||||
staged_stop_fg: pink,
|
||||
edit_bg: Color::Rgb(50, 70, 70),
|
||||
edit_fg: blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(48, 50, 45),
|
||||
muted_fg: comment,
|
||||
soloed_bg: Color::Rgb(70, 65, 45),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: pink,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 55),
|
||||
selected_bg: Color::Rgb(85, 75, 50),
|
||||
emit: (fg, Color::Rgb(85, 55, 65)),
|
||||
number: (purple, Color::Rgb(60, 50, 75)),
|
||||
string: (yellow, Color::Rgb(70, 65, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (pink, Color::Rgb(80, 45, 60)),
|
||||
stack_op: (blue, Color::Rgb(50, 70, 75)),
|
||||
operator: (pink, Color::Rgb(80, 45, 60)),
|
||||
sound: (blue, Color::Rgb(50, 70, 75)),
|
||||
param: (orange, Color::Rgb(80, 60, 40)),
|
||||
context: (orange, Color::Rgb(80, 60, 40)),
|
||||
note: (green, Color::Rgb(55, 75, 45)),
|
||||
interval: (Color::Rgb(180, 235, 80), Color::Rgb(55, 75, 40)),
|
||||
variable: (green, Color::Rgb(55, 75, 45)),
|
||||
vary: (yellow, Color::Rgb(70, 65, 45)),
|
||||
generator: (blue, Color::Rgb(50, 70, 70)),
|
||||
user_defined: (orange, Color::Rgb(60, 50, 30)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(80, 60, 75),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(75, 70, 75),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: blue,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(85, 85, 75),
|
||||
link: pink,
|
||||
link_url: Color::Rgb(130, 125, 115),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(80, 80, 72),
|
||||
scroll_indicator: Color::Rgb(95, 95, 88),
|
||||
label: Color::Rgb(150, 145, 135),
|
||||
label_focused: Color::Rgb(180, 175, 165),
|
||||
label_dim: Color::Rgb(120, 115, 105),
|
||||
value: Color::Rgb(210, 205, 195),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(95, 95, 88),
|
||||
path: Color::Rgb(150, 145, 135),
|
||||
border_magenta: pink,
|
||||
border_green: green,
|
||||
border_cyan: blue,
|
||||
separator: Color::Rgb(80, 80, 72),
|
||||
hint_active: Color::Rgb(220, 200, 100),
|
||||
hint_inactive: Color::Rgb(80, 80, 72),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 65, 60),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(150, 145, 135),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(95, 95, 88),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(80, 80, 72),
|
||||
header_desc: Color::Rgb(170, 165, 155),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: pink,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(170, 165, 155),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: pink,
|
||||
low_rgb: (155, 215, 45),
|
||||
mid_rgb: (220, 210, 105),
|
||||
high_rgb: (240, 50, 110),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(102, 217, 239),
|
||||
(253, 151, 31),
|
||||
(166, 226, 46),
|
||||
(249, 38, 114),
|
||||
(174, 129, 255),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,284 @@
|
||||
//! Nord palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let polar_night0 = Color::Rgb(46, 52, 64);
|
||||
let polar_night1 = Color::Rgb(59, 66, 82);
|
||||
let polar_night2 = Color::Rgb(67, 76, 94);
|
||||
let polar_night3 = Color::Rgb(76, 86, 106);
|
||||
let snow_storm0 = Color::Rgb(216, 222, 233);
|
||||
let snow_storm2 = Color::Rgb(236, 239, 244);
|
||||
let frost0 = Color::Rgb(143, 188, 187);
|
||||
let frost1 = Color::Rgb(136, 192, 208);
|
||||
let frost2 = Color::Rgb(129, 161, 193);
|
||||
let aurora_red = Color::Rgb(191, 97, 106);
|
||||
let aurora_orange = Color::Rgb(208, 135, 112);
|
||||
let aurora_yellow = Color::Rgb(235, 203, 139);
|
||||
let aurora_green = Color::Rgb(163, 190, 140);
|
||||
let aurora_purple = Color::Rgb(180, 142, 173);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (46, 52, 64),
|
||||
surface: (59, 66, 82),
|
||||
surface2: (67, 76, 94),
|
||||
fg: (236, 239, 244),
|
||||
fg_dim: (216, 222, 233),
|
||||
fg_muted: (76, 86, 106),
|
||||
accent: (136, 192, 208), // frost1
|
||||
red: (191, 97, 106),
|
||||
green: (163, 190, 140),
|
||||
yellow: (235, 203, 139),
|
||||
blue: (129, 161, 193), // frost2
|
||||
purple: (180, 142, 173),
|
||||
cyan: (143, 188, 187), // frost0
|
||||
orange: (208, 135, 112),
|
||||
tempo_color: (180, 142, 173),
|
||||
bank_color: (129, 161, 193),
|
||||
pattern_color: (143, 188, 187),
|
||||
title_accent: (136, 192, 208),
|
||||
title_author: (129, 161, 193),
|
||||
secondary: (208, 135, 112),
|
||||
link_bright: [
|
||||
(136, 192, 208), (180, 142, 173), (208, 135, 112),
|
||||
(143, 188, 187), (163, 190, 140),
|
||||
],
|
||||
link_dim: [
|
||||
(55, 75, 85), (70, 60, 70), (75, 55, 50),
|
||||
(55, 75, 75), (60, 75, 55),
|
||||
],
|
||||
sparkle: [
|
||||
(136, 192, 208), (208, 135, 112), (163, 190, 140),
|
||||
(180, 142, 173), (235, 203, 139),
|
||||
],
|
||||
meter: [(140, 180, 130), (220, 190, 120), (180, 90, 100)],
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: polar_night0,
|
||||
bg_rgb: (46, 52, 64),
|
||||
text_primary: snow_storm2,
|
||||
text_muted: snow_storm0,
|
||||
text_dim: polar_night3,
|
||||
border: polar_night2,
|
||||
header: frost1,
|
||||
unfocused: polar_night3,
|
||||
accent: frost1,
|
||||
surface: polar_night1,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 65, 60),
|
||||
playing_fg: aurora_green,
|
||||
stopped_bg: Color::Rgb(65, 50, 55),
|
||||
stopped_fg: aurora_red,
|
||||
fill_on: aurora_green,
|
||||
fill_off: polar_night3,
|
||||
fill_bg: polar_night1,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: frost1,
|
||||
cursor_fg: polar_night0,
|
||||
selected_bg: Color::Rgb(70, 85, 105),
|
||||
selected_fg: frost1,
|
||||
in_range_bg: Color::Rgb(60, 70, 90),
|
||||
in_range_fg: snow_storm0,
|
||||
cursor: frost1,
|
||||
selected: Color::Rgb(70, 85, 105),
|
||||
in_range: Color::Rgb(60, 70, 90),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(80, 70, 65),
|
||||
playing_active_fg: aurora_orange,
|
||||
playing_inactive_bg: Color::Rgb(75, 70, 55),
|
||||
playing_inactive_fg: aurora_yellow,
|
||||
active_bg: Color::Rgb(50, 65, 65),
|
||||
active_fg: frost0,
|
||||
content_bg: Color::Rgb(57, 72, 72),
|
||||
inactive_bg: polar_night1,
|
||||
inactive_fg: snow_storm0,
|
||||
active_selected_bg: Color::Rgb(75, 75, 95),
|
||||
active_in_range_bg: Color::Rgb(60, 70, 85),
|
||||
link_bright: [
|
||||
(136, 192, 208),
|
||||
(180, 142, 173),
|
||||
(208, 135, 112),
|
||||
(143, 188, 187),
|
||||
(163, 190, 140),
|
||||
],
|
||||
link_dim: [
|
||||
(55, 75, 85),
|
||||
(70, 60, 70),
|
||||
(75, 55, 50),
|
||||
(55, 75, 75),
|
||||
(60, 75, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 55, 70),
|
||||
tempo_fg: aurora_purple,
|
||||
bank_bg: Color::Rgb(45, 60, 70),
|
||||
bank_fg: frost2,
|
||||
pattern_bg: Color::Rgb(50, 65, 65),
|
||||
pattern_fg: frost0,
|
||||
stats_bg: polar_night1,
|
||||
stats_fg: snow_storm0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: frost1,
|
||||
border_accent: aurora_purple,
|
||||
border_warn: aurora_orange,
|
||||
border_dim: polar_night3,
|
||||
confirm: aurora_orange,
|
||||
rename: aurora_purple,
|
||||
input: frost2,
|
||||
editor: frost1,
|
||||
preview: polar_night3,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(65, 50, 55),
|
||||
error_fg: aurora_red,
|
||||
success_bg: Color::Rgb(50, 65, 55),
|
||||
success_fg: aurora_green,
|
||||
info_bg: polar_night1,
|
||||
info_fg: snow_storm2,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 65, 55),
|
||||
playing_fg: aurora_green,
|
||||
staged_play_bg: Color::Rgb(65, 55, 70),
|
||||
staged_play_fg: aurora_purple,
|
||||
staged_stop_bg: Color::Rgb(70, 55, 60),
|
||||
staged_stop_fg: aurora_red,
|
||||
edit_bg: Color::Rgb(50, 65, 65),
|
||||
edit_fg: frost0,
|
||||
hover_bg: polar_night2,
|
||||
hover_fg: snow_storm2,
|
||||
muted_bg: Color::Rgb(55, 60, 70),
|
||||
muted_fg: polar_night3,
|
||||
soloed_bg: Color::Rgb(70, 65, 50),
|
||||
soloed_fg: aurora_yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: aurora_red,
|
||||
connected: aurora_green,
|
||||
listening: aurora_yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: polar_night1,
|
||||
executed_bg: Color::Rgb(55, 55, 70),
|
||||
selected_bg: Color::Rgb(80, 70, 55),
|
||||
emit: (snow_storm2, Color::Rgb(75, 55, 60)),
|
||||
number: (aurora_orange, Color::Rgb(65, 55, 50)),
|
||||
string: (aurora_green, Color::Rgb(50, 60, 50)),
|
||||
comment: (polar_night3, polar_night0),
|
||||
keyword: (aurora_purple, Color::Rgb(60, 50, 65)),
|
||||
stack_op: (frost2, Color::Rgb(45, 55, 70)),
|
||||
operator: (aurora_yellow, Color::Rgb(65, 60, 45)),
|
||||
sound: (frost0, Color::Rgb(45, 60, 60)),
|
||||
param: (frost1, Color::Rgb(50, 60, 70)),
|
||||
context: (aurora_orange, Color::Rgb(65, 55, 50)),
|
||||
note: (aurora_green, Color::Rgb(50, 60, 50)),
|
||||
interval: (Color::Rgb(170, 200, 150), Color::Rgb(50, 60, 45)),
|
||||
variable: (aurora_purple, Color::Rgb(60, 50, 60)),
|
||||
vary: (aurora_yellow, Color::Rgb(65, 60, 45)),
|
||||
generator: (frost0, Color::Rgb(45, 60, 55)),
|
||||
user_defined: (aurora_orange, Color::Rgb(60, 50, 45)),
|
||||
default: (snow_storm0, polar_night1),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: polar_night1,
|
||||
row_odd: polar_night0,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: aurora_orange,
|
||||
value: snow_storm0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: aurora_orange,
|
||||
text: polar_night3,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: snow_storm2,
|
||||
fg: polar_night0,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(65, 75, 95),
|
||||
selected_fg: snow_storm2,
|
||||
unselected_bg: polar_night1,
|
||||
unselected_fg: polar_night3,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: snow_storm2,
|
||||
cursor_fg: polar_night0,
|
||||
selection_bg: Color::Rgb(60, 75, 100),
|
||||
completion_bg: polar_night1,
|
||||
completion_fg: snow_storm2,
|
||||
completion_selected: aurora_orange,
|
||||
completion_example: frost0,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: frost2,
|
||||
project_file: aurora_purple,
|
||||
selected: aurora_orange,
|
||||
file: snow_storm2,
|
||||
focused_border: aurora_orange,
|
||||
unfocused_border: polar_night3,
|
||||
root: snow_storm2,
|
||||
file_icon: polar_night3,
|
||||
folder_icon: frost2,
|
||||
empty_text: polar_night3,
|
||||
},
|
||||
input: InputColors {
|
||||
text: frost2,
|
||||
cursor: snow_storm2,
|
||||
hint: polar_night3,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: aurora_orange,
|
||||
inactive: polar_night3,
|
||||
match_bg: aurora_yellow,
|
||||
match_fg: polar_night0,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: frost2,
|
||||
h2: aurora_orange,
|
||||
h3: aurora_purple,
|
||||
code: aurora_green,
|
||||
code_border: Color::Rgb(75, 85, 100),
|
||||
link: frost0,
|
||||
link_url: Color::Rgb(100, 110, 125),
|
||||
quote: polar_night3,
|
||||
text: snow_storm2,
|
||||
list: snow_storm2,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: frost1,
|
||||
header_focused: aurora_yellow,
|
||||
divider: Color::Rgb(70, 80, 95),
|
||||
scroll_indicator: Color::Rgb(85, 95, 110),
|
||||
label: Color::Rgb(130, 140, 155),
|
||||
label_focused: Color::Rgb(160, 170, 185),
|
||||
label_dim: Color::Rgb(100, 110, 125),
|
||||
value: Color::Rgb(190, 200, 215),
|
||||
focused: aurora_yellow,
|
||||
normal: snow_storm2,
|
||||
dim: Color::Rgb(85, 95, 110),
|
||||
path: Color::Rgb(130, 140, 155),
|
||||
border_magenta: aurora_purple,
|
||||
border_green: aurora_green,
|
||||
border_cyan: frost2,
|
||||
separator: Color::Rgb(70, 80, 95),
|
||||
hint_active: Color::Rgb(200, 180, 100),
|
||||
hint_inactive: Color::Rgb(70, 80, 95),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: aurora_green,
|
||||
word_bg: Color::Rgb(50, 60, 75),
|
||||
alias: polar_night3,
|
||||
stack_sig: aurora_purple,
|
||||
description: snow_storm2,
|
||||
example: Color::Rgb(130, 140, 155),
|
||||
category_focused: aurora_yellow,
|
||||
category_selected: frost2,
|
||||
category_normal: snow_storm2,
|
||||
category_dimmed: Color::Rgb(85, 95, 110),
|
||||
border_focused: aurora_yellow,
|
||||
border_normal: Color::Rgb(70, 80, 95),
|
||||
header_desc: Color::Rgb(150, 160, 175),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: frost1,
|
||||
author: frost2,
|
||||
link: frost0,
|
||||
license: aurora_orange,
|
||||
prompt: Color::Rgb(150, 160, 175),
|
||||
subtitle: snow_storm2,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: aurora_green,
|
||||
mid: aurora_yellow,
|
||||
high: aurora_red,
|
||||
low_rgb: (140, 180, 130),
|
||||
mid_rgb: (220, 190, 120),
|
||||
high_rgb: (180, 90, 100),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(136, 192, 208),
|
||||
(208, 135, 112),
|
||||
(163, 190, 140),
|
||||
(180, 142, 173),
|
||||
(235, 203, 139),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: aurora_orange,
|
||||
button_selected_bg: aurora_orange,
|
||||
button_selected_fg: polar_night0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
//! Palette definition and color mixing utilities.
|
||||
|
||||
use ratatui::style::Color;
|
||||
|
||||
/// RGB color triple.
|
||||
pub type Rgb = (u8, u8, u8);
|
||||
|
||||
/// Base color palette that themes are derived from.
|
||||
pub struct Palette {
|
||||
// Core
|
||||
pub bg: Rgb,
|
||||
pub surface: Rgb,
|
||||
pub surface2: Rgb,
|
||||
pub fg: Rgb,
|
||||
pub fg_dim: Rgb,
|
||||
pub fg_muted: Rgb,
|
||||
// Semantic accents
|
||||
pub accent: Rgb,
|
||||
pub red: Rgb,
|
||||
pub green: Rgb,
|
||||
pub yellow: Rgb,
|
||||
pub blue: Rgb,
|
||||
pub purple: Rgb,
|
||||
pub cyan: Rgb,
|
||||
pub orange: Rgb,
|
||||
// Role assignments
|
||||
pub tempo_color: Rgb,
|
||||
pub bank_color: Rgb,
|
||||
pub pattern_color: Rgb,
|
||||
pub title_accent: Rgb,
|
||||
pub title_author: Rgb,
|
||||
pub secondary: Rgb,
|
||||
// Arrays
|
||||
pub link_bright: [Rgb; 5],
|
||||
pub link_dim: [Rgb; 5],
|
||||
pub sparkle: [Rgb; 5],
|
||||
pub meter: [Rgb; 3],
|
||||
}
|
||||
|
||||
/// Convert an RGB triple to a ratatui [`Color`].
|
||||
pub fn rgb(c: Rgb) -> Color {
|
||||
Color::Rgb(c.0, c.1, c.2)
|
||||
}
|
||||
|
||||
/// Blend `bg` toward `accent` by `amount` (0.0–1.0).
|
||||
pub fn tint(bg: Rgb, accent: Rgb, amount: f32) -> Rgb {
|
||||
let mix = |b: u8, a: u8| -> u8 {
|
||||
let v = b as f32 + (a as f32 - b as f32) * amount;
|
||||
v.clamp(0.0, 255.0) as u8
|
||||
};
|
||||
(mix(bg.0, accent.0), mix(bg.1, accent.1), mix(bg.2, accent.2))
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two colors.
|
||||
pub fn mid(a: Rgb, b: Rgb, t: f32) -> Rgb {
|
||||
tint(a, b, t)
|
||||
}
|
||||
|
||||
/// Darken a color by reducing brightness.
|
||||
pub fn darken(c: Rgb, amount: f32) -> Rgb {
|
||||
let d = |v: u8| -> u8 { (v as f32 * (1.0 - amount)).clamp(0.0, 255.0) as u8 };
|
||||
(d(c.0), d(c.1), d(c.2))
|
||||
}
|
||||
@@ -1,41 +1,282 @@
|
||||
//! Pitch Black palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(0, 0, 0);
|
||||
let surface = Color::Rgb(10, 10, 10);
|
||||
let surface2 = Color::Rgb(21, 21, 21);
|
||||
let border = Color::Rgb(40, 40, 40);
|
||||
let fg = Color::Rgb(230, 230, 230);
|
||||
let fg_dim = Color::Rgb(160, 160, 160);
|
||||
let fg_muted = Color::Rgb(100, 100, 100);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (0, 0, 0),
|
||||
surface: (10, 10, 10),
|
||||
surface2: (21, 21, 21),
|
||||
fg: (230, 230, 230),
|
||||
fg_dim: (160, 160, 160),
|
||||
fg_muted: (100, 100, 100),
|
||||
accent: (80, 230, 230), // cyan
|
||||
red: (255, 80, 80),
|
||||
green: (80, 255, 120),
|
||||
yellow: (255, 230, 80),
|
||||
blue: (80, 180, 255),
|
||||
purple: (200, 120, 255),
|
||||
cyan: (80, 230, 230),
|
||||
orange: (255, 160, 60),
|
||||
tempo_color: (200, 120, 255),
|
||||
bank_color: (80, 180, 255),
|
||||
pattern_color: (80, 230, 230),
|
||||
title_accent: (80, 230, 230),
|
||||
title_author: (80, 180, 255),
|
||||
secondary: (255, 160, 60),
|
||||
link_bright: [
|
||||
(80, 230, 230), (200, 120, 255), (255, 160, 60),
|
||||
(80, 180, 255), (80, 255, 120),
|
||||
],
|
||||
link_dim: [
|
||||
(25, 60, 60), (50, 35, 65), (60, 45, 20),
|
||||
(25, 50, 70), (25, 65, 35),
|
||||
],
|
||||
sparkle: [
|
||||
(80, 230, 230), (255, 160, 60), (80, 255, 120),
|
||||
(200, 120, 255), (80, 180, 255),
|
||||
],
|
||||
meter: [(70, 240, 110), (245, 220, 75), (245, 75, 75)],
|
||||
let red = Color::Rgb(255, 80, 80);
|
||||
let green = Color::Rgb(80, 255, 120);
|
||||
let yellow = Color::Rgb(255, 230, 80);
|
||||
let blue = Color::Rgb(80, 180, 255);
|
||||
let purple = Color::Rgb(200, 120, 255);
|
||||
let cyan = Color::Rgb(80, 230, 230);
|
||||
let orange = Color::Rgb(255, 160, 60);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (0, 0, 0),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: blue,
|
||||
unfocused: fg_muted,
|
||||
accent: cyan,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(15, 35, 20),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(40, 15, 20),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: fg_muted,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: cyan,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(40, 50, 60),
|
||||
selected_fg: cyan,
|
||||
in_range_bg: Color::Rgb(25, 35, 45),
|
||||
in_range_fg: fg,
|
||||
cursor: cyan,
|
||||
selected: Color::Rgb(40, 50, 60),
|
||||
in_range: Color::Rgb(25, 35, 45),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(50, 35, 20),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(45, 40, 15),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(15, 40, 40),
|
||||
active_fg: cyan,
|
||||
content_bg: Color::Rgb(22, 47, 47),
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(45, 40, 55),
|
||||
active_in_range_bg: Color::Rgb(30, 35, 45),
|
||||
link_bright: [
|
||||
(80, 230, 230),
|
||||
(200, 120, 255),
|
||||
(255, 160, 60),
|
||||
(80, 180, 255),
|
||||
(80, 255, 120),
|
||||
],
|
||||
link_dim: [
|
||||
(25, 60, 60),
|
||||
(50, 35, 65),
|
||||
(60, 45, 20),
|
||||
(25, 50, 70),
|
||||
(25, 65, 35),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 35, 55),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(20, 45, 60),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(20, 55, 50),
|
||||
pattern_fg: cyan,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: cyan,
|
||||
border_accent: purple,
|
||||
border_warn: orange,
|
||||
border_dim: fg_muted,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: cyan,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(50, 15, 20),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(15, 45, 25),
|
||||
success_fg: green,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(15, 45, 25),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(45, 30, 55),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(55, 25, 30),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(15, 45, 45),
|
||||
edit_fg: cyan,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(15, 15, 15),
|
||||
muted_fg: fg_muted,
|
||||
soloed_bg: Color::Rgb(45, 40, 15),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(25, 25, 35),
|
||||
selected_bg: Color::Rgb(55, 45, 25),
|
||||
emit: (fg, Color::Rgb(50, 30, 35)),
|
||||
number: (orange, Color::Rgb(50, 35, 20)),
|
||||
string: (green, Color::Rgb(20, 45, 25)),
|
||||
comment: (fg_muted, bg),
|
||||
keyword: (purple, Color::Rgb(40, 25, 50)),
|
||||
stack_op: (blue, Color::Rgb(20, 40, 55)),
|
||||
operator: (yellow, Color::Rgb(50, 45, 20)),
|
||||
sound: (cyan, Color::Rgb(20, 45, 45)),
|
||||
param: (purple, Color::Rgb(40, 25, 50)),
|
||||
context: (orange, Color::Rgb(50, 35, 20)),
|
||||
note: (green, Color::Rgb(20, 45, 25)),
|
||||
interval: (Color::Rgb(130, 255, 150), Color::Rgb(25, 55, 35)),
|
||||
variable: (purple, Color::Rgb(40, 25, 50)),
|
||||
vary: (yellow, Color::Rgb(50, 45, 20)),
|
||||
generator: (cyan, Color::Rgb(20, 45, 40)),
|
||||
user_defined: (orange, Color::Rgb(40, 25, 10)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(40, 45, 55),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(40, 50, 65),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: blue,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: fg_muted,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(50, 50, 50),
|
||||
link: cyan,
|
||||
link_url: Color::Rgb(90, 90, 90),
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(45, 45, 45),
|
||||
scroll_indicator: Color::Rgb(60, 60, 60),
|
||||
label: Color::Rgb(130, 130, 130),
|
||||
label_focused: Color::Rgb(170, 170, 170),
|
||||
label_dim: Color::Rgb(90, 90, 90),
|
||||
value: Color::Rgb(200, 200, 200),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(60, 60, 60),
|
||||
path: Color::Rgb(130, 130, 130),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(45, 45, 45),
|
||||
hint_active: Color::Rgb(220, 200, 80),
|
||||
hint_inactive: Color::Rgb(45, 45, 45),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(20, 30, 35),
|
||||
alias: fg_muted,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 130, 130),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(60, 60, 60),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(45, 45, 45),
|
||||
header_desc: Color::Rgb(150, 150, 150),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: cyan,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(150, 150, 150),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (70, 240, 110),
|
||||
mid_rgb: (245, 220, 75),
|
||||
high_rgb: (245, 75, 75),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(80, 230, 230),
|
||||
(255, 160, 60),
|
||||
(80, 255, 120),
|
||||
(200, 120, 255),
|
||||
(80, 180, 255),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,282 @@
|
||||
//! Rose Pine palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(25, 23, 36);
|
||||
let bg_light = Color::Rgb(33, 32, 46);
|
||||
let bg_lighter = Color::Rgb(42, 39, 63);
|
||||
let fg = Color::Rgb(224, 222, 244);
|
||||
let fg_dim = Color::Rgb(144, 140, 170);
|
||||
let muted = Color::Rgb(110, 106, 134);
|
||||
let rose = Color::Rgb(235, 188, 186);
|
||||
let gold = Color::Rgb(246, 193, 119);
|
||||
let foam = Color::Rgb(156, 207, 216);
|
||||
let iris = Color::Rgb(196, 167, 231);
|
||||
let pine = Color::Rgb(49, 116, 143);
|
||||
let subtle = Color::Rgb(235, 188, 186);
|
||||
let love = Color::Rgb(235, 111, 146);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (25, 23, 36),
|
||||
surface: (33, 32, 46),
|
||||
surface2: (42, 39, 63),
|
||||
fg: (224, 222, 244),
|
||||
fg_dim: (144, 140, 170),
|
||||
fg_muted: (110, 106, 134),
|
||||
accent: (235, 188, 186), // rose
|
||||
red: (235, 111, 146), // love
|
||||
green: (156, 207, 216), // foam
|
||||
yellow: (246, 193, 119), // gold
|
||||
blue: (49, 116, 143), // pine
|
||||
purple: (196, 167, 231), // iris
|
||||
cyan: (156, 207, 216), // foam
|
||||
orange: (246, 193, 119), // gold
|
||||
tempo_color: (196, 167, 231),
|
||||
bank_color: (156, 207, 216),
|
||||
pattern_color: (49, 116, 143),
|
||||
title_accent: (235, 188, 186),
|
||||
title_author: (156, 207, 216),
|
||||
secondary: (235, 111, 146),
|
||||
link_bright: [
|
||||
(235, 111, 146), (196, 167, 231), (246, 193, 119),
|
||||
(156, 207, 216), (49, 116, 143),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 55), (60, 50, 75), (75, 60, 45),
|
||||
(50, 65, 70), (30, 50, 55),
|
||||
],
|
||||
sparkle: [
|
||||
(156, 207, 216), (246, 193, 119), (49, 116, 143),
|
||||
(235, 111, 146), (196, 167, 231),
|
||||
],
|
||||
meter: [(156, 207, 216), (246, 193, 119), (235, 111, 146)],
|
||||
let darker_bg = Color::Rgb(21, 19, 30);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (25, 23, 36),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: muted,
|
||||
border: bg_lighter,
|
||||
header: foam,
|
||||
unfocused: muted,
|
||||
accent: rose,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(35, 50, 55),
|
||||
playing_fg: foam,
|
||||
stopped_bg: Color::Rgb(55, 40, 50),
|
||||
stopped_fg: love,
|
||||
fill_on: foam,
|
||||
fill_off: muted,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: rose,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: rose,
|
||||
in_range_bg: Color::Rgb(50, 45, 60),
|
||||
in_range_fg: fg,
|
||||
cursor: rose,
|
||||
selected: Color::Rgb(60, 50, 70),
|
||||
in_range: Color::Rgb(50, 45, 60),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(65, 55, 50),
|
||||
playing_active_fg: gold,
|
||||
playing_inactive_bg: Color::Rgb(55, 55, 55),
|
||||
playing_inactive_fg: subtle,
|
||||
active_bg: Color::Rgb(35, 50, 60),
|
||||
active_fg: foam,
|
||||
content_bg: Color::Rgb(42, 57, 67),
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(60, 50, 70),
|
||||
active_in_range_bg: Color::Rgb(50, 45, 60),
|
||||
link_bright: [
|
||||
(235, 111, 146),
|
||||
(196, 167, 231),
|
||||
(246, 193, 119),
|
||||
(156, 207, 216),
|
||||
(49, 116, 143),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 55),
|
||||
(60, 50, 75),
|
||||
(75, 60, 45),
|
||||
(50, 65, 70),
|
||||
(30, 50, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(60, 45, 60),
|
||||
tempo_fg: iris,
|
||||
bank_bg: Color::Rgb(35, 50, 60),
|
||||
bank_fg: foam,
|
||||
pattern_bg: Color::Rgb(35, 55, 60),
|
||||
pattern_fg: pine,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: foam,
|
||||
border_accent: rose,
|
||||
border_warn: gold,
|
||||
border_dim: muted,
|
||||
confirm: gold,
|
||||
rename: iris,
|
||||
input: foam,
|
||||
editor: foam,
|
||||
preview: muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 40, 50),
|
||||
error_fg: love,
|
||||
success_bg: Color::Rgb(35, 55, 55),
|
||||
success_fg: foam,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(35, 55, 55),
|
||||
playing_fg: foam,
|
||||
staged_play_bg: Color::Rgb(55, 50, 70),
|
||||
staged_play_fg: iris,
|
||||
staged_stop_bg: Color::Rgb(60, 45, 55),
|
||||
staged_stop_fg: love,
|
||||
edit_bg: Color::Rgb(35, 50, 60),
|
||||
edit_fg: foam,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(32, 30, 42),
|
||||
muted_fg: muted,
|
||||
soloed_bg: Color::Rgb(60, 50, 40),
|
||||
soloed_fg: gold,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: love,
|
||||
connected: foam,
|
||||
listening: gold,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(40, 40, 55),
|
||||
selected_bg: Color::Rgb(65, 55, 50),
|
||||
emit: (fg, Color::Rgb(60, 45, 60)),
|
||||
number: (iris, Color::Rgb(55, 50, 70)),
|
||||
string: (gold, Color::Rgb(65, 55, 45)),
|
||||
comment: (muted, darker_bg),
|
||||
keyword: (rose, Color::Rgb(60, 45, 55)),
|
||||
stack_op: (foam, Color::Rgb(40, 55, 60)),
|
||||
operator: (love, Color::Rgb(60, 45, 55)),
|
||||
sound: (foam, Color::Rgb(40, 55, 60)),
|
||||
param: (gold, Color::Rgb(65, 55, 45)),
|
||||
context: (gold, Color::Rgb(65, 55, 45)),
|
||||
note: (pine, Color::Rgb(35, 50, 55)),
|
||||
interval: (Color::Rgb(100, 160, 180), Color::Rgb(35, 55, 60)),
|
||||
variable: (pine, Color::Rgb(35, 50, 55)),
|
||||
vary: (subtle, Color::Rgb(60, 55, 55)),
|
||||
generator: (foam, Color::Rgb(40, 55, 60)),
|
||||
user_defined: (love, Color::Rgb(55, 35, 45)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: gold,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: gold,
|
||||
text: muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(55, 50, 70),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: gold,
|
||||
completion_example: foam,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: foam,
|
||||
project_file: iris,
|
||||
selected: gold,
|
||||
file: fg,
|
||||
focused_border: gold,
|
||||
unfocused_border: muted,
|
||||
root: fg,
|
||||
file_icon: muted,
|
||||
folder_icon: foam,
|
||||
empty_text: muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: foam,
|
||||
cursor: fg,
|
||||
hint: muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: gold,
|
||||
inactive: muted,
|
||||
match_bg: gold,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: foam,
|
||||
h2: gold,
|
||||
h3: iris,
|
||||
code: pine,
|
||||
code_border: Color::Rgb(60, 55, 75),
|
||||
link: rose,
|
||||
link_url: Color::Rgb(100, 95, 120),
|
||||
quote: muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: foam,
|
||||
header_focused: gold,
|
||||
divider: Color::Rgb(55, 52, 70),
|
||||
scroll_indicator: Color::Rgb(70, 65, 90),
|
||||
label: Color::Rgb(130, 125, 155),
|
||||
label_focused: Color::Rgb(160, 155, 185),
|
||||
label_dim: Color::Rgb(100, 95, 125),
|
||||
value: Color::Rgb(200, 195, 220),
|
||||
focused: gold,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(70, 65, 90),
|
||||
path: Color::Rgb(130, 125, 155),
|
||||
border_magenta: iris,
|
||||
border_green: foam,
|
||||
border_cyan: pine,
|
||||
separator: Color::Rgb(55, 52, 70),
|
||||
hint_active: Color::Rgb(230, 180, 110),
|
||||
hint_inactive: Color::Rgb(55, 52, 70),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: pine,
|
||||
word_bg: Color::Rgb(40, 50, 55),
|
||||
alias: muted,
|
||||
stack_sig: iris,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 125, 155),
|
||||
category_focused: gold,
|
||||
category_selected: foam,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(70, 65, 90),
|
||||
border_focused: gold,
|
||||
border_normal: Color::Rgb(55, 52, 70),
|
||||
header_desc: Color::Rgb(150, 145, 175),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: rose,
|
||||
author: foam,
|
||||
link: pine,
|
||||
license: gold,
|
||||
prompt: Color::Rgb(150, 145, 175),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: foam,
|
||||
mid: gold,
|
||||
high: love,
|
||||
low_rgb: (156, 207, 216),
|
||||
mid_rgb: (246, 193, 119),
|
||||
high_rgb: (235, 111, 146),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(156, 207, 216),
|
||||
(246, 193, 119),
|
||||
(49, 116, 143),
|
||||
(235, 111, 146),
|
||||
(196, 167, 231),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: gold,
|
||||
button_selected_bg: gold,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,282 @@
|
||||
//! Tokyo Night palette.
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
use super::palette::Palette;
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(26, 27, 38);
|
||||
let bg_light = Color::Rgb(36, 40, 59);
|
||||
let bg_lighter = Color::Rgb(52, 59, 88);
|
||||
let fg = Color::Rgb(169, 177, 214);
|
||||
let fg_dim = Color::Rgb(130, 140, 180);
|
||||
let comment = Color::Rgb(86, 95, 137);
|
||||
let blue = Color::Rgb(122, 162, 247);
|
||||
let purple = Color::Rgb(187, 154, 247);
|
||||
let green = Color::Rgb(158, 206, 106);
|
||||
let red = Color::Rgb(247, 118, 142);
|
||||
let orange = Color::Rgb(224, 175, 104);
|
||||
let cyan = Color::Rgb(125, 207, 255);
|
||||
let yellow = Color::Rgb(224, 175, 104);
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (26, 27, 38),
|
||||
surface: (36, 40, 59),
|
||||
surface2: (52, 59, 88),
|
||||
fg: (169, 177, 214),
|
||||
fg_dim: (130, 140, 180),
|
||||
fg_muted: (86, 95, 137),
|
||||
accent: (187, 154, 247), // purple
|
||||
red: (247, 118, 142),
|
||||
green: (158, 206, 106),
|
||||
yellow: (224, 175, 104),
|
||||
blue: (122, 162, 247),
|
||||
purple: (187, 154, 247),
|
||||
cyan: (125, 207, 255),
|
||||
orange: (224, 175, 104),
|
||||
tempo_color: (187, 154, 247),
|
||||
bank_color: (122, 162, 247),
|
||||
pattern_color: (158, 206, 106),
|
||||
title_accent: (187, 154, 247),
|
||||
title_author: (122, 162, 247),
|
||||
secondary: (224, 175, 104),
|
||||
link_bright: [
|
||||
(247, 118, 142), (187, 154, 247), (224, 175, 104),
|
||||
(125, 207, 255), (158, 206, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(80, 45, 55), (65, 55, 85), (75, 60, 40),
|
||||
(45, 70, 85), (55, 70, 45),
|
||||
],
|
||||
sparkle: [
|
||||
(125, 207, 255), (224, 175, 104), (158, 206, 106),
|
||||
(247, 118, 142), (187, 154, 247),
|
||||
],
|
||||
meter: [(158, 206, 106), (224, 175, 104), (247, 118, 142)],
|
||||
let darker_bg = Color::Rgb(22, 23, 32);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (26, 27, 38),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: blue,
|
||||
unfocused: comment,
|
||||
accent: purple,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(45, 60, 50),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(60, 40, 50),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: purple,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(70, 60, 90),
|
||||
selected_fg: purple,
|
||||
in_range_bg: Color::Rgb(55, 55, 75),
|
||||
in_range_fg: fg,
|
||||
cursor: purple,
|
||||
selected: Color::Rgb(70, 60, 90),
|
||||
in_range: Color::Rgb(55, 55, 75),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(70, 60, 45),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(60, 60, 50),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(45, 60, 75),
|
||||
active_fg: blue,
|
||||
content_bg: Color::Rgb(52, 67, 82),
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(70, 55, 85),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 75),
|
||||
link_bright: [
|
||||
(247, 118, 142),
|
||||
(187, 154, 247),
|
||||
(224, 175, 104),
|
||||
(125, 207, 255),
|
||||
(158, 206, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(80, 45, 55),
|
||||
(65, 55, 85),
|
||||
(75, 60, 40),
|
||||
(45, 70, 85),
|
||||
(55, 70, 45),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 50, 70),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(45, 55, 75),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(50, 65, 50),
|
||||
pattern_fg: green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: blue,
|
||||
border_accent: purple,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(65, 40, 50),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(45, 60, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(45, 60, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(60, 50, 75),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(70, 45, 55),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(45, 55, 70),
|
||||
edit_fg: blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
muted_bg: Color::Rgb(35, 38, 50),
|
||||
muted_fg: comment,
|
||||
soloed_bg: Color::Rgb(60, 55, 40),
|
||||
soloed_fg: yellow,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(45, 45, 60),
|
||||
selected_bg: Color::Rgb(70, 60, 50),
|
||||
emit: (fg, Color::Rgb(70, 50, 65)),
|
||||
number: (purple, Color::Rgb(55, 50, 70)),
|
||||
string: (green, Color::Rgb(50, 60, 50)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (purple, Color::Rgb(60, 50, 70)),
|
||||
stack_op: (cyan, Color::Rgb(45, 60, 75)),
|
||||
operator: (red, Color::Rgb(65, 45, 55)),
|
||||
sound: (blue, Color::Rgb(45, 55, 70)),
|
||||
param: (orange, Color::Rgb(70, 55, 45)),
|
||||
context: (orange, Color::Rgb(70, 55, 45)),
|
||||
note: (green, Color::Rgb(50, 60, 45)),
|
||||
interval: (Color::Rgb(180, 220, 130), Color::Rgb(50, 65, 45)),
|
||||
variable: (green, Color::Rgb(50, 60, 45)),
|
||||
vary: (yellow, Color::Rgb(70, 60, 45)),
|
||||
generator: (cyan, Color::Rgb(45, 60, 75)),
|
||||
user_defined: (orange, Color::Rgb(60, 50, 35)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(65, 55, 80),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(60, 60, 80),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(70, 75, 95),
|
||||
link: red,
|
||||
link_url: Color::Rgb(110, 120, 160),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(65, 70, 90),
|
||||
scroll_indicator: Color::Rgb(80, 85, 110),
|
||||
label: Color::Rgb(130, 140, 175),
|
||||
label_focused: Color::Rgb(160, 170, 200),
|
||||
label_dim: Color::Rgb(100, 110, 145),
|
||||
value: Color::Rgb(190, 195, 220),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(80, 85, 110),
|
||||
path: Color::Rgb(130, 140, 175),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(65, 70, 90),
|
||||
hint_active: Color::Rgb(210, 180, 100),
|
||||
hint_inactive: Color::Rgb(65, 70, 90),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(45, 55, 60),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 140, 175),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(80, 85, 110),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(65, 70, 90),
|
||||
header_desc: Color::Rgb(150, 160, 190),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: purple,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(150, 160, 190),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (158, 206, 106),
|
||||
mid_rgb: (224, 175, 104),
|
||||
high_rgb: (247, 118, 142),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(125, 207, 255),
|
||||
(224, 175, 104),
|
||||
(158, 206, 106),
|
||||
(247, 118, 142),
|
||||
(187, 154, 247),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
//! Hue rotation for palette-wide color transforms.
|
||||
|
||||
use super::palette::{Palette, Rgb};
|
||||
use super::build::build;
|
||||
use super::ThemeColors;
|
||||
use ratatui::style::Color;
|
||||
use super::*;
|
||||
|
||||
fn rgb_to_hsv(r: u8, g: u8, b: u8) -> (f32, f32, f32) {
|
||||
let r = r as f32 / 255.0;
|
||||
let g = g as f32 / 255.0;
|
||||
let b = b as f32 / 255.0;
|
||||
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let delta = max - min;
|
||||
|
||||
let h = if delta == 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
@@ -20,15 +19,19 @@ fn rgb_to_hsv(r: u8, g: u8, b: u8) -> (f32, f32, f32) {
|
||||
} else {
|
||||
60.0 * (((r - g) / delta) + 4.0)
|
||||
};
|
||||
|
||||
let h = if h < 0.0 { h + 360.0 } else { h };
|
||||
let s = if max == 0.0 { 0.0 } else { delta / max };
|
||||
(h, s, max)
|
||||
let v = max;
|
||||
|
||||
(h, s, v)
|
||||
}
|
||||
|
||||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||||
let c = v * s;
|
||||
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
|
||||
let m = v - c;
|
||||
|
||||
let (r, g, b) = if h < 60.0 {
|
||||
(c, x, 0.0)
|
||||
} else if h < 120.0 {
|
||||
@@ -42,6 +45,7 @@ fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||||
} else {
|
||||
(c, 0.0, x)
|
||||
};
|
||||
|
||||
(
|
||||
((r + m) * 255.0) as u8,
|
||||
((g + m) * 255.0) as u8,
|
||||
@@ -49,51 +53,298 @@ fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||||
)
|
||||
}
|
||||
|
||||
fn rotate(c: Rgb, degrees: f32) -> Rgb {
|
||||
let (h, s, v) = rgb_to_hsv(c.0, c.1, c.2);
|
||||
fn rotate_hue_rgb(r: u8, g: u8, b: u8, degrees: f32) -> (u8, u8, u8) {
|
||||
let (h, s, v) = rgb_to_hsv(r, g, b);
|
||||
let new_h = (h + degrees) % 360.0;
|
||||
let new_h = if new_h < 0.0 { new_h + 360.0 } else { new_h };
|
||||
hsv_to_rgb(new_h, s, v)
|
||||
}
|
||||
|
||||
fn rotate5(arr: [Rgb; 5], d: f32) -> [Rgb; 5] {
|
||||
[rotate(arr[0], d), rotate(arr[1], d), rotate(arr[2], d), rotate(arr[3], d), rotate(arr[4], d)]
|
||||
}
|
||||
|
||||
fn rotate3(arr: [Rgb; 3], d: f32) -> [Rgb; 3] {
|
||||
[rotate(arr[0], d), rotate(arr[1], d), rotate(arr[2], d)]
|
||||
}
|
||||
|
||||
/// Build a [`ThemeColors`] with all palette hues rotated by `degrees`.
|
||||
pub fn rotate_palette(palette: &Palette, degrees: f32) -> ThemeColors {
|
||||
if degrees == 0.0 {
|
||||
return build(palette);
|
||||
fn rotate_color(color: Color, degrees: f32) -> Color {
|
||||
match color {
|
||||
Color::Rgb(r, g, b) => {
|
||||
let (nr, ng, nb) = rotate_hue_rgb(r, g, b, degrees);
|
||||
Color::Rgb(nr, ng, nb)
|
||||
}
|
||||
_ => color,
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_tuple(tuple: (u8, u8, u8), degrees: f32) -> (u8, u8, u8) {
|
||||
rotate_hue_rgb(tuple.0, tuple.1, tuple.2, degrees)
|
||||
}
|
||||
|
||||
fn rotate_color_pair(pair: (Color, Color), degrees: f32) -> (Color, Color) {
|
||||
(rotate_color(pair.0, degrees), rotate_color(pair.1, degrees))
|
||||
}
|
||||
|
||||
pub fn rotate_theme(theme: ThemeColors, degrees: f32) -> ThemeColors {
|
||||
if degrees == 0.0 {
|
||||
return theme;
|
||||
}
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: rotate_color(theme.ui.bg, degrees),
|
||||
bg_rgb: rotate_tuple(theme.ui.bg_rgb, degrees),
|
||||
text_primary: rotate_color(theme.ui.text_primary, degrees),
|
||||
text_muted: rotate_color(theme.ui.text_muted, degrees),
|
||||
text_dim: rotate_color(theme.ui.text_dim, degrees),
|
||||
border: rotate_color(theme.ui.border, degrees),
|
||||
header: rotate_color(theme.ui.header, degrees),
|
||||
unfocused: rotate_color(theme.ui.unfocused, degrees),
|
||||
accent: rotate_color(theme.ui.accent, degrees),
|
||||
surface: rotate_color(theme.ui.surface, degrees),
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: rotate_color(theme.status.playing_bg, degrees),
|
||||
playing_fg: rotate_color(theme.status.playing_fg, degrees),
|
||||
stopped_bg: rotate_color(theme.status.stopped_bg, degrees),
|
||||
stopped_fg: rotate_color(theme.status.stopped_fg, degrees),
|
||||
fill_on: rotate_color(theme.status.fill_on, degrees),
|
||||
fill_off: rotate_color(theme.status.fill_off, degrees),
|
||||
fill_bg: rotate_color(theme.status.fill_bg, degrees),
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: rotate_color(theme.selection.cursor_bg, degrees),
|
||||
cursor_fg: rotate_color(theme.selection.cursor_fg, degrees),
|
||||
selected_bg: rotate_color(theme.selection.selected_bg, degrees),
|
||||
selected_fg: rotate_color(theme.selection.selected_fg, degrees),
|
||||
in_range_bg: rotate_color(theme.selection.in_range_bg, degrees),
|
||||
in_range_fg: rotate_color(theme.selection.in_range_fg, degrees),
|
||||
cursor: rotate_color(theme.selection.cursor, degrees),
|
||||
selected: rotate_color(theme.selection.selected, degrees),
|
||||
in_range: rotate_color(theme.selection.in_range, degrees),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: rotate_color(theme.tile.playing_active_bg, degrees),
|
||||
playing_active_fg: rotate_color(theme.tile.playing_active_fg, degrees),
|
||||
playing_inactive_bg: rotate_color(theme.tile.playing_inactive_bg, degrees),
|
||||
playing_inactive_fg: rotate_color(theme.tile.playing_inactive_fg, degrees),
|
||||
active_bg: rotate_color(theme.tile.active_bg, degrees),
|
||||
active_fg: rotate_color(theme.tile.active_fg, degrees),
|
||||
content_bg: rotate_color(theme.tile.content_bg, degrees),
|
||||
inactive_bg: rotate_color(theme.tile.inactive_bg, degrees),
|
||||
inactive_fg: rotate_color(theme.tile.inactive_fg, degrees),
|
||||
active_selected_bg: rotate_color(theme.tile.active_selected_bg, degrees),
|
||||
active_in_range_bg: rotate_color(theme.tile.active_in_range_bg, degrees),
|
||||
link_bright: [
|
||||
rotate_tuple(theme.tile.link_bright[0], degrees),
|
||||
rotate_tuple(theme.tile.link_bright[1], degrees),
|
||||
rotate_tuple(theme.tile.link_bright[2], degrees),
|
||||
rotate_tuple(theme.tile.link_bright[3], degrees),
|
||||
rotate_tuple(theme.tile.link_bright[4], degrees),
|
||||
],
|
||||
link_dim: [
|
||||
rotate_tuple(theme.tile.link_dim[0], degrees),
|
||||
rotate_tuple(theme.tile.link_dim[1], degrees),
|
||||
rotate_tuple(theme.tile.link_dim[2], degrees),
|
||||
rotate_tuple(theme.tile.link_dim[3], degrees),
|
||||
rotate_tuple(theme.tile.link_dim[4], degrees),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: rotate_color(theme.header.tempo_bg, degrees),
|
||||
tempo_fg: rotate_color(theme.header.tempo_fg, degrees),
|
||||
bank_bg: rotate_color(theme.header.bank_bg, degrees),
|
||||
bank_fg: rotate_color(theme.header.bank_fg, degrees),
|
||||
pattern_bg: rotate_color(theme.header.pattern_bg, degrees),
|
||||
pattern_fg: rotate_color(theme.header.pattern_fg, degrees),
|
||||
stats_bg: rotate_color(theme.header.stats_bg, degrees),
|
||||
stats_fg: rotate_color(theme.header.stats_fg, degrees),
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: rotate_color(theme.modal.border, degrees),
|
||||
border_accent: rotate_color(theme.modal.border_accent, degrees),
|
||||
border_warn: rotate_color(theme.modal.border_warn, degrees),
|
||||
border_dim: rotate_color(theme.modal.border_dim, degrees),
|
||||
confirm: rotate_color(theme.modal.confirm, degrees),
|
||||
rename: rotate_color(theme.modal.rename, degrees),
|
||||
input: rotate_color(theme.modal.input, degrees),
|
||||
editor: rotate_color(theme.modal.editor, degrees),
|
||||
preview: rotate_color(theme.modal.preview, degrees),
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: rotate_color(theme.flash.error_bg, degrees),
|
||||
error_fg: rotate_color(theme.flash.error_fg, degrees),
|
||||
success_bg: rotate_color(theme.flash.success_bg, degrees),
|
||||
success_fg: rotate_color(theme.flash.success_fg, degrees),
|
||||
info_bg: rotate_color(theme.flash.info_bg, degrees),
|
||||
info_fg: rotate_color(theme.flash.info_fg, degrees),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: rotate_color(theme.list.playing_bg, degrees),
|
||||
playing_fg: rotate_color(theme.list.playing_fg, degrees),
|
||||
staged_play_bg: rotate_color(theme.list.staged_play_bg, degrees),
|
||||
staged_play_fg: rotate_color(theme.list.staged_play_fg, degrees),
|
||||
staged_stop_bg: rotate_color(theme.list.staged_stop_bg, degrees),
|
||||
staged_stop_fg: rotate_color(theme.list.staged_stop_fg, degrees),
|
||||
edit_bg: rotate_color(theme.list.edit_bg, degrees),
|
||||
edit_fg: rotate_color(theme.list.edit_fg, degrees),
|
||||
hover_bg: rotate_color(theme.list.hover_bg, degrees),
|
||||
hover_fg: rotate_color(theme.list.hover_fg, degrees),
|
||||
muted_bg: rotate_color(theme.list.muted_bg, degrees),
|
||||
muted_fg: rotate_color(theme.list.muted_fg, degrees),
|
||||
soloed_bg: rotate_color(theme.list.soloed_bg, degrees),
|
||||
soloed_fg: rotate_color(theme.list.soloed_fg, degrees),
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: rotate_color(theme.link_status.disabled, degrees),
|
||||
connected: rotate_color(theme.link_status.connected, degrees),
|
||||
listening: rotate_color(theme.link_status.listening, degrees),
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: rotate_color(theme.syntax.gap_bg, degrees),
|
||||
executed_bg: rotate_color(theme.syntax.executed_bg, degrees),
|
||||
selected_bg: rotate_color(theme.syntax.selected_bg, degrees),
|
||||
emit: rotate_color_pair(theme.syntax.emit, degrees),
|
||||
number: rotate_color_pair(theme.syntax.number, degrees),
|
||||
string: rotate_color_pair(theme.syntax.string, degrees),
|
||||
comment: rotate_color_pair(theme.syntax.comment, degrees),
|
||||
keyword: rotate_color_pair(theme.syntax.keyword, degrees),
|
||||
stack_op: rotate_color_pair(theme.syntax.stack_op, degrees),
|
||||
operator: rotate_color_pair(theme.syntax.operator, degrees),
|
||||
sound: rotate_color_pair(theme.syntax.sound, degrees),
|
||||
param: rotate_color_pair(theme.syntax.param, degrees),
|
||||
context: rotate_color_pair(theme.syntax.context, degrees),
|
||||
note: rotate_color_pair(theme.syntax.note, degrees),
|
||||
interval: rotate_color_pair(theme.syntax.interval, degrees),
|
||||
variable: rotate_color_pair(theme.syntax.variable, degrees),
|
||||
vary: rotate_color_pair(theme.syntax.vary, degrees),
|
||||
generator: rotate_color_pair(theme.syntax.generator, degrees),
|
||||
user_defined: rotate_color_pair(theme.syntax.user_defined, degrees),
|
||||
default: rotate_color_pair(theme.syntax.default, degrees),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: rotate_color(theme.table.row_even, degrees),
|
||||
row_odd: rotate_color(theme.table.row_odd, degrees),
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: rotate_color(theme.values.tempo, degrees),
|
||||
value: rotate_color(theme.values.value, degrees),
|
||||
},
|
||||
hint: HintColors {
|
||||
key: rotate_color(theme.hint.key, degrees),
|
||||
text: rotate_color(theme.hint.text, degrees),
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: rotate_color(theme.view_badge.bg, degrees),
|
||||
fg: rotate_color(theme.view_badge.fg, degrees),
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: rotate_color(theme.nav.selected_bg, degrees),
|
||||
selected_fg: rotate_color(theme.nav.selected_fg, degrees),
|
||||
unselected_bg: rotate_color(theme.nav.unselected_bg, degrees),
|
||||
unselected_fg: rotate_color(theme.nav.unselected_fg, degrees),
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: rotate_color(theme.editor_widget.cursor_bg, degrees),
|
||||
cursor_fg: rotate_color(theme.editor_widget.cursor_fg, degrees),
|
||||
selection_bg: rotate_color(theme.editor_widget.selection_bg, degrees),
|
||||
completion_bg: rotate_color(theme.editor_widget.completion_bg, degrees),
|
||||
completion_fg: rotate_color(theme.editor_widget.completion_fg, degrees),
|
||||
completion_selected: rotate_color(theme.editor_widget.completion_selected, degrees),
|
||||
completion_example: rotate_color(theme.editor_widget.completion_example, degrees),
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: rotate_color(theme.browser.directory, degrees),
|
||||
project_file: rotate_color(theme.browser.project_file, degrees),
|
||||
selected: rotate_color(theme.browser.selected, degrees),
|
||||
file: rotate_color(theme.browser.file, degrees),
|
||||
focused_border: rotate_color(theme.browser.focused_border, degrees),
|
||||
unfocused_border: rotate_color(theme.browser.unfocused_border, degrees),
|
||||
root: rotate_color(theme.browser.root, degrees),
|
||||
file_icon: rotate_color(theme.browser.file_icon, degrees),
|
||||
folder_icon: rotate_color(theme.browser.folder_icon, degrees),
|
||||
empty_text: rotate_color(theme.browser.empty_text, degrees),
|
||||
},
|
||||
input: InputColors {
|
||||
text: rotate_color(theme.input.text, degrees),
|
||||
cursor: rotate_color(theme.input.cursor, degrees),
|
||||
hint: rotate_color(theme.input.hint, degrees),
|
||||
},
|
||||
search: SearchColors {
|
||||
active: rotate_color(theme.search.active, degrees),
|
||||
inactive: rotate_color(theme.search.inactive, degrees),
|
||||
match_bg: rotate_color(theme.search.match_bg, degrees),
|
||||
match_fg: rotate_color(theme.search.match_fg, degrees),
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: rotate_color(theme.markdown.h1, degrees),
|
||||
h2: rotate_color(theme.markdown.h2, degrees),
|
||||
h3: rotate_color(theme.markdown.h3, degrees),
|
||||
code: rotate_color(theme.markdown.code, degrees),
|
||||
code_border: rotate_color(theme.markdown.code_border, degrees),
|
||||
link: rotate_color(theme.markdown.link, degrees),
|
||||
link_url: rotate_color(theme.markdown.link_url, degrees),
|
||||
quote: rotate_color(theme.markdown.quote, degrees),
|
||||
text: rotate_color(theme.markdown.text, degrees),
|
||||
list: rotate_color(theme.markdown.list, degrees),
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: rotate_color(theme.engine.header, degrees),
|
||||
header_focused: rotate_color(theme.engine.header_focused, degrees),
|
||||
divider: rotate_color(theme.engine.divider, degrees),
|
||||
scroll_indicator: rotate_color(theme.engine.scroll_indicator, degrees),
|
||||
label: rotate_color(theme.engine.label, degrees),
|
||||
label_focused: rotate_color(theme.engine.label_focused, degrees),
|
||||
label_dim: rotate_color(theme.engine.label_dim, degrees),
|
||||
value: rotate_color(theme.engine.value, degrees),
|
||||
focused: rotate_color(theme.engine.focused, degrees),
|
||||
normal: rotate_color(theme.engine.normal, degrees),
|
||||
dim: rotate_color(theme.engine.dim, degrees),
|
||||
path: rotate_color(theme.engine.path, degrees),
|
||||
border_magenta: rotate_color(theme.engine.border_magenta, degrees),
|
||||
border_green: rotate_color(theme.engine.border_green, degrees),
|
||||
border_cyan: rotate_color(theme.engine.border_cyan, degrees),
|
||||
separator: rotate_color(theme.engine.separator, degrees),
|
||||
hint_active: rotate_color(theme.engine.hint_active, degrees),
|
||||
hint_inactive: rotate_color(theme.engine.hint_inactive, degrees),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: rotate_color(theme.dict.word_name, degrees),
|
||||
word_bg: rotate_color(theme.dict.word_bg, degrees),
|
||||
alias: rotate_color(theme.dict.alias, degrees),
|
||||
stack_sig: rotate_color(theme.dict.stack_sig, degrees),
|
||||
description: rotate_color(theme.dict.description, degrees),
|
||||
example: rotate_color(theme.dict.example, degrees),
|
||||
category_focused: rotate_color(theme.dict.category_focused, degrees),
|
||||
category_selected: rotate_color(theme.dict.category_selected, degrees),
|
||||
category_normal: rotate_color(theme.dict.category_normal, degrees),
|
||||
category_dimmed: rotate_color(theme.dict.category_dimmed, degrees),
|
||||
border_focused: rotate_color(theme.dict.border_focused, degrees),
|
||||
border_normal: rotate_color(theme.dict.border_normal, degrees),
|
||||
header_desc: rotate_color(theme.dict.header_desc, degrees),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: rotate_color(theme.title.big_title, degrees),
|
||||
author: rotate_color(theme.title.author, degrees),
|
||||
link: rotate_color(theme.title.link, degrees),
|
||||
license: rotate_color(theme.title.license, degrees),
|
||||
prompt: rotate_color(theme.title.prompt, degrees),
|
||||
subtitle: rotate_color(theme.title.subtitle, degrees),
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: rotate_color(theme.meter.low, degrees),
|
||||
mid: rotate_color(theme.meter.mid, degrees),
|
||||
high: rotate_color(theme.meter.high, degrees),
|
||||
low_rgb: rotate_tuple(theme.meter.low_rgb, degrees),
|
||||
mid_rgb: rotate_tuple(theme.meter.mid_rgb, degrees),
|
||||
high_rgb: rotate_tuple(theme.meter.high_rgb, degrees),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
rotate_tuple(theme.sparkle.colors[0], degrees),
|
||||
rotate_tuple(theme.sparkle.colors[1], degrees),
|
||||
rotate_tuple(theme.sparkle.colors[2], degrees),
|
||||
rotate_tuple(theme.sparkle.colors[3], degrees),
|
||||
rotate_tuple(theme.sparkle.colors[4], degrees),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: rotate_color(theme.confirm.border, degrees),
|
||||
button_selected_bg: rotate_color(theme.confirm.button_selected_bg, degrees),
|
||||
button_selected_fg: rotate_color(theme.confirm.button_selected_fg, degrees),
|
||||
},
|
||||
}
|
||||
let d = degrees;
|
||||
build(&Palette {
|
||||
bg: rotate(palette.bg, d),
|
||||
surface: rotate(palette.surface, d),
|
||||
surface2: rotate(palette.surface2, d),
|
||||
fg: rotate(palette.fg, d),
|
||||
fg_dim: rotate(palette.fg_dim, d),
|
||||
fg_muted: rotate(palette.fg_muted, d),
|
||||
accent: rotate(palette.accent, d),
|
||||
red: rotate(palette.red, d),
|
||||
green: rotate(palette.green, d),
|
||||
yellow: rotate(palette.yellow, d),
|
||||
blue: rotate(palette.blue, d),
|
||||
purple: rotate(palette.purple, d),
|
||||
cyan: rotate(palette.cyan, d),
|
||||
orange: rotate(palette.orange, d),
|
||||
tempo_color: rotate(palette.tempo_color, d),
|
||||
bank_color: rotate(palette.bank_color, d),
|
||||
pattern_color: rotate(palette.pattern_color, d),
|
||||
title_accent: rotate(palette.title_accent, d),
|
||||
title_author: rotate(palette.title_author, d),
|
||||
secondary: rotate(palette.secondary, d),
|
||||
link_bright: rotate5(palette.link_bright, d),
|
||||
link_dim: rotate5(palette.link_dim, d),
|
||||
sparkle: rotate5(palette.sparkle, d),
|
||||
meter: rotate3(palette.meter, d),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
//! Tropicalia palette.
|
||||
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (20, 26, 22),
|
||||
surface: (30, 40, 34),
|
||||
surface2: (44, 56, 48),
|
||||
fg: (235, 225, 200),
|
||||
fg_dim: (155, 145, 120),
|
||||
fg_muted: (85, 80, 62),
|
||||
accent: (230, 50, 120),
|
||||
red: (240, 70, 70),
|
||||
green: (80, 200, 50),
|
||||
yellow: (255, 195, 0),
|
||||
blue: (0, 160, 200),
|
||||
purple: (180, 60, 180),
|
||||
cyan: (0, 200, 170),
|
||||
orange: (255, 140, 30),
|
||||
tempo_color: (230, 50, 120),
|
||||
bank_color: (0, 160, 200),
|
||||
pattern_color: (0, 200, 170),
|
||||
title_accent: (230, 50, 120),
|
||||
title_author: (0, 160, 200),
|
||||
secondary: (255, 140, 30),
|
||||
link_bright: [
|
||||
(230, 50, 120), (0, 160, 200), (255, 140, 30),
|
||||
(0, 200, 170), (80, 200, 50),
|
||||
],
|
||||
link_dim: [
|
||||
(72, 20, 40), (6, 50, 64), (80, 44, 12),
|
||||
(6, 62, 54), (26, 62, 18),
|
||||
],
|
||||
sparkle: [
|
||||
(230, 50, 120), (255, 195, 0), (80, 200, 50),
|
||||
(0, 160, 200), (180, 60, 180),
|
||||
],
|
||||
meter: [(70, 182, 44), (236, 178, 0), (220, 62, 62)],
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Stereo VU meter with dB-scaled level display.
|
||||
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
@@ -10,7 +8,6 @@ const DB_MIN: f32 = -48.0;
|
||||
const DB_MAX: f32 = 3.0;
|
||||
const DB_RANGE: f32 = DB_MAX - DB_MIN;
|
||||
|
||||
/// Stereo VU meter displaying left/right levels in dB.
|
||||
pub struct VuMeter {
|
||||
left: f32,
|
||||
right: f32,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Filled waveform display using braille characters.
|
||||
|
||||
use crate::scope::Orientation;
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -12,7 +10,6 @@ thread_local! {
|
||||
static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// Filled waveform renderer using braille dot plotting.
|
||||
pub struct Waveform<'a> {
|
||||
data: &'a [f32],
|
||||
orientation: Orientation,
|
||||
@@ -84,6 +81,9 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g
|
||||
let fine_height = height * 4;
|
||||
let len = data.len();
|
||||
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
patterns.clear();
|
||||
@@ -97,7 +97,7 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g
|
||||
let mut min_s = f32::MAX;
|
||||
let mut max_s = f32::MIN;
|
||||
for &s in slice {
|
||||
let s = (s * gain).clamp(-1.0, 1.0);
|
||||
let s = (s * auto_gain).clamp(-1.0, 1.0);
|
||||
if s < min_s {
|
||||
min_s = s;
|
||||
}
|
||||
@@ -142,6 +142,9 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai
|
||||
let fine_height = height * 4;
|
||||
let len = data.len();
|
||||
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
patterns.clear();
|
||||
@@ -155,7 +158,7 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai
|
||||
let mut min_s = f32::MAX;
|
||||
let mut max_s = f32::MIN;
|
||||
for &s in slice {
|
||||
let s = (s * gain).clamp(-1.0, 1.0);
|
||||
let s = (s * auto_gain).clamp(-1.0, 1.0);
|
||||
if s < min_s {
|
||||
min_s = s;
|
||||
}
|
||||
|
||||
8398
demos/01.cagire
8398
demos/01.cagire
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
{"version":1,"banks":[],"tempo":120.0,"playing_patterns":[[0,0]],"prelude":""}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":1,"banks":[],"tempo":120.0,"playing_patterns":[[0,0]],"prelude":""}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":1,"banks":[],"tempo":120.0,"playing_patterns":[[0,0]],"prelude":""}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":1,"banks":[],"tempo":120.0,"playing_patterns":[[0,0]],"prelude":""}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user