New themes

This commit is contained in:
2026-02-06 00:19:16 +01:00
parent 51f52be4ce
commit 6ec3a86568
11 changed files with 1178 additions and 46 deletions

View File

@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
## [0.0.8] - 2026-06-05 ## [0.0.8] - 2026-06-05
### Added
- New themes: **Eden** (dark forest — black background with green-only palette, terminal aesthetic) and **Georges** (Commodore 64 palette on pure black background).
### Improved ### Improved
- Sample library browser: search now shows folder names only (no files) while typing, sorted by fuzzy match score. After confirming search with Enter, folders can be expanded and collapsed normally. Esc clears the search filter before closing the panel. Left arrow on a file collapses the parent folder. Cursor and scroll position stay valid after expand/collapse operations. - Sample library browser: search now shows folder names only (no files) while typing, sorted by fuzzy match score. After confirming search with Enter, folders can be expanded and collapsed normally. Esc clears the search filter before closing the panel. Left arrow on a file collapses the parent folder. Cursor and scroll position stay valid after expand/collapse operations.
- RAM optimizations saving ~5 MB at startup plus smaller enums and fewer hot-path allocations: - RAM optimizations saving ~5 MB at startup plus smaller enums and fewer hot-path allocations:

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use super::ops::Op; use super::ops::Op;
@@ -88,18 +89,18 @@ fn tokenize(input: &str) -> Vec<Token> {
let span = SourceSpan { start: start as u32, end: end as u32 }; let span = SourceSpan { start: start as u32, end: end as u32 };
// Normalize shorthand float syntax: .25 -> 0.25, -.5 -> -0.5 // Normalize shorthand float syntax: .25 -> 0.25, -.5 -> -0.5
let word_to_parse = if word.starts_with('.') let word_to_parse: Cow<str> = if word.starts_with('.')
&& word.len() > 1 && word.len() > 1
&& word.as_bytes()[1].is_ascii_digit() && word.as_bytes()[1].is_ascii_digit()
{ {
format!("0{word}") Cow::Owned(format!("0{word}"))
} else if word.starts_with("-.") } else if word.starts_with("-.")
&& word.len() > 2 && word.len() > 2
&& word.as_bytes()[2].is_ascii_digit() && word.as_bytes()[2].is_ascii_digit()
{ {
format!("-0{}", &word[1..]) Cow::Owned(format!("-0{}", &word[1..]))
} else { } else {
word.clone() Cow::Borrowed(&word)
}; };
if let Ok(i) = word_to_parse.parse::<i64>() { if let Ok(i) = word_to_parse.parse::<i64>() {
@@ -121,20 +122,10 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
while i < tokens.len() { while i < tokens.len() {
match &tokens[i] { match &tokens[i] {
Token::Int(n, span) => { Token::Int(n, span) => {
let key = n.to_string(); ops.push(Op::PushInt(*n, Some(*span)));
if let Some(body) = dict.lock().get(&key).cloned() {
ops.extend(body);
} else {
ops.push(Op::PushInt(*n, Some(*span)));
}
} }
Token::Float(f, span) => { Token::Float(f, span) => {
let key = f.to_string(); ops.push(Op::PushFloat(*f, Some(*span)));
if let Some(body) = dict.lock().get(&key).cloned() {
ops.extend(body);
} else {
ops.push(Op::PushFloat(*f, Some(*span)));
}
} }
Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))), Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))),
Token::Word(w, span) => { Token::Word(w, span) => {

View File

@@ -12,6 +12,7 @@ use tui_textarea::TextArea;
pub type Highlighter<'a> = &'a dyn Fn(usize, &str) -> Vec<(Style, String)>; pub type Highlighter<'a> = &'a dyn Fn(usize, &str) -> Vec<(Style, String)>;
#[derive(Clone)]
pub struct CompletionCandidate { pub struct CompletionCandidate {
pub name: String, pub name: String,
pub signature: String, pub signature: String,

View File

@@ -0,0 +1,282 @@
use super::*;
use ratatui::style::Color;
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);
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,
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)),
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,
},
}
}

View File

@@ -0,0 +1,280 @@
use super::*;
use ratatui::style::Color;
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);
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,
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)),
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,
},
}
}

View File

@@ -0,0 +1,284 @@
use super::*;
use ratatui::style::Color;
// 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);
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,
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)),
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,
},
}
}

View File

@@ -0,0 +1,281 @@
use super::*;
use ratatui::style::Color;
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);
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,
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)),
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,
},
}
}

View File

@@ -4,10 +4,14 @@
mod catppuccin_latte; mod catppuccin_latte;
mod catppuccin_mocha; mod catppuccin_mocha;
mod dracula; mod dracula;
mod eden;
mod ember;
mod georges;
mod fairyfloss; mod fairyfloss;
mod gruvbox_dark; mod gruvbox_dark;
mod hot_dog_stand; mod hot_dog_stand;
mod kanagawa; mod kanagawa;
mod letz_light;
mod monochrome_black; mod monochrome_black;
mod monochrome_white; mod monochrome_white;
mod monokai; mod monokai;
@@ -41,6 +45,10 @@ pub const THEMES: &[ThemeEntry] = &[
ThemeEntry { id: "Kanagawa", label: "Kanagawa", colors: kanagawa::theme }, ThemeEntry { id: "Kanagawa", label: "Kanagawa", colors: kanagawa::theme },
ThemeEntry { id: "Fairyfloss", label: "Fairyfloss", colors: fairyfloss::theme }, ThemeEntry { id: "Fairyfloss", label: "Fairyfloss", colors: fairyfloss::theme },
ThemeEntry { id: "HotDogStand", label: "Hot Dog Stand", colors: hot_dog_stand::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! { thread_local! {

View File

@@ -4,7 +4,9 @@ use rand::rngs::StdRng;
use rand::SeedableRng; use rand::SeedableRng;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::{Arc, LazyLock};
use cagire_ratatui::CompletionCandidate;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
@@ -25,6 +27,18 @@ use crate::state::{
const STEPS_PER_PAGE: usize = 32; const STEPS_PER_PAGE: usize = 32;
static COMPLETION_CANDIDATES: LazyLock<Vec<CompletionCandidate>> = LazyLock::new(|| {
model::WORDS
.iter()
.map(|w| CompletionCandidate {
name: w.name.to_string(),
signature: w.stack.to_string(),
description: w.desc.to_string(),
example: w.example.to_string(),
})
.collect()
});
pub struct App { pub struct App {
pub project_state: ProjectState, pub project_state: ProjectState,
pub ui: UiState, pub ui: UiState,
@@ -310,16 +324,7 @@ impl App {
script.lines().map(String::from).collect() script.lines().map(String::from).collect()
}; };
self.editor_ctx.editor.set_content(lines); self.editor_ctx.editor.set_content(lines);
let candidates = model::WORDS self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone());
.iter()
.map(|w| cagire_ratatui::CompletionCandidate {
name: w.name.to_string(),
signature: w.stack.to_string(),
description: w.desc.to_string(),
example: w.example.to_string(),
})
.collect();
self.editor_ctx.editor.set_candidates(candidates);
self.editor_ctx self.editor_ctx
.editor .editor
.set_completion_enabled(self.ui.show_completion); .set_completion_enabled(self.ui.show_completion);
@@ -350,16 +355,7 @@ impl App {
prelude.lines().map(String::from).collect() prelude.lines().map(String::from).collect()
}; };
self.editor_ctx.editor.set_content(lines); self.editor_ctx.editor.set_content(lines);
let candidates = model::WORDS self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone());
.iter()
.map(|w| cagire_ratatui::CompletionCandidate {
name: w.name.to_string(),
signature: w.stack.to_string(),
description: w.desc.to_string(),
example: w.example.to_string(),
})
.collect();
self.editor_ctx.editor.set_candidates(candidates);
self.editor_ctx self.editor_ctx
.editor .editor
.set_completion_enabled(self.ui.show_completion); .set_completion_enabled(self.ui.show_completion);

View File

@@ -7,6 +7,7 @@ pub struct ProjectState {
pub project: Project, pub project: Project,
pub file_path: Option<PathBuf>, pub file_path: Option<PathBuf>,
dirty_patterns: [[bool; MAX_PATTERNS]; MAX_BANKS], dirty_patterns: [[bool; MAX_PATTERNS]; MAX_BANKS],
dirty_count: usize,
} }
impl Default for ProjectState { impl Default for ProjectState {
@@ -15,6 +16,7 @@ impl Default for ProjectState {
project: Project::default(), project: Project::default(),
file_path: None, file_path: None,
dirty_patterns: [[false; MAX_PATTERNS]; MAX_BANKS], dirty_patterns: [[false; MAX_PATTERNS]; MAX_BANKS],
dirty_count: 0,
}; };
state.mark_all_dirty(); state.mark_all_dirty();
state state
@@ -23,15 +25,22 @@ impl Default for ProjectState {
impl ProjectState { impl ProjectState {
pub fn mark_dirty(&mut self, bank: usize, pattern: usize) { pub fn mark_dirty(&mut self, bank: usize, pattern: usize) {
self.dirty_patterns[bank][pattern] = true; if !self.dirty_patterns[bank][pattern] {
self.dirty_patterns[bank][pattern] = true;
self.dirty_count += 1;
}
} }
pub fn mark_all_dirty(&mut self) { pub fn mark_all_dirty(&mut self) {
self.dirty_patterns = [[true; MAX_PATTERNS]; MAX_BANKS]; self.dirty_patterns = [[true; MAX_PATTERNS]; MAX_BANKS];
self.dirty_count = MAX_BANKS * MAX_PATTERNS;
} }
pub fn take_dirty(&mut self) -> Vec<(usize, usize)> { pub fn take_dirty(&mut self) -> Vec<(usize, usize)> {
let mut result = Vec::new(); if self.dirty_count == 0 {
return Vec::new();
}
let mut result = Vec::with_capacity(self.dirty_count);
for (bank, patterns) in self.dirty_patterns.iter_mut().enumerate() { for (bank, patterns) in self.dirty_patterns.iter_mut().enumerate() {
for (pattern, dirty) in patterns.iter_mut().enumerate() { for (pattern, dirty) in patterns.iter_mut().enumerate() {
if *dirty { if *dirty {
@@ -40,6 +49,7 @@ impl ProjectState {
} }
} }
} }
self.dirty_count = 0;
result result
} }
} }

View File

@@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style}; use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span}; use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table}; use ratatui::widgets::{Block, Borders, Cell, Clear, Padding, Paragraph, Row, Table};
use ratatui::Frame; use ratatui::Frame;
use crate::app::App; use crate::app::App;
@@ -50,12 +50,8 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &Sequenc
let theme = theme::get(); let theme = theme::get();
let bg_color = theme.ui.bg; let bg_color = theme.ui.bg;
let blank = " ".repeat(term.width as usize); frame.render_widget(Clear, term);
let lines: Vec<Line> = (0..term.height).map(|_| Line::raw(&blank)).collect(); frame.render_widget(Block::new().style(Style::default().bg(bg_color)), term);
frame.render_widget(
Paragraph::new(lines).style(Style::default().bg(bg_color)),
term,
);
if app.ui.show_title { if app.ui.show_title {
title_view::render(frame, term, &app.ui); title_view::render(frame, term, &app.ui);