diff --git a/crates/forth/src/lib.rs b/crates/forth/src/lib.rs index c974809..169af86 100644 --- a/crates/forth/src/lib.rs +++ b/crates/forth/src/lib.rs @@ -5,6 +5,8 @@ mod types; mod vm; mod words; -pub use types::{CcMemory, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables}; +pub use types::{ + CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables, +}; pub use vm::Forth; pub use words::{Word, WordCompile, WORDS}; diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index f7777b2..da496b7 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -4,7 +4,11 @@ use std::sync::{Arc, Mutex}; use super::ops::Op; -pub const MAX_MIDI_DEVICES: usize = 4; +/// Trait for accessing MIDI CC values. Implement this to provide CC memory to the Forth VM. +pub trait CcAccess: Send + Sync { + /// Get the CC value for a given device, channel (0-15), and CC number (0-127). + fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8; +} #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SourceSpan { @@ -31,7 +35,7 @@ pub struct StepContext { pub speed: f64, pub fill: bool, pub nudge_secs: f64, - pub cc_memory: Option, + pub cc_access: Option>, #[cfg(feature = "desktop")] pub mouse_x: f64, #[cfg(feature = "desktop")] @@ -50,7 +54,6 @@ pub type Variables = Arc>>; pub type Dictionary = Arc>>>; pub type Rng = Arc>; pub type Stack = Arc>>; -pub type CcMemory = Arc>; pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(String, Value)]); #[derive(Clone, Debug)] @@ -134,7 +137,6 @@ pub(super) struct CmdRegister { deltas: Vec, } - impl CmdRegister { pub(super) fn set_sound(&mut self, val: Value) { self.sound = Some(val); @@ -165,4 +167,3 @@ impl CmdRegister { self.params.clear(); } } - diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 57f09a6..5344552 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -817,9 +817,7 @@ impl Forth { let chan = get_int("chan") .map(|c| (c.clamp(1, 16) - 1) as u8) .unwrap_or(0); - let dev = get_int("dev") - .map(|d| d.clamp(0, 3) as u8) - .unwrap_or(0); + let dev = get_int("dev").map(|d| d.clamp(0, 3) as u8).unwrap_or(0); if let (Some(cc), Some(val)) = (get_int("ccnum"), get_int("ccout")) { let cc = cc.clamp(0, 127) as u8; @@ -840,51 +838,29 @@ impl Forth { let velocity = get_int("velocity").unwrap_or(100).clamp(0, 127) as u8; let dur = get_float("dur").unwrap_or(1.0); let dur_secs = dur * ctx.step_duration(); - outputs.push(format!("/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}")); + outputs.push(format!( + "/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}" + )); } } Op::MidiClock => { let (_, params) = cmd.snapshot().unwrap_or((None, &[])); - let dev = params - .iter() - .rev() - .find(|(k, _)| k == "dev") - .and_then(|(_, v)| v.as_int().ok()) - .map(|d| d.clamp(0, 3) as u8) - .unwrap_or(0); + let dev = extract_dev_param(params); outputs.push(format!("/midi/clock/dev/{dev}")); } Op::MidiStart => { let (_, params) = cmd.snapshot().unwrap_or((None, &[])); - let dev = params - .iter() - .rev() - .find(|(k, _)| k == "dev") - .and_then(|(_, v)| v.as_int().ok()) - .map(|d| d.clamp(0, 3) as u8) - .unwrap_or(0); + let dev = extract_dev_param(params); outputs.push(format!("/midi/start/dev/{dev}")); } Op::MidiStop => { let (_, params) = cmd.snapshot().unwrap_or((None, &[])); - let dev = params - .iter() - .rev() - .find(|(k, _)| k == "dev") - .and_then(|(_, v)| v.as_int().ok()) - .map(|d| d.clamp(0, 3) as u8) - .unwrap_or(0); + let dev = extract_dev_param(params); outputs.push(format!("/midi/stop/dev/{dev}")); } Op::MidiContinue => { let (_, params) = cmd.snapshot().unwrap_or((None, &[])); - let dev = params - .iter() - .rev() - .find(|(k, _)| k == "dev") - .and_then(|(_, v)| v.as_int().ok()) - .map(|d| d.clamp(0, 3) as u8) - .unwrap_or(0); + let dev = extract_dev_param(params); outputs.push(format!("/midi/continue/dev/{dev}")); } Op::GetMidiCC => { @@ -893,18 +869,11 @@ impl Forth { let cc_clamped = (cc.clamp(0, 127)) as usize; let chan_clamped = (chan.clamp(1, 16) - 1) as usize; let (_, params) = cmd.snapshot().unwrap_or((None, &[])); - let dev = params - .iter() - .rev() - .find(|(k, _)| k == "dev") - .and_then(|(_, v)| v.as_int().ok()) - .map(|d| d.clamp(0, 3) as usize) - .unwrap_or(0); + let dev = extract_dev_param(params) as usize; let val = ctx - .cc_memory + .cc_access .as_ref() - .and_then(|mem| mem.lock().ok()) - .map(|mem| mem[dev][chan_clamped][cc_clamped]) + .map(|cc| cc.get_cc(dev, chan_clamped, cc_clamped)) .unwrap_or(0); stack.push(Value::Int(val as i64, None)); } @@ -916,6 +885,16 @@ impl Forth { } } +fn extract_dev_param(params: &[(String, Value)]) -> u8 { + params + .iter() + .rev() + .find(|(k, _)| k == "dev") + .and_then(|(_, v)| v.as_int().ok()) + .map(|d| d.clamp(0, 3) as u8) + .unwrap_or(0) +} + fn is_tempo_scaled_param(name: &str) -> bool { matches!( name, diff --git a/crates/forth/src/words.rs b/crates/forth/src/words.rs index ff34ff6..935cdde 100644 --- a/crates/forth/src/words.rs +++ b/crates/forth/src/words.rs @@ -1,3 +1,6 @@ +use std::collections::HashMap; +use std::sync::LazyLock; + use super::ops::Op; use super::theory; use super::types::{Dictionary, SourceSpan}; @@ -2446,6 +2449,21 @@ pub const WORDS: &[Word] = &[ }, ]; +static WORD_MAP: LazyLock> = LazyLock::new(|| { + let mut map = HashMap::with_capacity(WORDS.len() * 2); + for word in WORDS { + map.insert(word.name, word); + for alias in word.aliases { + map.insert(alias, word); + } + } + map +}); + +fn lookup_word(name: &str) -> Option<&'static Word> { + WORD_MAP.get(name).copied() +} + pub(super) fn simple_op(name: &str) -> Option { Some(match name { "dup" => Op::Dup, @@ -2636,23 +2654,21 @@ pub(super) fn compile_word( return true; } - for word in WORDS { - if word.name == name || word.aliases.contains(&name) { - match &word.compile { - Simple => { - if let Some(op) = simple_op(word.name) { - ops.push(op); - } - } - Context(ctx) => ops.push(Op::GetContext((*ctx).into())), - Param => ops.push(Op::SetParam(word.name.into())), - Probability(p) => { - ops.push(Op::PushFloat(*p, None)); - ops.push(Op::ChanceExec); + if let Some(word) = lookup_word(name) { + match &word.compile { + Simple => { + if let Some(op) = simple_op(word.name) { + ops.push(op); } } - return true; + Context(ctx) => ops.push(Op::GetContext((*ctx).into())), + Param => ops.push(Op::SetParam(word.name.into())), + Probability(p) => { + ops.push(Op::PushFloat(*p, None)); + ops.push(Op::ChanceExec); + } } + return true; } // @varname - fetch variable diff --git a/crates/ratatui/src/file_browser.rs b/crates/ratatui/src/file_browser.rs index c756733..26f976d 100644 --- a/crates/ratatui/src/file_browser.rs +++ b/crates/ratatui/src/file_browser.rs @@ -1,7 +1,6 @@ -use crate::theme::{browser, input, ui}; -use ratatui::style::Color; +use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; -use ratatui::style::Style; +use ratatui::style::{Color, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::Paragraph; use ratatui::Frame; @@ -14,7 +13,7 @@ pub struct FileBrowserModal<'a> { entries: &'a [(String, bool, bool)], selected: usize, scroll_offset: usize, - border_color: Color, + border_color: Option, width: u16, height: u16, } @@ -27,7 +26,7 @@ impl<'a> FileBrowserModal<'a> { entries, selected: 0, scroll_offset: 0, - border_color: ui::TEXT_PRIMARY, + border_color: None, width: 60, height: 16, } @@ -44,7 +43,7 @@ impl<'a> FileBrowserModal<'a> { } pub fn border_color(mut self, c: Color) -> Self { - self.border_color = c; + self.border_color = Some(c); self } @@ -59,10 +58,13 @@ impl<'a> FileBrowserModal<'a> { } pub fn render_centered(self, frame: &mut Frame, term: Rect) { + let colors = theme::get(); + let border_color = self.border_color.unwrap_or(colors.ui.text_primary); + let inner = ModalFrame::new(self.title) .width(self.width) .height(self.height) - .border_color(self.border_color) + .border_color(border_color) .render_centered(frame, term); let rows = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner); @@ -71,8 +73,8 @@ impl<'a> FileBrowserModal<'a> { frame.render_widget( Paragraph::new(Line::from(vec![ Span::raw("> "), - Span::styled(self.input, Style::new().fg(input::TEXT)), - Span::styled("█", Style::new().fg(input::CURSOR)), + Span::styled(self.input, Style::new().fg(colors.input.text)), + Span::styled("█", Style::new().fg(colors.input.cursor)), ])), rows[0], ); @@ -97,13 +99,13 @@ impl<'a> FileBrowserModal<'a> { format!("{prefix}{name}") }; let color = if is_selected { - browser::SELECTED + colors.browser.selected } else if *is_dir { - browser::DIRECTORY + colors.browser.directory } else if *is_cagire { - browser::PROJECT_FILE + colors.browser.project_file } else { - browser::FILE + colors.browser.file }; Line::from(Span::styled(display, Style::new().fg(color))) }) diff --git a/crates/ratatui/src/list_select.rs b/crates/ratatui/src/list_select.rs index e728db9..80ee26e 100644 --- a/crates/ratatui/src/list_select.rs +++ b/crates/ratatui/src/list_select.rs @@ -1,4 +1,4 @@ -use crate::theme::{hint, ui}; +use crate::theme; use ratatui::layout::Rect; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; @@ -51,10 +51,11 @@ impl<'a> ListSelect<'a> { } pub fn render(self, frame: &mut Frame, area: Rect) { - let cursor_style = Style::new().fg(hint::KEY).add_modifier(Modifier::BOLD); - let selected_style = Style::new().fg(ui::ACCENT); + let colors = theme::get(); + let cursor_style = Style::new().fg(colors.hint.key).add_modifier(Modifier::BOLD); + let selected_style = Style::new().fg(colors.ui.accent); let normal_style = Style::default(); - let indicator_style = Style::new().fg(ui::TEXT_DIM); + let indicator_style = Style::new().fg(colors.ui.text_dim); let visible_end = (self.scroll_offset + self.visible_count).min(self.items.len()); let has_above = self.scroll_offset > 0; diff --git a/crates/ratatui/src/sample_browser.rs b/crates/ratatui/src/sample_browser.rs index fc4c73f..64e7c58 100644 --- a/crates/ratatui/src/sample_browser.rs +++ b/crates/ratatui/src/sample_browser.rs @@ -1,4 +1,4 @@ -use crate::theme::{browser, search}; +use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; @@ -59,10 +59,11 @@ impl<'a> SampleBrowser<'a> { } pub fn render(self, frame: &mut Frame, area: Rect) { + let colors = theme::get(); let border_style = if self.focused { - Style::new().fg(browser::FOCUSED_BORDER) + Style::new().fg(colors.browser.focused_border) } else { - Style::new().fg(browser::UNFOCUSED_BORDER) + Style::new().fg(colors.browser.unfocused_border) }; let block = Block::default() @@ -90,16 +91,16 @@ impl<'a> SampleBrowser<'a> { }; if let Some(sa) = search_area { - self.render_search(frame, sa); + self.render_search(frame, sa, &colors); } - self.render_tree(frame, list_area); + self.render_tree(frame, list_area, &colors); } - fn render_search(&self, frame: &mut Frame, area: Rect) { + fn render_search(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) { let style = if self.search_active { - Style::new().fg(search::ACTIVE) + Style::new().fg(colors.search.active) } else { - Style::new().fg(search::INACTIVE) + Style::new().fg(colors.search.inactive) }; let cursor = if self.search_active { "_" } else { "" }; let text = format!("/{}{}", self.search_query, cursor); @@ -107,7 +108,7 @@ impl<'a> SampleBrowser<'a> { frame.render_widget(Paragraph::new(vec![line]), area); } - fn render_tree(&self, frame: &mut Frame, area: Rect) { + fn render_tree(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) { let height = area.height as usize; if self.entries.is_empty() { let msg = if self.search_query.is_empty() { @@ -115,7 +116,7 @@ impl<'a> SampleBrowser<'a> { } else { "No matches" }; - let line = Line::from(Span::styled(msg, Style::new().fg(browser::EMPTY_TEXT))); + let line = Line::from(Span::styled(msg, Style::new().fg(colors.browser.empty_text))); frame.render_widget(Paragraph::new(vec![line]), area); return; } @@ -130,23 +131,23 @@ impl<'a> SampleBrowser<'a> { let (icon, icon_color) = match entry.kind { TreeLineKind::Root { expanded: true } | TreeLineKind::Folder { expanded: true } => { - ("\u{25BC} ", browser::FOLDER_ICON) + ("\u{25BC} ", colors.browser.folder_icon) } TreeLineKind::Root { expanded: false } - | TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", browser::FOLDER_ICON), - TreeLineKind::File => ("\u{266A} ", browser::FILE_ICON), + | TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", colors.browser.folder_icon), + TreeLineKind::File => ("\u{266A} ", colors.browser.file_icon), }; let label_style = if is_cursor && self.focused { - Style::new().fg(browser::SELECTED).add_modifier(Modifier::BOLD) + Style::new().fg(colors.browser.selected).add_modifier(Modifier::BOLD) } else if is_cursor { - Style::new().fg(browser::FILE) + Style::new().fg(colors.browser.file) } else { match entry.kind { TreeLineKind::Root { .. } => { - Style::new().fg(browser::ROOT).add_modifier(Modifier::BOLD) + Style::new().fg(colors.browser.root).add_modifier(Modifier::BOLD) } - TreeLineKind::Folder { .. } => Style::new().fg(browser::DIRECTORY), + TreeLineKind::Folder { .. } => Style::new().fg(colors.browser.directory), TreeLineKind::File => Style::default(), } }; diff --git a/crates/ratatui/src/scope.rs b/crates/ratatui/src/scope.rs index 4bdbda8..df39f20 100644 --- a/crates/ratatui/src/scope.rs +++ b/crates/ratatui/src/scope.rs @@ -1,4 +1,4 @@ -use crate::theme::meter; +use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Color; @@ -18,7 +18,7 @@ pub enum Orientation { pub struct Scope<'a> { data: &'a [f32], orientation: Orientation, - color: Color, + color: Option, gain: f32, } @@ -27,7 +27,7 @@ impl<'a> Scope<'a> { Self { data, orientation: Orientation::Horizontal, - color: meter::LOW, + color: None, gain: 1.0, } } @@ -38,7 +38,7 @@ impl<'a> Scope<'a> { } pub fn color(mut self, c: Color) -> Self { - self.color = c; + self.color = Some(c); self } } @@ -49,11 +49,13 @@ impl Widget for Scope<'_> { return; } + let color = self.color.unwrap_or_else(|| theme::get().meter.low); + match self.orientation { Orientation::Horizontal => { - render_horizontal(self.data, area, buf, self.color, self.gain) + render_horizontal(self.data, area, buf, color, self.gain) } - Orientation::Vertical => render_vertical(self.data, area, buf, self.color, self.gain), + Orientation::Vertical => render_vertical(self.data, area, buf, color, self.gain), } } } @@ -64,7 +66,6 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g let fine_width = width * 2; let fine_height = height * 4; - // Auto-scale: find peak amplitude and normalize to fill height let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max); let auto_gain = if peak > 0.001 { gain / peak } else { gain }; @@ -121,7 +122,6 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai let fine_width = width * 2; let fine_height = height * 4; - // Auto-scale: find peak amplitude and normalize to fill width let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max); let auto_gain = if peak > 0.001 { gain / peak } else { gain }; diff --git a/crates/ratatui/src/sparkles.rs b/crates/ratatui/src/sparkles.rs index 7db6610..644c62f 100644 --- a/crates/ratatui/src/sparkles.rs +++ b/crates/ratatui/src/sparkles.rs @@ -1,4 +1,4 @@ -use crate::theme::sparkle; +use crate::theme; use rand::Rng; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -41,8 +41,9 @@ impl Sparkles { impl Widget for &Sparkles { fn render(self, area: Rect, buf: &mut Buffer) { + let colors = theme::get().sparkle.colors; for sp in &self.sparkles { - let color = sparkle::COLORS[sp.char_idx % sparkle::COLORS.len()]; + let color = colors[sp.char_idx % colors.len()]; let intensity = (sp.life as f32 / 30.0).min(1.0); let r = (color.0 as f32 * intensity) as u8; let g = (color.1 as f32 * intensity) as u8; diff --git a/crates/ratatui/src/spectrum.rs b/crates/ratatui/src/spectrum.rs index ee8d7f9..b40db9a 100644 --- a/crates/ratatui/src/spectrum.rs +++ b/crates/ratatui/src/spectrum.rs @@ -1,4 +1,4 @@ -use crate::theme::meter; +use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Color; @@ -22,6 +22,7 @@ impl Widget for Spectrum<'_> { return; } + let colors = theme::get(); let height = area.height as f32; let band_width = area.width as usize / 32; if band_width == 0 { @@ -40,11 +41,11 @@ impl Widget for Spectrum<'_> { 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(meter::LOW_RGB.0, meter::LOW_RGB.1, meter::LOW_RGB.2) + Color::Rgb(colors.meter.low_rgb.0, colors.meter.low_rgb.1, colors.meter.low_rgb.2) } else if ratio < 0.66 { - Color::Rgb(meter::MID_RGB.0, meter::MID_RGB.1, meter::MID_RGB.2) + Color::Rgb(colors.meter.mid_rgb.0, colors.meter.mid_rgb.1, colors.meter.mid_rgb.2) } else { - Color::Rgb(meter::HIGH_RGB.0, meter::HIGH_RGB.1, meter::HIGH_RGB.2) + Color::Rgb(colors.meter.high_rgb.0, colors.meter.high_rgb.1, colors.meter.high_rgb.2) }; for dx in 0..band_width as u16 { let x = x_start + dx; diff --git a/crates/ratatui/src/text_input.rs b/crates/ratatui/src/text_input.rs index 28e745c..9fd0f14 100644 --- a/crates/ratatui/src/text_input.rs +++ b/crates/ratatui/src/text_input.rs @@ -1,4 +1,4 @@ -use crate::theme::{input, ui}; +use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Color, Style}; use ratatui::text::{Line, Span}; @@ -11,7 +11,7 @@ pub struct TextInputModal<'a> { title: &'a str, input: &'a str, hint: Option<&'a str>, - border_color: Color, + border_color: Option, width: u16, } @@ -21,7 +21,7 @@ impl<'a> TextInputModal<'a> { title, input, hint: None, - border_color: ui::TEXT_PRIMARY, + border_color: None, width: 50, } } @@ -32,7 +32,7 @@ impl<'a> TextInputModal<'a> { } pub fn border_color(mut self, c: Color) -> Self { - self.border_color = c; + self.border_color = Some(c); self } @@ -42,12 +42,14 @@ impl<'a> TextInputModal<'a> { } pub fn render_centered(self, frame: &mut Frame, term: Rect) { + let colors = theme::get(); + let border_color = self.border_color.unwrap_or(colors.ui.text_primary); let height = if self.hint.is_some() { 6 } else { 5 }; let inner = ModalFrame::new(self.title) .width(self.width) .height(height) - .border_color(self.border_color) + .border_color(border_color) .render_centered(frame, term); if self.hint.is_some() { @@ -57,15 +59,15 @@ impl<'a> TextInputModal<'a> { frame.render_widget( Paragraph::new(Line::from(vec![ Span::raw("> "), - Span::styled(self.input, Style::new().fg(input::TEXT)), - Span::styled("█", Style::new().fg(input::CURSOR)), + Span::styled(self.input, Style::new().fg(colors.input.text)), + Span::styled("█", Style::new().fg(colors.input.cursor)), ])), rows[0], ); if let Some(hint) = self.hint { frame.render_widget( - Paragraph::new(Span::styled(hint, Style::new().fg(input::HINT))), + Paragraph::new(Span::styled(hint, Style::new().fg(colors.input.hint))), rows[1], ); } @@ -73,8 +75,8 @@ impl<'a> TextInputModal<'a> { frame.render_widget( Paragraph::new(Line::from(vec![ Span::raw("> "), - Span::styled(self.input, Style::new().fg(input::TEXT)), - Span::styled("█", Style::new().fg(input::CURSOR)), + Span::styled(self.input, Style::new().fg(colors.input.text)), + Span::styled("█", Style::new().fg(colors.input.cursor)), ])), inner, ); diff --git a/crates/ratatui/src/theme.rs b/crates/ratatui/src/theme.rs deleted file mode 100644 index 88e1abd..0000000 --- a/crates/ratatui/src/theme.rs +++ /dev/null @@ -1,2651 +0,0 @@ -//! Centralized color definitions for Cagire TUI. -//! Supports multiple color schemes with runtime switching. - -use ratatui::style::Color; -use std::cell::RefCell; - -// Thread-local current theme -thread_local! { - static CURRENT_THEME: RefCell = RefCell::new(ThemeColors::catppuccin_mocha()); -} - -/// Get the current theme colors -pub fn get() -> ThemeColors { - CURRENT_THEME.with(|t| t.borrow().clone()) -} - -/// Set the current theme colors -pub fn set(theme: ThemeColors) { - CURRENT_THEME.with(|t| *t.borrow_mut() = theme); -} - -#[derive(Clone)] -pub struct ThemeColors { - pub ui: UiColors, - pub status: StatusColors, - pub selection: SelectionColors, - pub tile: TileColors, - pub header: HeaderColors, - pub modal: ModalColors, - pub flash: FlashColors, - pub list: ListColors, - pub link_status: LinkStatusColors, - pub syntax: SyntaxColors, - pub table: TableColors, - pub values: ValuesColors, - pub hint: HintColors, - pub view_badge: ViewBadgeColors, - pub nav: NavColors, - pub editor_widget: EditorWidgetColors, - pub browser: BrowserColors, - pub input: InputColors, - pub search: SearchColors, - pub markdown: MarkdownColors, - pub engine: EngineColors, - pub dict: DictColors, - pub title: TitleColors, - pub meter: MeterColors, - pub sparkle: SparkleColors, - pub confirm: ConfirmColors, -} - -#[derive(Clone)] -pub struct UiColors { - pub bg: Color, - pub bg_rgb: (u8, u8, u8), - pub text_primary: Color, - pub text_muted: Color, - pub text_dim: Color, - pub border: Color, - pub header: Color, - pub unfocused: Color, - pub accent: Color, - pub surface: Color, -} - -#[derive(Clone)] -pub struct StatusColors { - pub playing_bg: Color, - pub playing_fg: Color, - pub stopped_bg: Color, - pub stopped_fg: Color, - pub fill_on: Color, - pub fill_off: Color, - pub fill_bg: Color, -} - -#[derive(Clone)] -pub struct SelectionColors { - pub cursor_bg: Color, - pub cursor_fg: Color, - pub selected_bg: Color, - pub selected_fg: Color, - pub in_range_bg: Color, - pub in_range_fg: Color, - pub cursor: Color, - pub selected: Color, - pub in_range: Color, -} - -#[derive(Clone)] -pub struct TileColors { - pub playing_active_bg: Color, - pub playing_active_fg: Color, - pub playing_inactive_bg: Color, - pub playing_inactive_fg: Color, - pub active_bg: Color, - pub active_fg: Color, - pub inactive_bg: Color, - pub inactive_fg: Color, - pub active_selected_bg: Color, - pub active_in_range_bg: Color, - pub link_bright: [(u8, u8, u8); 5], - pub link_dim: [(u8, u8, u8); 5], -} - -#[derive(Clone)] -pub struct HeaderColors { - pub tempo_bg: Color, - pub tempo_fg: Color, - pub bank_bg: Color, - pub bank_fg: Color, - pub pattern_bg: Color, - pub pattern_fg: Color, - pub stats_bg: Color, - pub stats_fg: Color, -} - -#[derive(Clone)] -pub struct ModalColors { - pub border: Color, - pub border_accent: Color, - pub border_warn: Color, - pub border_dim: Color, - pub confirm: Color, - pub rename: Color, - pub input: Color, - pub editor: Color, - pub preview: Color, -} - -#[derive(Clone)] -pub struct FlashColors { - pub error_bg: Color, - pub error_fg: Color, - pub success_bg: Color, - pub success_fg: Color, - pub info_bg: Color, - pub info_fg: Color, - pub event_rgb: (u8, u8, u8), -} - -#[derive(Clone)] -pub struct ListColors { - pub playing_bg: Color, - pub playing_fg: Color, - pub staged_play_bg: Color, - pub staged_play_fg: Color, - pub staged_stop_bg: Color, - pub staged_stop_fg: Color, - pub edit_bg: Color, - pub edit_fg: Color, - pub hover_bg: Color, - pub hover_fg: Color, -} - -#[derive(Clone)] -pub struct LinkStatusColors { - pub disabled: Color, - pub connected: Color, - pub listening: Color, -} - -#[derive(Clone)] -pub struct SyntaxColors { - pub gap_bg: Color, - pub executed_bg: Color, - pub selected_bg: Color, - pub emit: (Color, Color), - pub number: (Color, Color), - pub string: (Color, Color), - pub comment: (Color, Color), - pub keyword: (Color, Color), - pub stack_op: (Color, Color), - pub operator: (Color, Color), - pub sound: (Color, Color), - pub param: (Color, Color), - pub context: (Color, Color), - pub note: (Color, Color), - pub interval: (Color, Color), - pub variable: (Color, Color), - pub vary: (Color, Color), - pub generator: (Color, Color), - pub default: (Color, Color), -} - -#[derive(Clone)] -pub struct TableColors { - pub row_even: Color, - pub row_odd: Color, -} - -#[derive(Clone)] -pub struct ValuesColors { - pub tempo: Color, - pub value: Color, -} - -#[derive(Clone)] -pub struct HintColors { - pub key: Color, - pub text: Color, -} - -#[derive(Clone)] -pub struct ViewBadgeColors { - pub bg: Color, - pub fg: Color, -} - -#[derive(Clone)] -pub struct NavColors { - pub selected_bg: Color, - pub selected_fg: Color, - pub unselected_bg: Color, - pub unselected_fg: Color, -} - -#[derive(Clone)] -pub struct EditorWidgetColors { - pub cursor_bg: Color, - pub cursor_fg: Color, - pub selection_bg: Color, - pub completion_bg: Color, - pub completion_fg: Color, - pub completion_selected: Color, - pub completion_example: Color, -} - -#[derive(Clone)] -pub struct BrowserColors { - pub directory: Color, - pub project_file: Color, - pub selected: Color, - pub file: Color, - pub focused_border: Color, - pub unfocused_border: Color, - pub root: Color, - pub file_icon: Color, - pub folder_icon: Color, - pub empty_text: Color, -} - -#[derive(Clone)] -pub struct InputColors { - pub text: Color, - pub cursor: Color, - pub hint: Color, -} - -#[derive(Clone)] -pub struct SearchColors { - pub active: Color, - pub inactive: Color, - pub match_bg: Color, - pub match_fg: Color, -} - -#[derive(Clone)] -pub struct MarkdownColors { - pub h1: Color, - pub h2: Color, - pub h3: Color, - pub code: Color, - pub code_border: Color, - pub link: Color, - pub link_url: Color, - pub quote: Color, - pub text: Color, - pub list: Color, -} - -#[derive(Clone)] -pub struct EngineColors { - pub header: Color, - pub header_focused: Color, - pub divider: Color, - pub scroll_indicator: Color, - pub label: Color, - pub label_focused: Color, - pub label_dim: Color, - pub value: Color, - pub focused: Color, - pub normal: Color, - pub dim: Color, - pub path: Color, - pub border_magenta: Color, - pub border_green: Color, - pub border_cyan: Color, - pub separator: Color, - pub hint_active: Color, - pub hint_inactive: Color, -} - -#[derive(Clone)] -pub struct DictColors { - pub word_name: Color, - pub word_bg: Color, - pub alias: Color, - pub stack_sig: Color, - pub description: Color, - pub example: Color, - pub category_focused: Color, - pub category_selected: Color, - pub category_normal: Color, - pub category_dimmed: Color, - pub border_focused: Color, - pub border_normal: Color, - pub header_desc: Color, -} - -#[derive(Clone)] -pub struct TitleColors { - pub big_title: Color, - pub author: Color, - pub link: Color, - pub license: Color, - pub prompt: Color, - pub subtitle: Color, -} - -#[derive(Clone)] -pub struct MeterColors { - pub low: Color, - pub mid: Color, - pub high: Color, - pub low_rgb: (u8, u8, u8), - pub mid_rgb: (u8, u8, u8), - pub high_rgb: (u8, u8, u8), -} - -#[derive(Clone)] -pub struct SparkleColors { - pub colors: [(u8, u8, u8); 5], -} - -#[derive(Clone)] -pub struct ConfirmColors { - pub border: Color, - pub button_selected_bg: Color, - pub button_selected_fg: Color, -} - -impl ThemeColors { - pub fn catppuccin_mocha() -> Self { - // Catppuccin Mocha base palette - 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 _surface2 = Color::Rgb(88, 91, 112); - let overlay0 = Color::Rgb(108, 112, 134); - let overlay1 = Color::Rgb(127, 132, 156); - let _overlay2 = Color::Rgb(147, 153, 178); - let subtext0 = Color::Rgb(166, 173, 200); - let subtext1 = Color::Rgb(186, 194, 222); - let text = Color::Rgb(205, 214, 244); - let _rosewater = Color::Rgb(245, 224, 220); - let _flamingo = Color::Rgb(242, 205, 205); - 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 _sky = Color::Rgb(137, 220, 235); - let sapphire = Color::Rgb(116, 199, 236); - let _blue = Color::Rgb(137, 180, 250); - let lavender = Color::Rgb(180, 190, 254); - - Self { - 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, - 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), // Mauve - (245, 194, 231), // Pink - (250, 179, 135), // Peach - (137, 220, 235), // Sky - (166, 227, 161), // Green - ], - link_dim: [ - (70, 55, 85), // Mauve dimmed - (85, 65, 80), // Pink dimmed - (85, 60, 45), // Peach dimmed - (45, 75, 80), // Sky dimmed - (55, 80, 55), // Green dimmed - ], - }, - 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, - event_rgb: (55, 45, 70), - }, - 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, - }, - 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)), - 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), // Lavender-ish - (250, 179, 135), // Peach - (166, 227, 161), // Green - (245, 194, 231), // Pink - (203, 166, 247), // Mauve - ], - }, - confirm: ConfirmColors { - border: peach, - button_selected_bg: peach, - button_selected_fg: crust, - }, - } - } - - pub fn catppuccin_latte() -> Self { - // Catppuccin Latte base palette (light theme) - 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 _surface2 = Color::Rgb(172, 176, 190); - let overlay0 = Color::Rgb(156, 160, 176); - let overlay1 = Color::Rgb(140, 143, 161); - let _overlay2 = Color::Rgb(124, 127, 147); - let subtext0 = Color::Rgb(108, 111, 133); - let subtext1 = Color::Rgb(92, 95, 119); - let text = Color::Rgb(76, 79, 105); - let _rosewater = Color::Rgb(220, 138, 120); - let _flamingo = Color::Rgb(221, 120, 120); - 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 _sky = Color::Rgb(4, 165, 229); - let sapphire = Color::Rgb(32, 159, 181); - let _blue = Color::Rgb(30, 102, 245); - let lavender = Color::Rgb(114, 135, 253); - - Self { - 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, - 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), // Mauve - (234, 118, 203), // Pink - (254, 100, 11), // Peach - (4, 165, 229), // Sky - (64, 160, 43), // Green - ], - link_dim: [ - (210, 200, 240), // Mauve dimmed - (240, 210, 230), // Pink dimmed - (250, 220, 200), // Peach dimmed - (200, 230, 240), // Sky dimmed - (210, 235, 210), // Green dimmed - ], - }, - 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, - event_rgb: (225, 215, 240), - }, - 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, - }, - 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)), - 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), // Lavender - (254, 100, 11), // Peach - (64, 160, 43), // Green - (234, 118, 203), // Pink - (136, 57, 239), // Mauve - ], - }, - confirm: ConfirmColors { - border: peach, - button_selected_bg: peach, - button_selected_fg: base, - }, - } - } - - pub fn nord() -> Self { - // Nord color palette - let polar_night0 = Color::Rgb(46, 52, 64); // nord0 - let polar_night1 = Color::Rgb(59, 66, 82); // nord1 - let polar_night2 = Color::Rgb(67, 76, 94); // nord2 - let polar_night3 = Color::Rgb(76, 86, 106); // nord3 - let snow_storm0 = Color::Rgb(216, 222, 233); // nord4 - let _snow_storm1 = Color::Rgb(229, 233, 240); // nord5 - let snow_storm2 = Color::Rgb(236, 239, 244); // nord6 - let frost0 = Color::Rgb(143, 188, 187); // nord7 (teal) - let frost1 = Color::Rgb(136, 192, 208); // nord8 (light blue) - let frost2 = Color::Rgb(129, 161, 193); // nord9 (blue) - let _frost3 = Color::Rgb(94, 129, 172); // nord10 (dark blue) - let aurora_red = Color::Rgb(191, 97, 106); // nord11 - let aurora_orange = Color::Rgb(208, 135, 112); // nord12 - let aurora_yellow = Color::Rgb(235, 203, 139); // nord13 - let aurora_green = Color::Rgb(163, 190, 140); // nord14 - let aurora_purple = Color::Rgb(180, 142, 173); // nord15 - - Self { - 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, - 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), // Frost1 - (180, 142, 173), // Aurora purple - (208, 135, 112), // Aurora orange - (143, 188, 187), // Frost0 - (163, 190, 140), // Aurora green - ], - link_dim: [ - (55, 75, 85), // Frost1 dimmed - (70, 60, 70), // Purple dimmed - (75, 55, 50), // Orange dimmed - (55, 75, 75), // Frost0 dimmed - (60, 75, 55), // Green dimmed - ], - }, - 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, - event_rgb: (60, 55, 75), - }, - 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, - }, - 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)), - 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), // Frost1 - (208, 135, 112), // Aurora orange - (163, 190, 140), // Aurora green - (180, 142, 173), // Aurora purple - (235, 203, 139), // Aurora yellow - ], - }, - confirm: ConfirmColors { - border: aurora_orange, - button_selected_bg: aurora_orange, - button_selected_fg: polar_night0, - }, - } - } - - pub fn dracula() -> Self { - // Dracula color palette - 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); - - let darker_bg = Color::Rgb(33, 34, 44); - let lighter_bg = Color::Rgb(55, 57, 70); - - Self { - 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, - 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), // Purple - (255, 121, 198), // Pink - (255, 184, 108), // Orange - (139, 233, 253), // Cyan - (80, 250, 123), // Green - ], - link_dim: [ - (75, 60, 95), // Purple dimmed - (95, 55, 80), // Pink dimmed - (95, 70, 50), // Orange dimmed - (55, 90, 95), // Cyan dimmed - (40, 95, 55), // Green dimmed - ], - }, - 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, - event_rgb: (70, 55, 85), - }, - 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, - }, - 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)), - 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), // Purple - (255, 184, 108), // Orange - (80, 250, 123), // Green - (255, 121, 198), // Pink - (139, 233, 253), // Cyan - ], - }, - confirm: ConfirmColors { - border: orange, - button_selected_bg: orange, - button_selected_fg: background, - }, - } - } - - pub fn gruvbox_dark() -> Self { - // Gruvbox Dark palette - let bg0 = Color::Rgb(40, 40, 40); // #282828 - let bg1 = Color::Rgb(60, 56, 54); // #3c3836 - let bg2 = Color::Rgb(80, 73, 69); // #504945 - let _bg3 = Color::Rgb(102, 92, 84); // #665c54 - let fg = Color::Rgb(235, 219, 178); // #ebdbb2 - let fg2 = Color::Rgb(213, 196, 161); // #d5c4a1 - let fg3 = Color::Rgb(189, 174, 147); // #bdae93 - let fg4 = Color::Rgb(168, 153, 132); // #a89984 - let red = Color::Rgb(251, 73, 52); // #fb4934 - let green = Color::Rgb(184, 187, 38); // #b8bb26 - let yellow = Color::Rgb(250, 189, 47); // #fabd2f - let blue = Color::Rgb(131, 165, 152); // #83a598 - let purple = Color::Rgb(211, 134, 155); // #d3869b - let aqua = Color::Rgb(142, 192, 124); // #8ec07c - let orange = Color::Rgb(254, 128, 25); // #fe8019 - - let darker_bg = Color::Rgb(29, 32, 33); // #1d2021 - - Self { - 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, - 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), // Orange - (211, 134, 155), // Purple - (250, 189, 47), // Yellow - (131, 165, 152), // Blue - (184, 187, 38), // Green - ], - link_dim: [ - (85, 55, 35), // Orange dimmed - (75, 55, 65), // Purple dimmed - (80, 70, 40), // Yellow dimmed - (50, 60, 60), // Blue dimmed - (60, 65, 35), // Green dimmed - ], - }, - 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, - event_rgb: (70, 55, 45), - }, - 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, - }, - 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)), - 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), // Yellow - (254, 128, 25), // Orange - (184, 187, 38), // Green - (211, 134, 155), // Purple - (131, 165, 152), // Blue - ], - }, - confirm: ConfirmColors { - border: orange, - button_selected_bg: orange, - button_selected_fg: bg0, - }, - } - } - - pub fn monokai() -> Self { - // Monokai palette - let bg = Color::Rgb(39, 40, 34); // #272822 - let bg_light = Color::Rgb(53, 54, 47); // #35362f - let bg_lighter = Color::Rgb(70, 71, 62); - let fg = Color::Rgb(248, 248, 242); // #f8f8f2 - let fg_dim = Color::Rgb(190, 190, 180); - let comment = Color::Rgb(117, 113, 94); // #75715e - let pink = Color::Rgb(249, 38, 114); // #f92672 - let green = Color::Rgb(166, 226, 46); // #a6e22e - let yellow = Color::Rgb(230, 219, 116); // #e6db74 - let blue = Color::Rgb(102, 217, 239); // #66d9ef - let purple = Color::Rgb(174, 129, 255); // #ae81ff - let orange = Color::Rgb(253, 151, 31); // #fd971f - - let darker_bg = Color::Rgb(30, 31, 26); - - Self { - 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, - 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), // Pink - (174, 129, 255), // Purple - (253, 151, 31), // Orange - (102, 217, 239), // Blue - (166, 226, 46), // Green - ], - link_dim: [ - (90, 40, 60), // Pink dimmed - (70, 55, 90), // Purple dimmed - (85, 60, 35), // Orange dimmed - (50, 75, 85), // Blue dimmed - (60, 80, 40), // Green dimmed - ], - }, - 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, - event_rgb: (70, 55, 70), - }, - 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, - }, - 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)), - 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), // Blue - (253, 151, 31), // Orange - (166, 226, 46), // Green - (249, 38, 114), // Pink - (174, 129, 255), // Purple - ], - }, - confirm: ConfirmColors { - border: orange, - button_selected_bg: orange, - button_selected_fg: bg, - }, - } - } - - pub fn pitch_black() -> Self { - // Pitch Black (OLED) palette - pure black background with high contrast - let bg = Color::Rgb(0, 0, 0); // Pure black - let surface = Color::Rgb(10, 10, 10); // Very subtle surface - let surface2 = Color::Rgb(21, 21, 21); // Slightly visible surface - let border = Color::Rgb(40, 40, 40); // Subtle borders - let fg = Color::Rgb(230, 230, 230); // Bright white text - let fg_dim = Color::Rgb(160, 160, 160); - let fg_muted = Color::Rgb(100, 100, 100); - - // High contrast accent colors - 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); - - Self { - 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, - 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), // Cyan - (200, 120, 255), // Purple - (255, 160, 60), // Orange - (80, 180, 255), // Blue - (80, 255, 120), // Green - ], - link_dim: [ - (25, 60, 60), // Cyan dimmed - (50, 35, 65), // Purple dimmed - (60, 45, 20), // Orange dimmed - (25, 50, 70), // Blue dimmed - (25, 65, 35), // Green dimmed - ], - }, - 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, - event_rgb: (40, 30, 50), - }, - 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, - }, - 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)), - 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), // Cyan - (255, 160, 60), // Orange - (80, 255, 120), // Green - (200, 120, 255), // Purple - (80, 180, 255), // Blue - ], - }, - confirm: ConfirmColors { - border: orange, - button_selected_bg: orange, - button_selected_fg: bg, - }, - } - } -} - -// Backward-compatible module aliases that delegate to get() -// These allow existing code to work during migration - -pub mod ui { - use super::*; - pub fn bg() -> Color { - get().ui.bg - } - pub fn bg_rgb() -> (u8, u8, u8) { - get().ui.bg_rgb - } - pub fn text_primary() -> Color { - get().ui.text_primary - } - pub fn text_muted() -> Color { - get().ui.text_muted - } - pub fn text_dim() -> Color { - get().ui.text_dim - } - pub fn border() -> Color { - get().ui.border - } - pub fn header() -> Color { - get().ui.header - } - pub fn unfocused() -> Color { - get().ui.unfocused - } - pub fn accent() -> Color { - get().ui.accent - } - pub fn surface() -> Color { - get().ui.surface - } - - // Constants for backward compatibility - pub const BG: Color = Color::Rgb(30, 30, 46); - pub const BG_RGB: (u8, u8, u8) = (30, 30, 46); - pub const TEXT_PRIMARY: Color = Color::Rgb(205, 214, 244); - pub const TEXT_MUTED: Color = Color::Rgb(166, 173, 200); - pub const TEXT_DIM: Color = Color::Rgb(127, 132, 156); - pub const BORDER: Color = Color::Rgb(69, 71, 90); - pub const HEADER: Color = Color::Rgb(180, 190, 254); - pub const UNFOCUSED: Color = Color::Rgb(108, 112, 134); - pub const ACCENT: Color = Color::Rgb(203, 166, 247); - pub const SURFACE: Color = Color::Rgb(49, 50, 68); -} - -pub mod status { - use super::*; - pub const PLAYING_BG: Color = Color::Rgb(30, 50, 40); - pub const PLAYING_FG: Color = Color::Rgb(166, 227, 161); - pub const STOPPED_BG: Color = Color::Rgb(50, 30, 40); - pub const STOPPED_FG: Color = Color::Rgb(243, 139, 168); - pub const FILL_ON: Color = Color::Rgb(166, 227, 161); - pub const FILL_OFF: Color = Color::Rgb(108, 112, 134); - pub const FILL_BG: Color = Color::Rgb(49, 50, 68); -} - -pub mod selection { - use super::*; - pub const CURSOR_BG: Color = Color::Rgb(203, 166, 247); - pub const CURSOR_FG: Color = Color::Rgb(17, 17, 27); - pub const SELECTED_BG: Color = Color::Rgb(60, 60, 90); - pub const SELECTED_FG: Color = Color::Rgb(180, 190, 254); - pub const IN_RANGE_BG: Color = Color::Rgb(50, 50, 75); - pub const IN_RANGE_FG: Color = Color::Rgb(186, 194, 222); - pub const CURSOR: Color = CURSOR_BG; - pub const SELECTED: Color = SELECTED_BG; - pub const IN_RANGE: Color = IN_RANGE_BG; -} - -pub mod tile { - use super::*; - pub const PLAYING_ACTIVE_BG: Color = Color::Rgb(80, 50, 60); - pub const PLAYING_ACTIVE_FG: Color = Color::Rgb(250, 179, 135); - pub const PLAYING_INACTIVE_BG: Color = Color::Rgb(70, 55, 45); - pub const PLAYING_INACTIVE_FG: Color = Color::Rgb(249, 226, 175); - pub const ACTIVE_BG: Color = Color::Rgb(40, 55, 55); - pub const ACTIVE_FG: Color = Color::Rgb(148, 226, 213); - pub const INACTIVE_BG: Color = Color::Rgb(49, 50, 68); - pub const INACTIVE_FG: Color = Color::Rgb(166, 173, 200); - pub const ACTIVE_SELECTED_BG: Color = Color::Rgb(70, 60, 80); - pub const ACTIVE_IN_RANGE_BG: Color = Color::Rgb(55, 55, 70); - pub const LINK_BRIGHT: [(u8, u8, u8); 5] = [ - (203, 166, 247), - (245, 194, 231), - (250, 179, 135), - (137, 220, 235), - (166, 227, 161), - ]; - pub const LINK_DIM: [(u8, u8, u8); 5] = [ - (70, 55, 85), - (85, 65, 80), - (85, 60, 45), - (45, 75, 80), - (55, 80, 55), - ]; -} - -pub mod header { - use super::*; - pub const TEMPO_BG: Color = Color::Rgb(50, 40, 60); - pub const TEMPO_FG: Color = Color::Rgb(203, 166, 247); - pub const BANK_BG: Color = Color::Rgb(35, 50, 55); - pub const BANK_FG: Color = Color::Rgb(116, 199, 236); - pub const PATTERN_BG: Color = Color::Rgb(40, 50, 50); - pub const PATTERN_FG: Color = Color::Rgb(148, 226, 213); - pub const STATS_BG: Color = Color::Rgb(49, 50, 68); - pub const STATS_FG: Color = Color::Rgb(166, 173, 200); -} - -pub mod modal { - use super::*; - pub const BORDER: Color = Color::Rgb(180, 190, 254); - pub const BORDER_ACCENT: Color = Color::Rgb(203, 166, 247); - pub const BORDER_WARN: Color = Color::Rgb(250, 179, 135); - pub const BORDER_DIM: Color = Color::Rgb(127, 132, 156); - pub const CONFIRM: Color = Color::Rgb(250, 179, 135); - pub const RENAME: Color = Color::Rgb(203, 166, 247); - pub const INPUT: Color = Color::Rgb(116, 199, 236); - pub const EDITOR: Color = Color::Rgb(180, 190, 254); - pub const PREVIEW: Color = Color::Rgb(127, 132, 156); -} - -pub mod flash { - use super::*; - pub const ERROR_BG: Color = Color::Rgb(50, 30, 40); - pub const ERROR_FG: Color = Color::Rgb(243, 139, 168); - pub const SUCCESS_BG: Color = Color::Rgb(30, 50, 40); - pub const SUCCESS_FG: Color = Color::Rgb(166, 227, 161); - pub const INFO_BG: Color = Color::Rgb(49, 50, 68); - pub const INFO_FG: Color = Color::Rgb(205, 214, 244); - pub const EVENT_RGB: (u8, u8, u8) = (55, 45, 70); -} - -pub mod list { - use super::*; - pub const PLAYING_BG: Color = Color::Rgb(35, 55, 45); - pub const PLAYING_FG: Color = Color::Rgb(166, 227, 161); - pub const STAGED_PLAY_BG: Color = Color::Rgb(55, 45, 65); - pub const STAGED_PLAY_FG: Color = Color::Rgb(203, 166, 247); - pub const STAGED_STOP_BG: Color = Color::Rgb(60, 40, 50); - pub const STAGED_STOP_FG: Color = Color::Rgb(235, 160, 172); - pub const EDIT_BG: Color = Color::Rgb(40, 55, 55); - pub const EDIT_FG: Color = Color::Rgb(148, 226, 213); - pub const HOVER_BG: Color = Color::Rgb(69, 71, 90); - pub const HOVER_FG: Color = Color::Rgb(205, 214, 244); -} - -pub mod link_status { - use super::*; - pub const DISABLED: Color = Color::Rgb(243, 139, 168); - pub const CONNECTED: Color = Color::Rgb(166, 227, 161); - pub const LISTENING: Color = Color::Rgb(249, 226, 175); -} - -pub mod syntax { - use super::*; - pub const GAP_BG: Color = Color::Rgb(24, 24, 37); - pub const EXECUTED_BG: Color = Color::Rgb(45, 40, 55); - pub const SELECTED_BG: Color = Color::Rgb(70, 55, 40); - pub const EMIT: (Color, Color) = (Color::Rgb(205, 214, 244), Color::Rgb(80, 50, 60)); - pub const NUMBER: (Color, Color) = (Color::Rgb(250, 179, 135), Color::Rgb(55, 45, 35)); - pub const STRING: (Color, Color) = (Color::Rgb(166, 227, 161), Color::Rgb(35, 50, 40)); - pub const COMMENT: (Color, Color) = (Color::Rgb(127, 132, 156), Color::Rgb(17, 17, 27)); - pub const KEYWORD: (Color, Color) = (Color::Rgb(203, 166, 247), Color::Rgb(50, 40, 60)); - pub const STACK_OP: (Color, Color) = (Color::Rgb(116, 199, 236), Color::Rgb(35, 45, 55)); - pub const OPERATOR: (Color, Color) = (Color::Rgb(249, 226, 175), Color::Rgb(55, 50, 35)); - pub const SOUND: (Color, Color) = (Color::Rgb(148, 226, 213), Color::Rgb(35, 55, 55)); - pub const PARAM: (Color, Color) = (Color::Rgb(180, 190, 254), Color::Rgb(45, 45, 60)); - pub const CONTEXT: (Color, Color) = (Color::Rgb(250, 179, 135), Color::Rgb(55, 45, 35)); - pub const NOTE: (Color, Color) = (Color::Rgb(166, 227, 161), Color::Rgb(35, 50, 40)); - pub const INTERVAL: (Color, Color) = (Color::Rgb(180, 230, 150), Color::Rgb(40, 55, 35)); - pub const VARIABLE: (Color, Color) = (Color::Rgb(245, 194, 231), Color::Rgb(55, 40, 55)); - pub const VARY: (Color, Color) = (Color::Rgb(249, 226, 175), Color::Rgb(55, 50, 35)); - pub const GENERATOR: (Color, Color) = (Color::Rgb(148, 226, 213), Color::Rgb(35, 55, 50)); - pub const DEFAULT: (Color, Color) = (Color::Rgb(166, 173, 200), Color::Rgb(24, 24, 37)); -} - -pub mod table { - use super::*; - pub const ROW_EVEN: Color = Color::Rgb(24, 24, 37); - pub const ROW_ODD: Color = Color::Rgb(30, 30, 46); -} - -pub mod values { - use super::*; - pub const TEMPO: Color = Color::Rgb(250, 179, 135); - pub const VALUE: Color = Color::Rgb(166, 173, 200); -} - -pub mod hint { - use super::*; - pub const KEY: Color = Color::Rgb(250, 179, 135); - pub const TEXT: Color = Color::Rgb(127, 132, 156); -} - -pub mod view_badge { - use super::*; - pub const BG: Color = Color::Rgb(205, 214, 244); - pub const FG: Color = Color::Rgb(17, 17, 27); -} - -pub mod nav { - use super::*; - pub const SELECTED_BG: Color = Color::Rgb(60, 50, 75); - pub const SELECTED_FG: Color = Color::Rgb(205, 214, 244); - pub const UNSELECTED_BG: Color = Color::Rgb(49, 50, 68); - pub const UNSELECTED_FG: Color = Color::Rgb(127, 132, 156); -} - -pub mod editor_widget { - use super::*; - pub const CURSOR_BG: Color = Color::Rgb(205, 214, 244); - pub const CURSOR_FG: Color = Color::Rgb(17, 17, 27); - pub const SELECTION_BG: Color = Color::Rgb(50, 60, 90); - pub const COMPLETION_BG: Color = Color::Rgb(49, 50, 68); - pub const COMPLETION_FG: Color = Color::Rgb(205, 214, 244); - pub const COMPLETION_SELECTED: Color = Color::Rgb(250, 179, 135); - pub const COMPLETION_EXAMPLE: Color = Color::Rgb(148, 226, 213); -} - -pub mod browser { - use super::*; - pub const DIRECTORY: Color = Color::Rgb(116, 199, 236); - pub const PROJECT_FILE: Color = Color::Rgb(203, 166, 247); - pub const SELECTED: Color = Color::Rgb(250, 179, 135); - pub const FILE: Color = Color::Rgb(205, 214, 244); - pub const FOCUSED_BORDER: Color = Color::Rgb(250, 179, 135); - pub const UNFOCUSED_BORDER: Color = Color::Rgb(108, 112, 134); - pub const ROOT: Color = Color::Rgb(205, 214, 244); - pub const FILE_ICON: Color = Color::Rgb(127, 132, 156); - pub const FOLDER_ICON: Color = Color::Rgb(116, 199, 236); - pub const EMPTY_TEXT: Color = Color::Rgb(127, 132, 156); -} - -pub mod input { - use super::*; - pub const TEXT: Color = Color::Rgb(116, 199, 236); - pub const CURSOR: Color = Color::Rgb(205, 214, 244); - pub const HINT: Color = Color::Rgb(127, 132, 156); -} - -pub mod search { - use super::*; - pub const ACTIVE: Color = Color::Rgb(250, 179, 135); - pub const INACTIVE: Color = Color::Rgb(108, 112, 134); - pub const MATCH_BG: Color = Color::Rgb(249, 226, 175); - pub const MATCH_FG: Color = Color::Rgb(17, 17, 27); -} - -pub mod markdown { - use super::*; - pub const H1: Color = Color::Rgb(116, 199, 236); - pub const H2: Color = Color::Rgb(250, 179, 135); - pub const H3: Color = Color::Rgb(203, 166, 247); - pub const CODE: Color = Color::Rgb(166, 227, 161); - pub const CODE_BORDER: Color = Color::Rgb(60, 60, 70); - pub const LINK: Color = Color::Rgb(148, 226, 213); - pub const LINK_URL: Color = Color::Rgb(100, 100, 100); - pub const QUOTE: Color = Color::Rgb(127, 132, 156); - pub const TEXT: Color = Color::Rgb(205, 214, 244); - pub const LIST: Color = Color::Rgb(205, 214, 244); -} - -pub mod engine { - use super::*; - pub const HEADER: Color = Color::Rgb(100, 160, 180); - pub const HEADER_FOCUSED: Color = Color::Rgb(249, 226, 175); - pub const DIVIDER: Color = Color::Rgb(60, 65, 70); - pub const SCROLL_INDICATOR: Color = Color::Rgb(80, 85, 95); - pub const LABEL: Color = Color::Rgb(120, 125, 135); - pub const LABEL_FOCUSED: Color = Color::Rgb(150, 155, 165); - pub const LABEL_DIM: Color = Color::Rgb(100, 105, 115); - pub const VALUE: Color = Color::Rgb(180, 180, 190); - pub const FOCUSED: Color = Color::Rgb(249, 226, 175); - pub const NORMAL: Color = Color::Rgb(205, 214, 244); - pub const DIM: Color = Color::Rgb(80, 85, 95); - pub const PATH: Color = Color::Rgb(120, 125, 135); - pub const BORDER_MAGENTA: Color = Color::Rgb(203, 166, 247); - pub const BORDER_GREEN: Color = Color::Rgb(166, 227, 161); - pub const BORDER_CYAN: Color = Color::Rgb(116, 199, 236); - pub const SEPARATOR: Color = Color::Rgb(60, 65, 75); - pub const HINT_ACTIVE: Color = Color::Rgb(180, 180, 100); - pub const HINT_INACTIVE: Color = Color::Rgb(60, 60, 70); -} - -pub mod dict { - use super::*; - pub const WORD_NAME: Color = Color::Rgb(166, 227, 161); - pub const WORD_BG: Color = Color::Rgb(40, 50, 60); - pub const ALIAS: Color = Color::Rgb(127, 132, 156); - pub const STACK_SIG: Color = Color::Rgb(203, 166, 247); - pub const DESCRIPTION: Color = Color::Rgb(205, 214, 244); - pub const EXAMPLE: Color = Color::Rgb(120, 130, 140); - pub const CATEGORY_FOCUSED: Color = Color::Rgb(249, 226, 175); - pub const CATEGORY_SELECTED: Color = Color::Rgb(116, 199, 236); - pub const CATEGORY_NORMAL: Color = Color::Rgb(205, 214, 244); - pub const CATEGORY_DIMMED: Color = Color::Rgb(80, 80, 90); - pub const BORDER_FOCUSED: Color = Color::Rgb(249, 226, 175); - pub const BORDER_NORMAL: Color = Color::Rgb(60, 60, 70); - pub const HEADER_DESC: Color = Color::Rgb(140, 145, 155); -} - -pub mod title { - use super::*; - pub const BIG_TITLE: Color = Color::Rgb(203, 166, 247); - pub const AUTHOR: Color = Color::Rgb(180, 190, 254); - pub const LINK: Color = Color::Rgb(148, 226, 213); - pub const LICENSE: Color = Color::Rgb(250, 179, 135); - pub const PROMPT: Color = Color::Rgb(140, 160, 170); - pub const SUBTITLE: Color = Color::Rgb(205, 214, 244); -} - -pub mod meter { - use super::*; - pub const LOW: Color = Color::Rgb(166, 227, 161); - pub const MID: Color = Color::Rgb(249, 226, 175); - pub const HIGH: Color = Color::Rgb(243, 139, 168); - pub const LOW_RGB: (u8, u8, u8) = (40, 180, 80); - pub const MID_RGB: (u8, u8, u8) = (220, 180, 40); - pub const HIGH_RGB: (u8, u8, u8) = (220, 60, 40); -} - -pub mod sparkle { - pub const COLORS: &[(u8, u8, u8)] = &[ - (200, 220, 255), - (250, 179, 135), - (166, 227, 161), - (245, 194, 231), - (203, 166, 247), - ]; -} - -pub mod confirm { - use super::*; - pub const BORDER: Color = Color::Rgb(250, 179, 135); - pub const BUTTON_SELECTED_BG: Color = Color::Rgb(250, 179, 135); - pub const BUTTON_SELECTED_FG: Color = Color::Rgb(17, 17, 27); -} diff --git a/crates/ratatui/src/theme/catppuccin_latte.rs b/crates/ratatui/src/theme/catppuccin_latte.rs new file mode 100644 index 0000000..3af9f8b --- /dev/null +++ b/crates/ratatui/src/theme/catppuccin_latte.rs @@ -0,0 +1,282 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (225, 215, 240), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/catppuccin_mocha.rs b/crates/ratatui/src/theme/catppuccin_mocha.rs new file mode 100644 index 0000000..349e3f1 --- /dev/null +++ b/crates/ratatui/src/theme/catppuccin_mocha.rs @@ -0,0 +1,285 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (55, 45, 70), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/dracula.rs b/crates/ratatui/src/theme/dracula.rs new file mode 100644 index 0000000..681e58d --- /dev/null +++ b/crates/ratatui/src/theme/dracula.rs @@ -0,0 +1,279 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (70, 55, 85), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/gruvbox_dark.rs b/crates/ratatui/src/theme/gruvbox_dark.rs new file mode 100644 index 0000000..4accfc5 --- /dev/null +++ b/crates/ratatui/src/theme/gruvbox_dark.rs @@ -0,0 +1,278 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (70, 55, 45), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/kanagawa.rs b/crates/ratatui/src/theme/kanagawa.rs new file mode 100644 index 0000000..b7512ef --- /dev/null +++ b/crates/ratatui/src/theme/kanagawa.rs @@ -0,0 +1,278 @@ +use super::*; +use ratatui::style::Color; + +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(226, 109, 115); + let sakura_pink = Color::Rgb(212, 140, 149); + + 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, + 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: [ + (226, 109, 115), + (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, + event_rgb: (50, 50, 60), + }, + 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, + }, + 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)), + 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: (226, 109, 115), + }, + sparkle: SparkleColors { + colors: [ + (127, 180, 202), + (230, 195, 132), + (118, 148, 106), + (226, 109, 115), + (149, 127, 184), + ], + }, + confirm: ConfirmColors { + border: carp_yellow, + button_selected_bg: carp_yellow, + button_selected_fg: bg, + }, + } +} diff --git a/crates/ratatui/src/theme/mod.rs b/crates/ratatui/src/theme/mod.rs new file mode 100644 index 0000000..9e5bc25 --- /dev/null +++ b/crates/ratatui/src/theme/mod.rs @@ -0,0 +1,373 @@ +//! Centralized color definitions for Cagire TUI. +//! Supports multiple color schemes with runtime switching. + +mod catppuccin_latte; +mod catppuccin_mocha; +mod dracula; +mod gruvbox_dark; +mod kanagawa; +mod monochrome_black; +mod monochrome_white; +mod monokai; +mod nord; +mod pitch_black; +mod rose_pine; +mod tokyo_night; + +use ratatui::style::Color; +use std::cell::RefCell; + +pub struct ThemeEntry { + pub id: &'static str, + pub label: &'static str, + pub colors: fn() -> ThemeColors, +} + +pub const THEMES: &[ThemeEntry] = &[ + 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 }, +]; + +thread_local! { + static CURRENT_THEME: RefCell = RefCell::new((THEMES[0].colors)()); +} + +pub fn get() -> ThemeColors { + CURRENT_THEME.with(|t| t.borrow().clone()) +} + +pub fn set(theme: ThemeColors) { + CURRENT_THEME.with(|t| *t.borrow_mut() = theme); +} + +#[derive(Clone)] +pub struct ThemeColors { + pub ui: UiColors, + pub status: StatusColors, + pub selection: SelectionColors, + pub tile: TileColors, + pub header: HeaderColors, + pub modal: ModalColors, + pub flash: FlashColors, + pub list: ListColors, + pub link_status: LinkStatusColors, + pub syntax: SyntaxColors, + pub table: TableColors, + pub values: ValuesColors, + pub hint: HintColors, + pub view_badge: ViewBadgeColors, + pub nav: NavColors, + pub editor_widget: EditorWidgetColors, + pub browser: BrowserColors, + pub input: InputColors, + pub search: SearchColors, + pub markdown: MarkdownColors, + pub engine: EngineColors, + pub dict: DictColors, + pub title: TitleColors, + pub meter: MeterColors, + pub sparkle: SparkleColors, + pub confirm: ConfirmColors, +} + +#[derive(Clone)] +pub struct UiColors { + pub bg: Color, + pub bg_rgb: (u8, u8, u8), + pub text_primary: Color, + pub text_muted: Color, + pub text_dim: Color, + pub border: Color, + pub header: Color, + pub unfocused: Color, + pub accent: Color, + pub surface: Color, +} + +#[derive(Clone)] +pub struct StatusColors { + pub playing_bg: Color, + pub playing_fg: Color, + pub stopped_bg: Color, + pub stopped_fg: Color, + pub fill_on: Color, + pub fill_off: Color, + pub fill_bg: Color, +} + +#[derive(Clone)] +pub struct SelectionColors { + pub cursor_bg: Color, + pub cursor_fg: Color, + pub selected_bg: Color, + pub selected_fg: Color, + pub in_range_bg: Color, + pub in_range_fg: Color, + pub cursor: Color, + pub selected: Color, + pub in_range: Color, +} + +#[derive(Clone)] +pub struct TileColors { + pub playing_active_bg: Color, + pub playing_active_fg: Color, + pub playing_inactive_bg: Color, + pub playing_inactive_fg: Color, + pub active_bg: Color, + pub active_fg: Color, + pub inactive_bg: Color, + pub inactive_fg: Color, + pub active_selected_bg: Color, + pub active_in_range_bg: Color, + pub link_bright: [(u8, u8, u8); 5], + pub link_dim: [(u8, u8, u8); 5], +} + +#[derive(Clone)] +pub struct HeaderColors { + pub tempo_bg: Color, + pub tempo_fg: Color, + pub bank_bg: Color, + pub bank_fg: Color, + pub pattern_bg: Color, + pub pattern_fg: Color, + pub stats_bg: Color, + pub stats_fg: Color, +} + +#[derive(Clone)] +pub struct ModalColors { + pub border: Color, + pub border_accent: Color, + pub border_warn: Color, + pub border_dim: Color, + pub confirm: Color, + pub rename: Color, + pub input: Color, + pub editor: Color, + pub preview: Color, +} + +#[derive(Clone)] +pub struct FlashColors { + pub error_bg: Color, + pub error_fg: Color, + pub success_bg: Color, + pub success_fg: Color, + pub info_bg: Color, + pub info_fg: Color, + pub event_rgb: (u8, u8, u8), +} + +#[derive(Clone)] +pub struct ListColors { + pub playing_bg: Color, + pub playing_fg: Color, + pub staged_play_bg: Color, + pub staged_play_fg: Color, + pub staged_stop_bg: Color, + pub staged_stop_fg: Color, + pub edit_bg: Color, + pub edit_fg: Color, + pub hover_bg: Color, + pub hover_fg: Color, +} + +#[derive(Clone)] +pub struct LinkStatusColors { + pub disabled: Color, + pub connected: Color, + pub listening: Color, +} + +#[derive(Clone)] +pub struct SyntaxColors { + pub gap_bg: Color, + pub executed_bg: Color, + pub selected_bg: Color, + pub emit: (Color, Color), + pub number: (Color, Color), + pub string: (Color, Color), + pub comment: (Color, Color), + pub keyword: (Color, Color), + pub stack_op: (Color, Color), + pub operator: (Color, Color), + pub sound: (Color, Color), + pub param: (Color, Color), + pub context: (Color, Color), + pub note: (Color, Color), + pub interval: (Color, Color), + pub variable: (Color, Color), + pub vary: (Color, Color), + pub generator: (Color, Color), + pub default: (Color, Color), +} + +#[derive(Clone)] +pub struct TableColors { + pub row_even: Color, + pub row_odd: Color, +} + +#[derive(Clone)] +pub struct ValuesColors { + pub tempo: Color, + pub value: Color, +} + +#[derive(Clone)] +pub struct HintColors { + pub key: Color, + pub text: Color, +} + +#[derive(Clone)] +pub struct ViewBadgeColors { + pub bg: Color, + pub fg: Color, +} + +#[derive(Clone)] +pub struct NavColors { + pub selected_bg: Color, + pub selected_fg: Color, + pub unselected_bg: Color, + pub unselected_fg: Color, +} + +#[derive(Clone)] +pub struct EditorWidgetColors { + pub cursor_bg: Color, + pub cursor_fg: Color, + pub selection_bg: Color, + pub completion_bg: Color, + pub completion_fg: Color, + pub completion_selected: Color, + pub completion_example: Color, +} + +#[derive(Clone)] +pub struct BrowserColors { + pub directory: Color, + pub project_file: Color, + pub selected: Color, + pub file: Color, + pub focused_border: Color, + pub unfocused_border: Color, + pub root: Color, + pub file_icon: Color, + pub folder_icon: Color, + pub empty_text: Color, +} + +#[derive(Clone)] +pub struct InputColors { + pub text: Color, + pub cursor: Color, + pub hint: Color, +} + +#[derive(Clone)] +pub struct SearchColors { + pub active: Color, + pub inactive: Color, + pub match_bg: Color, + pub match_fg: Color, +} + +#[derive(Clone)] +pub struct MarkdownColors { + pub h1: Color, + pub h2: Color, + pub h3: Color, + pub code: Color, + pub code_border: Color, + pub link: Color, + pub link_url: Color, + pub quote: Color, + pub text: Color, + pub list: Color, +} + +#[derive(Clone)] +pub struct EngineColors { + pub header: Color, + pub header_focused: Color, + pub divider: Color, + pub scroll_indicator: Color, + pub label: Color, + pub label_focused: Color, + pub label_dim: Color, + pub value: Color, + pub focused: Color, + pub normal: Color, + pub dim: Color, + pub path: Color, + pub border_magenta: Color, + pub border_green: Color, + pub border_cyan: Color, + pub separator: Color, + pub hint_active: Color, + pub hint_inactive: Color, +} + +#[derive(Clone)] +pub struct DictColors { + pub word_name: Color, + pub word_bg: Color, + pub alias: Color, + pub stack_sig: Color, + pub description: Color, + pub example: Color, + pub category_focused: Color, + pub category_selected: Color, + pub category_normal: Color, + pub category_dimmed: Color, + pub border_focused: Color, + pub border_normal: Color, + pub header_desc: Color, +} + +#[derive(Clone)] +pub struct TitleColors { + pub big_title: Color, + pub author: Color, + pub link: Color, + pub license: Color, + pub prompt: Color, + pub subtitle: Color, +} + +#[derive(Clone)] +pub struct MeterColors { + pub low: Color, + pub mid: Color, + pub high: Color, + pub low_rgb: (u8, u8, u8), + pub mid_rgb: (u8, u8, u8), + pub high_rgb: (u8, u8, u8), +} + +#[derive(Clone)] +pub struct SparkleColors { + pub colors: [(u8, u8, u8); 5], +} + +#[derive(Clone)] +pub struct ConfirmColors { + pub border: Color, + pub button_selected_bg: Color, + pub button_selected_fg: Color, +} + diff --git a/crates/ratatui/src/theme/monochrome_black.rs b/crates/ratatui/src/theme/monochrome_black.rs new file mode 100644 index 0000000..9930a7a --- /dev/null +++ b/crates/ratatui/src/theme/monochrome_black.rs @@ -0,0 +1,275 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (40, 40, 40), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/monochrome_white.rs b/crates/ratatui/src/theme/monochrome_white.rs new file mode 100644 index 0000000..d19ce72 --- /dev/null +++ b/crates/ratatui/src/theme/monochrome_white.rs @@ -0,0 +1,275 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (220, 220, 220), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/monokai.rs b/crates/ratatui/src/theme/monokai.rs new file mode 100644 index 0000000..42705f9 --- /dev/null +++ b/crates/ratatui/src/theme/monokai.rs @@ -0,0 +1,276 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (70, 55, 70), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/nord.rs b/crates/ratatui/src/theme/nord.rs new file mode 100644 index 0000000..2e06828 --- /dev/null +++ b/crates/ratatui/src/theme/nord.rs @@ -0,0 +1,279 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (60, 55, 75), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/pitch_black.rs b/crates/ratatui/src/theme/pitch_black.rs new file mode 100644 index 0000000..de926c9 --- /dev/null +++ b/crates/ratatui/src/theme/pitch_black.rs @@ -0,0 +1,277 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (40, 30, 50), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/rose_pine.rs b/crates/ratatui/src/theme/rose_pine.rs new file mode 100644 index 0000000..52b9e62 --- /dev/null +++ b/crates/ratatui/src/theme/rose_pine.rs @@ -0,0 +1,277 @@ +use super::*; +use ratatui::style::Color; + +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, 111, 146); + 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); + + 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, + 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, + event_rgb: (50, 45, 60), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/theme/tokyo_night.rs b/crates/ratatui/src/theme/tokyo_night.rs new file mode 100644 index 0000000..a6b1120 --- /dev/null +++ b/crates/ratatui/src/theme/tokyo_night.rs @@ -0,0 +1,277 @@ +use super::*; +use ratatui::style::Color; + +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); + + 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, + 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, + event_rgb: (55, 50, 70), + }, + 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, + }, + 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)), + 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, + }, + } +} diff --git a/crates/ratatui/src/vu_meter.rs b/crates/ratatui/src/vu_meter.rs index b3d1891..a23076f 100644 --- a/crates/ratatui/src/vu_meter.rs +++ b/crates/ratatui/src/vu_meter.rs @@ -1,4 +1,4 @@ -use crate::theme::meter; +use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Color; @@ -30,13 +30,13 @@ impl VuMeter { (db - DB_MIN) / DB_RANGE } - fn row_to_color(row_position: f32) -> Color { + fn row_to_color(row_position: f32, colors: &theme::ThemeColors) -> Color { if row_position > 0.9 { - meter::HIGH + colors.meter.high } else if row_position > 0.75 { - meter::MID + colors.meter.mid } else { - meter::LOW + colors.meter.low } } } @@ -47,6 +47,7 @@ impl Widget for VuMeter { return; } + let colors = theme::get(); let height = area.height as usize; let half_width = area.width / 2; let gap = 1u16; @@ -62,7 +63,7 @@ impl Widget for VuMeter { for row in 0..height { let y = area.y + area.height - 1 - row as u16; let row_position = (row as f32 + 0.5) / height as f32; - let color = Self::row_to_color(row_position); + let color = Self::row_to_color(row_position, &colors); for col in 0..half_width.saturating_sub(gap) { let x = area.x + col; diff --git a/src/app.rs b/src/app.rs index 0402f31..a1a95e8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -120,14 +120,24 @@ impl App { midi: crate::settings::MidiSettings { output_devices: { let outputs = crate::midi::list_midi_outputs(); - self.midi.selected_outputs.iter() - .map(|opt| opt.and_then(|idx| outputs.get(idx).map(|d| d.name.clone())).unwrap_or_default()) + self.midi + .selected_outputs + .iter() + .map(|opt| { + opt.and_then(|idx| outputs.get(idx).map(|d| d.name.clone())) + .unwrap_or_default() + }) .collect() }, input_devices: { let inputs = crate::midi::list_midi_inputs(); - self.midi.selected_inputs.iter() - .map(|opt| opt.and_then(|idx| inputs.get(idx).map(|d| d.name.clone())).unwrap_or_default()) + self.midi + .selected_inputs + .iter() + .map(|opt| { + opt.and_then(|idx| inputs.get(idx).map(|d| d.name.clone())) + .unwrap_or_default() + }) .collect() }, }, @@ -139,6 +149,21 @@ impl App { (self.editor_ctx.bank, self.editor_ctx.pattern) } + fn selected_steps(&self) -> Vec { + match self.editor_ctx.selection_range() { + Some(range) => range.collect(), + None => vec![self.editor_ctx.step], + } + } + + fn annotate_copy_name(name: &Option) -> Option { + match name { + Some(n) if !n.ends_with(" (copy)") => Some(format!("{n} (copy)")), + Some(n) => Some(n.clone()), + None => Some("(copy)".to_string()), + } + } + pub fn mark_all_patterns_dirty(&mut self) { self.project_state.mark_all_dirty(); } @@ -216,17 +241,8 @@ impl App { pub fn toggle_steps(&mut self) { let (bank, pattern) = self.current_bank_pattern(); - let indices: Vec = match self.editor_ctx.selection_range() { - Some(range) => range.collect(), - None => vec![self.editor_ctx.step], - }; - for idx in indices { - pattern_editor::toggle_step( - &mut self.project_state.project, - bank, - pattern, - idx, - ); + for idx in self.selected_steps() { + pattern_editor::toggle_step(&mut self.project_state.project, bank, pattern, idx); } self.project_state.mark_dirty(bank, pattern); } @@ -261,6 +277,37 @@ impl App { self.project_state.mark_dirty(change.bank, change.pattern); } + fn create_step_context(&self, step_idx: usize, link: &LinkState) -> StepContext { + let (bank, pattern) = self.current_bank_pattern(); + let speed = self + .project_state + .project + .pattern_at(bank, pattern) + .speed + .multiplier(); + StepContext { + step: step_idx, + beat: link.beat(), + bank, + pattern, + tempo: link.tempo(), + phase: link.phase(), + slot: 0, + runs: 0, + iter: 0, + speed, + fill: false, + nudge_secs: 0.0, + cc_access: None, + #[cfg(feature = "desktop")] + mouse_x: 0.5, + #[cfg(feature = "desktop")] + mouse_y: 0.5, + #[cfg(feature = "desktop")] + mouse_down: 0.0, + } + } + fn load_step_to_editor(&mut self) { let (bank, pattern) = self.current_bank_pattern(); if let Some(script) = pattern_editor::get_step_script( @@ -310,37 +357,7 @@ impl App { link: &LinkState, audio_tx: &arc_swap::ArcSwap>, ) -> Result<(), String> { - let (bank, pattern) = self.current_bank_pattern(); - let step_idx = self.editor_ctx.step; - let speed = self - .project_state - .project - .pattern_at(bank, pattern) - .speed - .multiplier(); - - let ctx = StepContext { - step: step_idx, - beat: link.beat(), - bank, - pattern, - tempo: link.tempo(), - phase: link.phase(), - slot: 0, - runs: 0, - iter: 0, - speed, - fill: false, - nudge_secs: 0.0, - cc_memory: None, - #[cfg(feature = "desktop")] - mouse_x: 0.5, - #[cfg(feature = "desktop")] - mouse_y: 0.5, - #[cfg(feature = "desktop")] - mouse_down: 0.0, - }; - + let ctx = self.create_step_context(self.editor_ctx.step, link); let cmds = self.script_engine.evaluate(script, &ctx)?; for cmd in cmds { let _ = audio_tx @@ -370,33 +387,7 @@ impl App { return; } - let speed = self - .project_state - .project - .pattern_at(bank, pattern) - .speed - .multiplier(); - let ctx = StepContext { - step: step_idx, - beat: link.beat(), - bank, - pattern, - tempo: link.tempo(), - phase: link.phase(), - slot: 0, - runs: 0, - iter: 0, - speed, - fill: false, - nudge_secs: 0.0, - cc_memory: None, - #[cfg(feature = "desktop")] - mouse_x: 0.5, - #[cfg(feature = "desktop")] - mouse_y: 0.5, - #[cfg(feature = "desktop")] - mouse_down: 0.0, - }; + let ctx = self.create_step_context(step_idx, link); match self.script_engine.evaluate(&script, &ctx) { Ok(cmds) => { @@ -454,33 +445,7 @@ impl App { continue; } - let speed = self - .project_state - .project - .pattern_at(bank, pattern) - .speed - .multiplier(); - let ctx = StepContext { - step: step_idx, - beat: 0.0, - bank, - pattern, - tempo: link.tempo(), - phase: 0.0, - slot: 0, - runs: 0, - iter: 0, - speed, - fill: false, - nudge_secs: 0.0, - cc_memory: None, - #[cfg(feature = "desktop")] - mouse_x: 0.5, - #[cfg(feature = "desktop")] - mouse_y: 0.5, - #[cfg(feature = "desktop")] - mouse_down: 0.0, - }; + let ctx = self.create_step_context(step_idx, link); if let Ok(cmds) = self.script_engine.evaluate(&script, &ctx) { if let Some(step) = self @@ -582,7 +547,8 @@ impl App { self.project_state.project.tempo = link.tempo(); match model::save(&self.project_state.project, &path) { Ok(final_path) => { - self.ui.set_status(format!("Saved: {}", final_path.display())); + self.ui + .set_status(format!("Saved: {}", final_path.display())); self.project_state.file_path = Some(final_path); } Err(e) => { @@ -715,11 +681,7 @@ impl App { pub fn paste_pattern(&mut self, bank: usize, pattern: usize) { if let Some(src) = &self.copied_pattern { let mut pat = src.clone(); - pat.name = match &src.name { - Some(name) if !name.ends_with(" (copy)") => Some(format!("{name} (copy)")), - Some(name) => Some(name.clone()), - None => Some("(copy)".to_string()), - }; + pat.name = Self::annotate_copy_name(&src.name); self.project_state.project.banks[bank].patterns[pattern] = pat; self.project_state.mark_dirty(bank, pattern); if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern { @@ -738,11 +700,7 @@ impl App { pub fn paste_bank(&mut self, bank: usize) { if let Some(src) = &self.copied_bank { let mut b = src.clone(); - b.name = match &src.name { - Some(name) if !name.ends_with(" (copy)") => Some(format!("{name} (copy)")), - Some(name) => Some(name.clone()), - None => Some("(copy)".to_string()), - }; + b.name = Self::annotate_copy_name(&src.name); self.project_state.project.banks[bank] = b; for pattern in 0..self.project_state.project.banks[bank].patterns.len() { self.project_state.mark_dirty(bank, pattern); @@ -756,10 +714,7 @@ impl App { pub fn harden_steps(&mut self) { let (bank, pattern) = self.current_bank_pattern(); - let indices: Vec = match self.editor_ctx.selection_range() { - Some(range) => range.collect(), - None => vec![self.editor_ctx.step], - }; + let indices = self.selected_steps(); let pat = self.project_state.project.pattern_at(bank, pattern); let resolutions: Vec<(usize, String)> = indices @@ -796,18 +751,15 @@ impl App { if count == 1 { self.ui.flash("Step hardened", 150, FlashKind::Success); } else { - self.ui.flash(&format!("{count} steps hardened"), 150, FlashKind::Success); + self.ui + .flash(&format!("{count} steps hardened"), 150, FlashKind::Success); } } pub fn copy_steps(&mut self) { let (bank, pattern) = self.current_bank_pattern(); let pat = self.project_state.project.pattern_at(bank, pattern); - - let indices: Vec = match self.editor_ctx.selection_range() { - Some(range) => range.collect(), - None => vec![self.editor_ctx.step], - }; + let indices = self.selected_steps(); let mut steps = Vec::new(); let mut scripts = Vec::new(); @@ -836,7 +788,8 @@ impl App { let _ = clip.set_text(scripts.join("\n")); } - self.ui.flash(&format!("Copied {count} steps"), 150, FlashKind::Info); + self.ui + .flash(&format!("Copied {count} steps"), 150, FlashKind::Info); } pub fn paste_steps(&mut self, link: &LinkState) { @@ -855,7 +808,12 @@ impl App { if target >= pat_len { break; } - if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) { + if let Some(step) = self + .project_state + .project + .pattern_at_mut(bank, pattern) + .step_mut(target) + { let source = if same_pattern { data.source } else { None }; step.active = data.active; step.source = source; @@ -885,7 +843,11 @@ impl App { } self.editor_ctx.clear_selection(); - self.ui.flash(&format!("Pasted {} steps", copied.steps.len()), 150, FlashKind::Success); + self.ui.flash( + &format!("Pasted {} steps", copied.steps.len()), + 150, + FlashKind::Success, + ); } pub fn link_paste_steps(&mut self) { @@ -897,7 +859,8 @@ impl App { let (bank, pattern) = self.current_bank_pattern(); if copied.bank != bank || copied.pattern != pattern { - self.ui.set_status("Can only link within same pattern".to_string()); + self.ui + .set_status("Can only link within same pattern".to_string()); return; } @@ -918,7 +881,12 @@ impl App { if source_idx == Some(target) { continue; } - if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) { + if let Some(step) = self + .project_state + .project + .pattern_at_mut(bank, pattern) + .step_mut(target) + { step.source = source_idx; step.script.clear(); step.command = None; @@ -928,17 +896,18 @@ impl App { self.project_state.mark_dirty(bank, pattern); self.load_step_to_editor(); self.editor_ctx.clear_selection(); - self.ui.flash(&format!("Linked {} steps", copied.steps.len()), 150, FlashKind::Success); + self.ui.flash( + &format!("Linked {} steps", copied.steps.len()), + 150, + FlashKind::Success, + ); } pub fn duplicate_steps(&mut self, link: &LinkState) { let (bank, pattern) = self.current_bank_pattern(); let pat = self.project_state.project.pattern_at(bank, pattern); let pat_len = pat.length; - let indices: Vec = match self.editor_ctx.selection_range() { - Some(range) => range.collect(), - None => vec![self.editor_ctx.step], - }; + let indices = self.selected_steps(); let count = indices.len(); let paste_at = *indices.last().unwrap() + 1; @@ -958,7 +927,12 @@ impl App { if target >= pat_len { break; } - if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) { + if let Some(step) = self + .project_state + .project + .pattern_at_mut(bank, pattern) + .step_mut(target) + { step.active = active; step.source = source; if source.is_some() { @@ -984,7 +958,11 @@ impl App { } self.editor_ctx.clear_selection(); - self.ui.flash(&format!("Duplicated {count} steps"), 150, FlashKind::Success); + self.ui.flash( + &format!("Duplicated {count} steps"), + 150, + FlashKind::Success, + ); } pub fn open_pattern_modal(&mut self, field: PatternField) { @@ -1139,7 +1117,9 @@ impl App { step, name, } => { - if let Some(s) = self.project_state.project.banks[bank].patterns[pattern].step_mut(step) { + if let Some(s) = + self.project_state.project.banks[bank].patterns[pattern].step_mut(step) + { s.name = name; } self.project_state.mark_dirty(bank, pattern); @@ -1323,6 +1303,160 @@ impl App { let pattern = self.patterns_nav.selected_pattern(); self.stage_pattern_toggle(bank, pattern, snapshot); } + + // UI state + AppCommand::ClearMinimap => { + self.ui.minimap_until = None; + } + AppCommand::HideTitle => { + self.ui.show_title = false; + } + AppCommand::ToggleEditorStack => { + self.editor_ctx.show_stack = !self.editor_ctx.show_stack; + } + AppCommand::SetColorScheme(scheme) => { + self.ui.color_scheme = scheme; + crate::theme::set(scheme.to_theme()); + } + AppCommand::ToggleRuntimeHighlight => { + self.ui.runtime_highlight = !self.ui.runtime_highlight; + } + AppCommand::ToggleCompletion => { + self.ui.show_completion = !self.ui.show_completion; + self.editor_ctx + .editor + .set_completion_enabled(self.ui.show_completion); + } + AppCommand::AdjustFlashBrightness(delta) => { + self.ui.flash_brightness = (self.ui.flash_brightness + delta).clamp(0.0, 1.0); + } + + // Live keys + AppCommand::ToggleLiveKeysFill => { + self.live_keys.flip_fill(); + } + + // Panel + AppCommand::ClosePanel => { + self.panel.visible = false; + self.panel.focus = crate::state::PanelFocus::Main; + } + + // Selection + AppCommand::SetSelectionAnchor(step) => { + self.editor_ctx.selection_anchor = Some(step); + } + AppCommand::ClearSelectionAnchor => { + self.editor_ctx.selection_anchor = None; + } + + // Audio settings (engine page) + AppCommand::AudioNextSection => { + self.audio.next_section(); + } + AppCommand::AudioPrevSection => { + self.audio.prev_section(); + } + AppCommand::AudioOutputListUp => { + self.audio.output_list.move_up(); + } + AppCommand::AudioOutputListDown(count) => { + self.audio.output_list.move_down(count); + } + AppCommand::AudioOutputPageUp => { + self.audio.output_list.page_up(); + } + AppCommand::AudioOutputPageDown(count) => { + self.audio.output_list.page_down(count); + } + AppCommand::AudioInputListUp => { + self.audio.input_list.move_up(); + } + AppCommand::AudioInputListDown(count) => { + self.audio.input_list.move_down(count); + } + AppCommand::AudioInputPageDown(count) => { + self.audio.input_list.page_down(count); + } + AppCommand::AudioSettingNext => { + self.audio.setting_kind = self.audio.setting_kind.next(); + } + AppCommand::AudioSettingPrev => { + self.audio.setting_kind = self.audio.setting_kind.prev(); + } + AppCommand::SetOutputDevice(name) => { + self.audio.config.output_device = Some(name); + } + AppCommand::SetInputDevice(name) => { + self.audio.config.input_device = Some(name); + } + AppCommand::SetDeviceKind(kind) => { + self.audio.device_kind = kind; + } + AppCommand::AdjustAudioSetting { setting, delta } => { + use crate::state::SettingKind; + match setting { + SettingKind::Channels => self.audio.adjust_channels(delta as i16), + SettingKind::BufferSize => self.audio.adjust_buffer_size(delta), + SettingKind::Polyphony => self.audio.adjust_max_voices(delta), + SettingKind::Nudge => { + self.metrics.nudge_ms = + (self.metrics.nudge_ms + delta as f64).clamp(-50.0, 50.0); + } + SettingKind::Lookahead => self.audio.adjust_lookahead(delta), + } + } + AppCommand::AudioTriggerRestart => { + self.audio.trigger_restart(); + } + AppCommand::RemoveLastSamplePath => { + self.audio.remove_last_sample_path(); + } + AppCommand::AudioRefreshDevices => { + self.audio.refresh_devices(); + } + + // Options page + AppCommand::OptionsNextFocus => { + self.options.next_focus(); + } + AppCommand::OptionsPrevFocus => { + self.options.prev_focus(); + } + AppCommand::ToggleRefreshRate => { + self.audio.toggle_refresh_rate(); + } + AppCommand::ToggleScope => { + self.audio.config.show_scope = !self.audio.config.show_scope; + } + AppCommand::ToggleSpectrum => { + self.audio.config.show_spectrum = !self.audio.config.show_spectrum; + } + + // Metrics + AppCommand::ResetPeakVoices => { + self.metrics.peak_voices = 0; + } + + // MIDI connections + AppCommand::ConnectMidiOutput { slot, port } => { + if let Err(e) = self.midi.connect_output(slot, port) { + self.ui + .flash(&format!("MIDI error: {e}"), 300, FlashKind::Error); + } + } + AppCommand::DisconnectMidiOutput(slot) => { + self.midi.disconnect_output(slot); + } + AppCommand::ConnectMidiInput { slot, port } => { + if let Err(e) = self.midi.connect_input(slot, port) { + self.ui + .flash(&format!("MIDI error: {e}"), 300, FlashKind::Error); + } + } + AppCommand::DisconnectMidiInput(slot) => { + self.midi.disconnect_input(slot); + } } } diff --git a/src/bin/desktop.rs b/src/bin/desktop.rs index 62282ff..02c2b7b 100644 --- a/src/bin/desktop.rs +++ b/src/bin/desktop.rs @@ -8,12 +8,10 @@ use eframe::NativeOptions; use egui_ratatui::RataguiBackend; use ratatui::Terminal; use soft_ratatui::embedded_graphics_unicodefonts::{ - mono_6x13_atlas, mono_6x13_bold_atlas, mono_6x13_italic_atlas, - mono_7x13_atlas, mono_7x13_bold_atlas, mono_7x13_italic_atlas, - mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, - mono_9x15_atlas, mono_9x15_bold_atlas, + mono_10x20_atlas, mono_6x13_atlas, mono_6x13_bold_atlas, mono_6x13_italic_atlas, + mono_7x13_atlas, mono_7x13_bold_atlas, mono_7x13_italic_atlas, mono_8x13_atlas, + mono_8x13_bold_atlas, mono_8x13_italic_atlas, mono_9x15_atlas, mono_9x15_bold_atlas, mono_9x18_atlas, mono_9x18_bold_atlas, - mono_10x20_atlas, }; use soft_ratatui::{EmbeddedGraphics, SoftBackend}; @@ -22,12 +20,12 @@ use cagire::engine::{ build_stream, spawn_sequencer, AnalysisHandle, AudioStreamConfig, LinkState, MidiCommand, ScopeBuffer, SequencerConfig, SequencerHandle, SpectrumBuffer, }; -use crossbeam_channel::Receiver; use cagire::input::{handle_key, InputContext, InputResult}; use cagire::input_egui::convert_egui_events; use cagire::settings::Settings; use cagire::state::audio::RefreshRate; use cagire::views; +use crossbeam_channel::Receiver; #[derive(Parser)] #[command(name = "cagire-desktop", about = "Cagire desktop application")] @@ -214,7 +212,9 @@ impl CagireDesktop { audio_sample_pos: Arc::clone(&audio_sample_pos), sample_rate: Arc::clone(&sample_rate_shared), lookahead_ms: Arc::clone(&lookahead_ms), - cc_memory: Some(Arc::clone(&app.midi.cc_memory)), + cc_access: Some( + Arc::new(app.midi.cc_memory.clone()) as Arc + ), mouse_x: Arc::clone(&mouse_x), mouse_y: Arc::clone(&mouse_y), mouse_down: Arc::clone(&mouse_down), @@ -349,7 +349,11 @@ impl CagireDesktop { self.app.metrics.active_voices = self.metrics.active_voices.load(Ordering::Relaxed) as usize; - self.app.metrics.peak_voices = self.app.metrics.peak_voices.max(self.app.metrics.active_voices); + self.app.metrics.peak_voices = self + .app + .metrics + .peak_voices + .max(self.app.metrics.active_voices); self.app.metrics.cpu_load = self.metrics.load.get_load(); self.app.metrics.schedule_depth = self.metrics.schedule_depth.load(Ordering::Relaxed) as usize; @@ -399,7 +403,11 @@ impl eframe::App for CagireDesktop { self.mouse_x.store(nx.to_bits(), Ordering::Relaxed); self.mouse_y.store(ny.to_bits(), Ordering::Relaxed); } - let down = if i.pointer.primary_down() { 1.0_f32 } else { 0.0_f32 }; + let down = if i.pointer.primary_down() { + 1.0_f32 + } else { + 0.0_f32 + }; self.mouse_down.store(down.to_bits(), Ordering::Relaxed); }); @@ -427,22 +435,48 @@ impl eframe::App for CagireDesktop { while let Ok(midi_cmd) = self.midi_rx.try_recv() { match midi_cmd { - MidiCommand::NoteOn { device, channel, note, velocity } => { + MidiCommand::NoteOn { + device, + channel, + note, + velocity, + } => { self.app.midi.send_note_on(device, channel, note, velocity); } - MidiCommand::NoteOff { device, channel, note } => { + MidiCommand::NoteOff { + device, + channel, + note, + } => { self.app.midi.send_note_off(device, channel, note); } - MidiCommand::CC { device, channel, cc, value } => { + MidiCommand::CC { + device, + channel, + cc, + value, + } => { self.app.midi.send_cc(device, channel, cc, value); } - MidiCommand::PitchBend { device, channel, value } => { + MidiCommand::PitchBend { + device, + channel, + value, + } => { self.app.midi.send_pitch_bend(device, channel, value); } - MidiCommand::Pressure { device, channel, value } => { + MidiCommand::Pressure { + device, + channel, + value, + } => { self.app.midi.send_pressure(device, channel, value); } - MidiCommand::ProgramChange { device, channel, program } => { + MidiCommand::ProgramChange { + device, + channel, + program, + } => { self.app.midi.send_program_change(device, channel, program); } MidiCommand::Clock { device } => self.app.midi.send_realtime(device, 0xF8), diff --git a/src/commands.rs b/src/commands.rs index f81a99b..afad74a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use crate::model::{LaunchQuantization, PatternSpeed, SyncMode}; -use crate::state::{FlashKind, Modal, PatternField}; +use crate::state::{ColorScheme, DeviceKind, FlashKind, Modal, PatternField, SettingKind}; #[allow(dead_code)] pub enum AppCommand { @@ -169,4 +169,68 @@ pub enum AppCommand { PatternsEnter, PatternsBack, PatternsTogglePlay, + + // UI state + ClearMinimap, + HideTitle, + ToggleEditorStack, + SetColorScheme(ColorScheme), + ToggleRuntimeHighlight, + ToggleCompletion, + AdjustFlashBrightness(f32), + + // Live keys + ToggleLiveKeysFill, + + // Panel + ClosePanel, + + // Selection + SetSelectionAnchor(usize), + ClearSelectionAnchor, + + // Audio settings (engine page) + AudioNextSection, + AudioPrevSection, + AudioOutputListUp, + AudioOutputListDown(usize), + AudioOutputPageUp, + AudioOutputPageDown(usize), + AudioInputListUp, + AudioInputListDown(usize), + AudioInputPageDown(usize), + AudioSettingNext, + AudioSettingPrev, + SetOutputDevice(String), + SetInputDevice(String), + SetDeviceKind(DeviceKind), + AdjustAudioSetting { + setting: SettingKind, + delta: i32, + }, + AudioTriggerRestart, + RemoveLastSamplePath, + AudioRefreshDevices, + + // Options page + OptionsNextFocus, + OptionsPrevFocus, + ToggleRefreshRate, + ToggleScope, + ToggleSpectrum, + + // Metrics + ResetPeakVoices, + + // MIDI connections + ConnectMidiOutput { + slot: usize, + port: usize, + }, + DisconnectMidiOutput(usize), + ConnectMidiInput { + slot: usize, + port: usize, + }, + DisconnectMidiInput(usize), } diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index 3ffb15d..8495e8d 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -10,7 +10,9 @@ use std::time::Duration; use thread_priority::{set_current_thread_priority, ThreadPriority}; use super::LinkState; -use crate::model::{CcMemory, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables}; +use crate::model::{ + CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables, +}; use crate::model::{LaunchQuantization, SyncMode, MAX_BANKS, MAX_PATTERNS}; use crate::state::LiveKeyState; @@ -55,16 +57,50 @@ pub enum AudioCommand { #[derive(Clone, Debug)] pub enum MidiCommand { - NoteOn { device: u8, channel: u8, note: u8, velocity: u8 }, - NoteOff { device: u8, channel: u8, note: u8 }, - CC { device: u8, channel: u8, cc: u8, value: u8 }, - PitchBend { device: u8, channel: u8, value: u16 }, - Pressure { device: u8, channel: u8, value: u8 }, - ProgramChange { device: u8, channel: u8, program: u8 }, - Clock { device: u8 }, - Start { device: u8 }, - Stop { device: u8 }, - Continue { device: u8 }, + NoteOn { + device: u8, + channel: u8, + note: u8, + velocity: u8, + }, + NoteOff { + device: u8, + channel: u8, + note: u8, + }, + CC { + device: u8, + channel: u8, + cc: u8, + value: u8, + }, + PitchBend { + device: u8, + channel: u8, + value: u16, + }, + Pressure { + device: u8, + channel: u8, + value: u8, + }, + ProgramChange { + device: u8, + channel: u8, + program: u8, + }, + Clock { + device: u8, + }, + Start { + device: u8, + }, + Stop { + device: u8, + }, + Continue { + device: u8, + }, } pub enum SeqCommand { @@ -233,7 +269,7 @@ pub struct SequencerConfig { pub audio_sample_pos: Arc, pub sample_rate: Arc, pub lookahead_ms: Arc, - pub cc_memory: Option, + pub cc_access: Option>, #[cfg(feature = "desktop")] pub mouse_x: Arc, #[cfg(feature = "desktop")] @@ -253,7 +289,11 @@ pub fn spawn_sequencer( live_keys: Arc, nudge_us: Arc, config: SequencerConfig, -) -> (SequencerHandle, Receiver, Receiver) { +) -> ( + SequencerHandle, + Receiver, + Receiver, +) { let (cmd_tx, cmd_rx) = bounded::(64); let (audio_tx, audio_rx) = bounded::(256); let (midi_tx, midi_rx) = bounded::(256); @@ -291,7 +331,7 @@ pub fn spawn_sequencer( config.audio_sample_pos, config.sample_rate, config.lookahead_ms, - config.cc_memory, + config.cc_access, #[cfg(feature = "desktop")] mouse_x, #[cfg(feature = "desktop")] @@ -510,12 +550,17 @@ pub(crate) struct SequencerState { speed_overrides: HashMap<(usize, usize), f64>, key_cache: KeyCache, buf_audio_commands: Vec, - cc_memory: Option, + cc_access: Option>, active_notes: HashMap<(u8, u8, u8), ActiveNote>, } impl SequencerState { - pub fn new(variables: Variables, dict: Dictionary, rng: Rng, cc_memory: Option) -> Self { + pub fn new( + variables: Variables, + dict: Dictionary, + rng: Rng, + cc_access: Option>, + ) -> Self { let script_engine = ScriptEngine::new(Arc::clone(&variables), dict, rng); Self { audio_state: AudioState::new(), @@ -529,7 +574,7 @@ impl SequencerState { speed_overrides: HashMap::new(), key_cache: KeyCache::new(), buf_audio_commands: Vec::new(), - cc_memory, + cc_access, active_notes: HashMap::new(), } } @@ -781,7 +826,7 @@ impl SequencerState { speed: speed_mult, fill, nudge_secs, - cc_memory: self.cc_memory.clone(), + cc_access: self.cc_access.clone(), #[cfg(feature = "desktop")] mouse_x, #[cfg(feature = "desktop")] @@ -943,7 +988,7 @@ fn sequencer_loop( audio_sample_pos: Arc, sample_rate: Arc, lookahead_ms: Arc, - cc_memory: Option, + cc_access: Option>, #[cfg(feature = "desktop")] mouse_x: Arc, #[cfg(feature = "desktop")] mouse_y: Arc, #[cfg(feature = "desktop")] mouse_down: Arc, @@ -952,7 +997,7 @@ fn sequencer_loop( let _ = set_current_thread_priority(ThreadPriority::Max); - let mut seq_state = SequencerState::new(variables, dict, rng, cc_memory); + let mut seq_state = SequencerState::new(variables, dict, rng, cc_access); loop { let mut commands = Vec::new(); @@ -1002,8 +1047,15 @@ fn sequencer_loop( if let Some((midi_cmd, dur)) = parse_midi_command(&tsc.cmd) { match midi_tx.load().try_send(midi_cmd.clone()) { Ok(()) => { - if let (MidiCommand::NoteOn { device, channel, note, .. }, Some(dur_secs)) = - (&midi_cmd, dur) + if let ( + MidiCommand::NoteOn { + device, + channel, + note, + .. + }, + Some(dur_secs), + ) = (&midi_cmd, dur) { let dur_us = (dur_secs * 1_000_000.0) as i64; seq_state.active_notes.insert( @@ -1037,28 +1089,41 @@ fn sequencer_loop( if output.flush_midi_notes { for ((device, channel, note), _) in seq_state.active_notes.drain() { - let _ = midi_tx.load().try_send(MidiCommand::NoteOff { device, channel, note }); + let _ = midi_tx.load().try_send(MidiCommand::NoteOff { + device, + channel, + note, + }); } // Send MIDI panic (CC 123 = All Notes Off) on all 16 channels for all devices for dev in 0..4u8 { for chan in 0..16u8 { - let _ = midi_tx - .load() - .try_send(MidiCommand::CC { device: dev, channel: chan, cc: 123, value: 0 }); + let _ = midi_tx.load().try_send(MidiCommand::CC { + device: dev, + channel: chan, + cc: 123, + value: 0, + }); } } } else { - seq_state.active_notes.retain(|&(device, channel, note), active| { - let should_release = current_time_us >= active.off_time_us; - let timed_out = (current_time_us - active.start_time_us) > MAX_NOTE_DURATION_US; + seq_state + .active_notes + .retain(|&(device, channel, note), active| { + let should_release = current_time_us >= active.off_time_us; + let timed_out = (current_time_us - active.start_time_us) > MAX_NOTE_DURATION_US; - if should_release || timed_out { - let _ = midi_tx.load().try_send(MidiCommand::NoteOff { device, channel, note }); - false - } else { - true - } - }); + if should_release || timed_out { + let _ = midi_tx.load().try_send(MidiCommand::NoteOff { + device, + channel, + note, + }); + false + } else { + true + } + }); } if let Some(t) = output.new_tempo { @@ -1081,7 +1146,8 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option)> { } let find_param = |key: &str| -> Option<&str> { - parts.iter() + parts + .iter() .position(|&s| s == key) .and_then(|i| parts.get(i + 1).copied()) }; @@ -1124,19 +1190,40 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option)> { // /midi/bend//chan//dev/ let value: u16 = parts.get(2)?.parse().ok()?; let chan: u8 = find_param("chan")?.parse().ok()?; - Some((MidiCommand::PitchBend { device, channel: chan, value }, None)) + Some(( + MidiCommand::PitchBend { + device, + channel: chan, + value, + }, + None, + )) } "pressure" => { // /midi/pressure//chan//dev/ let value: u8 = parts.get(2)?.parse().ok()?; let chan: u8 = find_param("chan")?.parse().ok()?; - Some((MidiCommand::Pressure { device, channel: chan, value }, None)) + Some(( + MidiCommand::Pressure { + device, + channel: chan, + value, + }, + None, + )) } "program" => { // /midi/program//chan//dev/ let program: u8 = parts.get(2)?.parse().ok()?; let chan: u8 = find_param("chan")?.parse().ok()?; - Some((MidiCommand::ProgramChange { device, channel: chan, program }, None)) + Some(( + MidiCommand::ProgramChange { + device, + channel: chan, + program, + }, + None, + )) } "clock" => Some((MidiCommand::Clock { device }, None)), "start" => Some((MidiCommand::Start { device }, None)), diff --git a/src/input.rs b/src/input.rs index 0062a4f..2fb0ff1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -52,11 +52,11 @@ pub fn handle_key(ctx: &mut InputContext, key: KeyEvent) -> InputResult { KeyCode::Left | KeyCode::Right | KeyCode::Up | KeyCode::Down ); if ctx.app.ui.minimap_until.is_some() && !(ctrl && is_arrow) { - ctx.app.ui.minimap_until = None; + ctx.dispatch(AppCommand::ClearMinimap); } if ctx.app.ui.show_title { - ctx.app.ui.show_title = false; + ctx.dispatch(AppCommand::HideTitle); return InputResult::Continue; } @@ -73,7 +73,7 @@ fn handle_live_keys(ctx: &mut InputContext, key: &KeyEvent) -> bool { match (key.code, key.kind) { _ if !matches!(ctx.app.ui.modal, Modal::None) => false, (KeyCode::Char('f'), KeyEventKind::Press) => { - ctx.app.live_keys.flip_fill(); + ctx.dispatch(AppCommand::ToggleLiveKeysFill); true } _ => false, @@ -506,13 +506,23 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult { editor.search_prev(); } KeyCode::Char('s') if ctrl => { - ctx.app.editor_ctx.show_stack = !ctx.app.editor_ctx.show_stack; + ctx.dispatch(AppCommand::ToggleEditorStack); } KeyCode::Char('r') if ctrl => { let script = ctx.app.editor_ctx.editor.lines().join("\n"); - match ctx.app.execute_script_oneshot(&script, ctx.link, ctx.audio_tx) { - Ok(()) => ctx.app.ui.flash("Executed", 100, crate::state::FlashKind::Info), - Err(e) => ctx.app.ui.flash(&format!("Error: {e}"), 200, crate::state::FlashKind::Error), + match ctx + .app + .execute_script_oneshot(&script, ctx.link, ctx.audio_tx) + { + Ok(()) => ctx + .app + .ui + .flash("Executed", 100, crate::state::FlashKind::Info), + Err(e) => ctx.app.ui.flash( + &format!("Error: {e}"), + 200, + crate::state::FlashKind::Error, + ), } } KeyCode::Char('a') if ctrl => { @@ -745,8 +755,7 @@ fn handle_panel_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult { KeyCode::Left => state.collapse_at_cursor(), KeyCode::Char('/') => state.activate_search(), KeyCode::Esc | KeyCode::Tab => { - ctx.app.panel.visible = false; - ctx.app.panel.focus = PanelFocus::Main; + ctx.dispatch(AppCommand::ClosePanel); } _ => {} } @@ -783,25 +792,25 @@ fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputR } KeyCode::Left if shift && !ctrl => { if ctx.app.editor_ctx.selection_anchor.is_none() { - ctx.app.editor_ctx.selection_anchor = Some(ctx.app.editor_ctx.step); + ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step)); } ctx.dispatch(AppCommand::PrevStep); } KeyCode::Right if shift && !ctrl => { if ctx.app.editor_ctx.selection_anchor.is_none() { - ctx.app.editor_ctx.selection_anchor = Some(ctx.app.editor_ctx.step); + ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step)); } ctx.dispatch(AppCommand::NextStep); } KeyCode::Up if shift && !ctrl => { if ctx.app.editor_ctx.selection_anchor.is_none() { - ctx.app.editor_ctx.selection_anchor = Some(ctx.app.editor_ctx.step); + ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step)); } ctx.dispatch(AppCommand::StepUp); } KeyCode::Down if shift && !ctrl => { if ctx.app.editor_ctx.selection_anchor.is_none() { - ctx.app.editor_ctx.selection_anchor = Some(ctx.app.editor_ctx.step); + ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step)); } ctx.dispatch(AppCommand::StepDown); } @@ -910,9 +919,19 @@ fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputR let pattern = ctx.app.current_edit_pattern(); if let Some(script) = pattern.resolve_script(ctx.app.editor_ctx.step) { if !script.trim().is_empty() { - match ctx.app.execute_script_oneshot(script, ctx.link, ctx.audio_tx) { - Ok(()) => ctx.app.ui.flash("Executed", 100, crate::state::FlashKind::Info), - Err(e) => ctx.app.ui.flash(&format!("Error: {e}"), 200, crate::state::FlashKind::Error), + match ctx + .app + .execute_script_oneshot(script, ctx.link, ctx.audio_tx) + { + Ok(()) => ctx + .app + .ui + .flash("Executed", 100, crate::state::FlashKind::Info), + Err(e) => ctx.app.ui.flash( + &format!("Error: {e}"), + 200, + crate::state::FlashKind::Error, + ), } } } @@ -923,7 +942,9 @@ fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputR ctx.app.editor_ctx.pattern, ctx.app.editor_ctx.step, ); - let current_name = ctx.app.current_edit_pattern() + let current_name = ctx + .app + .current_edit_pattern() .step(step) .and_then(|s| s.name.clone()) .unwrap_or_default(); @@ -1063,15 +1084,15 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { selected: false, })); } - KeyCode::Tab => ctx.app.audio.next_section(), - KeyCode::BackTab => ctx.app.audio.prev_section(), + KeyCode::Tab => ctx.dispatch(AppCommand::AudioNextSection), + KeyCode::BackTab => ctx.dispatch(AppCommand::AudioPrevSection), KeyCode::Up => match ctx.app.audio.section { EngineSection::Devices => match ctx.app.audio.device_kind { - DeviceKind::Output => ctx.app.audio.output_list.move_up(), - DeviceKind::Input => ctx.app.audio.input_list.move_up(), + DeviceKind::Output => ctx.dispatch(AppCommand::AudioOutputListUp), + DeviceKind::Input => ctx.dispatch(AppCommand::AudioInputListUp), }, EngineSection::Settings => { - ctx.app.audio.setting_kind = ctx.app.audio.setting_kind.prev(); + ctx.dispatch(AppCommand::AudioSettingPrev); } EngineSection::Samples => {} }, @@ -1079,22 +1100,22 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { EngineSection::Devices => match ctx.app.audio.device_kind { DeviceKind::Output => { let count = ctx.app.audio.output_devices.len(); - ctx.app.audio.output_list.move_down(count); + ctx.dispatch(AppCommand::AudioOutputListDown(count)); } DeviceKind::Input => { let count = ctx.app.audio.input_devices.len(); - ctx.app.audio.input_list.move_down(count); + ctx.dispatch(AppCommand::AudioInputListDown(count)); } }, EngineSection::Settings => { - ctx.app.audio.setting_kind = ctx.app.audio.setting_kind.next(); + ctx.dispatch(AppCommand::AudioSettingNext); } EngineSection::Samples => {} }, KeyCode::PageUp => { if ctx.app.audio.section == EngineSection::Devices { match ctx.app.audio.device_kind { - DeviceKind::Output => ctx.app.audio.output_list.page_up(), + DeviceKind::Output => ctx.dispatch(AppCommand::AudioOutputPageUp), DeviceKind::Input => ctx.app.audio.input_list.page_up(), } } @@ -1104,11 +1125,11 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { match ctx.app.audio.device_kind { DeviceKind::Output => { let count = ctx.app.audio.output_devices.len(); - ctx.app.audio.output_list.page_down(count); + ctx.dispatch(AppCommand::AudioOutputPageDown(count)); } DeviceKind::Input => { let count = ctx.app.audio.input_devices.len(); - ctx.app.audio.input_list.page_down(count); + ctx.dispatch(AppCommand::AudioInputPageDown(count)); } } } @@ -1119,16 +1140,16 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { DeviceKind::Output => { let cursor = ctx.app.audio.output_list.cursor; if cursor < ctx.app.audio.output_devices.len() { - ctx.app.audio.config.output_device = - Some(ctx.app.audio.output_devices[cursor].name.clone()); + let name = ctx.app.audio.output_devices[cursor].name.clone(); + ctx.dispatch(AppCommand::SetOutputDevice(name)); ctx.app.save_settings(ctx.link); } } DeviceKind::Input => { let cursor = ctx.app.audio.input_list.cursor; if cursor < ctx.app.audio.input_devices.len() { - ctx.app.audio.config.input_device = - Some(ctx.app.audio.input_devices[cursor].name.clone()); + let name = ctx.app.audio.input_devices[cursor].name.clone(); + ctx.dispatch(AppCommand::SetInputDevice(name)); ctx.app.save_settings(ctx.link); } } @@ -1137,20 +1158,32 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { } KeyCode::Left => match ctx.app.audio.section { EngineSection::Devices => { - ctx.app.audio.device_kind = DeviceKind::Output; + ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Output)); } EngineSection::Settings => { match ctx.app.audio.setting_kind { - SettingKind::Channels => ctx.app.audio.adjust_channels(-1), - SettingKind::BufferSize => ctx.app.audio.adjust_buffer_size(-64), - SettingKind::Polyphony => ctx.app.audio.adjust_max_voices(-1), + SettingKind::Channels => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Channels, + delta: -1, + }), + SettingKind::BufferSize => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::BufferSize, + delta: -64, + }), + SettingKind::Polyphony => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Polyphony, + delta: -1, + }), SettingKind::Nudge => { let prev = ctx.nudge_us.load(Ordering::Relaxed); ctx.nudge_us .store((prev - 1000).max(-100_000), Ordering::Relaxed); } SettingKind::Lookahead => { - ctx.app.audio.adjust_lookahead(-1); + ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Lookahead, + delta: -1, + }); ctx.lookahead_ms .store(ctx.app.audio.config.lookahead_ms, Ordering::Relaxed); } @@ -1161,20 +1194,32 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { }, KeyCode::Right => match ctx.app.audio.section { EngineSection::Devices => { - ctx.app.audio.device_kind = DeviceKind::Input; + ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Input)); } EngineSection::Settings => { match ctx.app.audio.setting_kind { - SettingKind::Channels => ctx.app.audio.adjust_channels(1), - SettingKind::BufferSize => ctx.app.audio.adjust_buffer_size(64), - SettingKind::Polyphony => ctx.app.audio.adjust_max_voices(1), + SettingKind::Channels => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Channels, + delta: 1, + }), + SettingKind::BufferSize => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::BufferSize, + delta: 64, + }), + SettingKind::Polyphony => ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Polyphony, + delta: 1, + }), SettingKind::Nudge => { let prev = ctx.nudge_us.load(Ordering::Relaxed); ctx.nudge_us .store((prev + 1000).min(100_000), Ordering::Relaxed); } SettingKind::Lookahead => { - ctx.app.audio.adjust_lookahead(1); + ctx.dispatch(AppCommand::AdjustAudioSetting { + setting: SettingKind::Lookahead, + delta: 1, + }); ctx.lookahead_ms .store(ctx.app.audio.config.lookahead_ms, Ordering::Relaxed); } @@ -1183,7 +1228,7 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { } EngineSection::Samples => {} }, - KeyCode::Char('R') => ctx.app.audio.trigger_restart(), + KeyCode::Char('R') => ctx.dispatch(AppCommand::AudioTriggerRestart), KeyCode::Char('A') => { use crate::state::file_browser::FileBrowserState; let state = FileBrowserState::new_load(String::new()); @@ -1191,9 +1236,9 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { } KeyCode::Char('D') => { if ctx.app.audio.section == EngineSection::Samples { - ctx.app.audio.remove_last_sample_path(); + ctx.dispatch(AppCommand::RemoveLastSamplePath); } else { - ctx.app.audio.refresh_devices(); + ctx.dispatch(AppCommand::AudioRefreshDevices); let out_count = ctx.app.audio.output_devices.len(); let in_count = ctx.app.audio.input_devices.len(); ctx.dispatch(AppCommand::SetStatus(format!( @@ -1209,7 +1254,7 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { let _ = ctx.audio_tx.load().send(AudioCommand::Panic); let _ = ctx.seq_cmd_tx.send(SeqCommand::StopAll); } - KeyCode::Char('r') => ctx.app.metrics.peak_voices = 0, + KeyCode::Char('r') => ctx.dispatch(AppCommand::ResetPeakVoices), KeyCode::Char('t') => { let _ = ctx.audio_tx.load().send(AudioCommand::Evaluate { cmd: "/sound/sine/dur/0.5/decay/0.2".into(), @@ -1231,8 +1276,8 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { selected: false, })); } - KeyCode::Down | KeyCode::Tab => ctx.app.options.next_focus(), - KeyCode::Up | KeyCode::BackTab => ctx.app.options.prev_focus(), + KeyCode::Down | KeyCode::Tab => ctx.dispatch(AppCommand::OptionsNextFocus), + KeyCode::Up | KeyCode::BackTab => ctx.dispatch(AppCommand::OptionsPrevFocus), KeyCode::Left | KeyCode::Right => { match ctx.app.options.focus { OptionsFocus::ColorScheme => { @@ -1241,26 +1286,24 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { } else { ctx.app.ui.color_scheme.next() }; - ctx.app.ui.color_scheme = new_scheme; - crate::theme::set(new_scheme.to_theme()); + ctx.dispatch(AppCommand::SetColorScheme(new_scheme)); } - OptionsFocus::RefreshRate => ctx.app.audio.toggle_refresh_rate(), + OptionsFocus::RefreshRate => ctx.dispatch(AppCommand::ToggleRefreshRate), OptionsFocus::RuntimeHighlight => { - ctx.app.ui.runtime_highlight = !ctx.app.ui.runtime_highlight + ctx.dispatch(AppCommand::ToggleRuntimeHighlight); } OptionsFocus::ShowScope => { - ctx.app.audio.config.show_scope = !ctx.app.audio.config.show_scope + ctx.dispatch(AppCommand::ToggleScope); } OptionsFocus::ShowSpectrum => { - ctx.app.audio.config.show_spectrum = !ctx.app.audio.config.show_spectrum + ctx.dispatch(AppCommand::ToggleSpectrum); } OptionsFocus::ShowCompletion => { - ctx.app.ui.show_completion = !ctx.app.ui.show_completion + ctx.dispatch(AppCommand::ToggleCompletion); } OptionsFocus::FlashBrightness => { let delta = if key.code == KeyCode::Left { -0.1 } else { 0.1 }; - ctx.app.ui.flash_brightness = - (ctx.app.ui.flash_brightness + delta).clamp(0.0, 1.0); + ctx.dispatch(AppCommand::AdjustFlashBrightness(delta)); } OptionsFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()), OptionsFocus::StartStopSync => ctx @@ -1270,8 +1313,10 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { let delta = if key.code == KeyCode::Left { -1.0 } else { 1.0 }; ctx.link.set_quantum(ctx.link.quantum() + delta); } - OptionsFocus::MidiOutput0 | OptionsFocus::MidiOutput1 | - OptionsFocus::MidiOutput2 | OptionsFocus::MidiOutput3 => { + OptionsFocus::MidiOutput0 + | OptionsFocus::MidiOutput1 + | OptionsFocus::MidiOutput2 + | OptionsFocus::MidiOutput3 => { let slot = match ctx.app.options.focus { OptionsFocus::MidiOutput0 => 0, OptionsFocus::MidiOutput1 => 1, @@ -1285,7 +1330,12 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { .enumerate() .filter(|(idx, _)| { ctx.app.midi.selected_outputs[slot] == Some(*idx) - || !ctx.app.midi.selected_outputs.iter().enumerate() + || !ctx + .app + .midi + .selected_outputs + .iter() + .enumerate() .any(|(s, sel)| s != slot && *sel == Some(*idx)) }) .collect(); @@ -1295,7 +1345,11 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { .map(|p| p + 1) .unwrap_or(0); let new_pos = if key.code == KeyCode::Left { - if current_pos == 0 { total_options - 1 } else { current_pos - 1 } + if current_pos == 0 { + total_options - 1 + } else { + current_pos - 1 + } } else { (current_pos + 1) % total_options }; @@ -1308,13 +1362,16 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { let (device_idx, device) = available[new_pos - 1]; if ctx.app.midi.connect_output(slot, device_idx).is_ok() { ctx.dispatch(AppCommand::SetStatus(format!( - "MIDI output {}: {}", slot, device.name + "MIDI output {}: {}", + slot, device.name ))); } } } - OptionsFocus::MidiInput0 | OptionsFocus::MidiInput1 | - OptionsFocus::MidiInput2 | OptionsFocus::MidiInput3 => { + OptionsFocus::MidiInput0 + | OptionsFocus::MidiInput1 + | OptionsFocus::MidiInput2 + | OptionsFocus::MidiInput3 => { let slot = match ctx.app.options.focus { OptionsFocus::MidiInput0 => 0, OptionsFocus::MidiInput1 => 1, @@ -1328,7 +1385,12 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { .enumerate() .filter(|(idx, _)| { ctx.app.midi.selected_inputs[slot] == Some(*idx) - || !ctx.app.midi.selected_inputs.iter().enumerate() + || !ctx + .app + .midi + .selected_inputs + .iter() + .enumerate() .any(|(s, sel)| s != slot && *sel == Some(*idx)) }) .collect(); @@ -1338,7 +1400,11 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { .map(|p| p + 1) .unwrap_or(0); let new_pos = if key.code == KeyCode::Left { - if current_pos == 0 { total_options - 1 } else { current_pos - 1 } + if current_pos == 0 { + total_options - 1 + } else { + current_pos - 1 + } } else { (current_pos + 1) % total_options }; @@ -1351,7 +1417,8 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { let (device_idx, device) = available[new_pos - 1]; if ctx.app.midi.connect_input(slot, device_idx).is_ok() { ctx.dispatch(AppCommand::SetStatus(format!( - "MIDI input {}: {}", slot, device.name + "MIDI input {}: {}", + slot, device.name ))); } } diff --git a/src/main.rs b/src/main.rs index 2cd25c9..ebc8e76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -146,7 +146,7 @@ fn main() -> io::Result<()> { audio_sample_pos: Arc::clone(&audio_sample_pos), sample_rate: Arc::clone(&sample_rate_shared), lookahead_ms: Arc::clone(&lookahead_ms), - cc_memory: Some(Arc::clone(&app.midi.cc_memory)), + cc_access: Some(Arc::new(app.midi.cc_memory.clone()) as Arc), #[cfg(feature = "desktop")] mouse_x: Arc::clone(&mouse_x), #[cfg(feature = "desktop")] @@ -257,22 +257,48 @@ fn main() -> io::Result<()> { // Process pending MIDI commands while let Ok(midi_cmd) = midi_rx.try_recv() { match midi_cmd { - engine::MidiCommand::NoteOn { device, channel, note, velocity } => { + engine::MidiCommand::NoteOn { + device, + channel, + note, + velocity, + } => { app.midi.send_note_on(device, channel, note, velocity); } - engine::MidiCommand::NoteOff { device, channel, note } => { + engine::MidiCommand::NoteOff { + device, + channel, + note, + } => { app.midi.send_note_off(device, channel, note); } - engine::MidiCommand::CC { device, channel, cc, value } => { + engine::MidiCommand::CC { + device, + channel, + cc, + value, + } => { app.midi.send_cc(device, channel, cc, value); } - engine::MidiCommand::PitchBend { device, channel, value } => { + engine::MidiCommand::PitchBend { + device, + channel, + value, + } => { app.midi.send_pitch_bend(device, channel, value); } - engine::MidiCommand::Pressure { device, channel, value } => { + engine::MidiCommand::Pressure { + device, + channel, + value, + } => { app.midi.send_pressure(device, channel, value); } - engine::MidiCommand::ProgramChange { device, channel, program } => { + engine::MidiCommand::ProgramChange { + device, + channel, + program, + } => { app.midi.send_program_change(device, channel, program); } engine::MidiCommand::Clock { device } => app.midi.send_realtime(device, 0xF8), diff --git a/src/midi.rs b/src/midi.rs index 9b159ce..d29217c 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -2,10 +2,53 @@ use std::sync::{Arc, Mutex}; use midir::{MidiInput, MidiOutput}; -use crate::model::CcMemory; +use cagire_forth::CcAccess; pub const MAX_MIDI_OUTPUTS: usize = 4; pub const MAX_MIDI_INPUTS: usize = 4; +pub const MAX_MIDI_DEVICES: usize = 4; + +/// Raw CC memory storage type +type CcMemoryInner = Arc>; + +/// CC memory storage: [device][channel][cc_number] -> value +/// Wrapped in a newtype to implement CcAccess (orphan rule) +#[derive(Clone)] +pub struct CcMemory(CcMemoryInner); + +impl CcMemory { + pub fn new() -> Self { + Self(Arc::new(Mutex::new([[[0u8; 128]; 16]; MAX_MIDI_DEVICES]))) + } + + fn inner(&self) -> &CcMemoryInner { + &self.0 + } + + /// Set a CC value (for testing) + #[allow(dead_code)] + pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) { + if let Ok(mut mem) = self.0.lock() { + mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value; + } + } +} + +impl Default for CcMemory { + fn default() -> Self { + Self::new() + } +} + +impl CcAccess for CcMemory { + fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8 { + self.0 + .lock() + .ok() + .map(|mem| mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)]) + .unwrap_or(0) + } +} #[derive(Clone, Debug)] pub struct MidiDeviceInfo { @@ -46,7 +89,7 @@ pub fn list_midi_inputs() -> Vec { pub struct MidiState { output_conns: [Option; MAX_MIDI_OUTPUTS], - input_conns: [Option>; MAX_MIDI_INPUTS], + input_conns: [Option>; MAX_MIDI_INPUTS], pub selected_outputs: [Option; MAX_MIDI_OUTPUTS], pub selected_inputs: [Option; MAX_MIDI_INPUTS], pub cc_memory: CcMemory, @@ -65,7 +108,7 @@ impl MidiState { input_conns: [None, None, None, None], selected_outputs: [None; MAX_MIDI_OUTPUTS], selected_inputs: [None; MAX_MIDI_INPUTS], - cc_memory: Arc::new(Mutex::new([[[0u8; 128]; 16]; MAX_MIDI_OUTPUTS])), + cc_memory: CcMemory::new(), } } @@ -99,7 +142,7 @@ impl MidiState { let ports = midi_in.ports(); let port = ports.get(port_index).ok_or("MIDI input port not found")?; - let cc_mem = Arc::clone(&self.cc_memory); + let cc_mem = Arc::clone(self.cc_memory.inner()); let conn = midi_in .connect( port, @@ -133,60 +176,46 @@ impl MidiState { } } - pub fn send_note_on(&mut self, device: u8, channel: u8, note: u8, velocity: u8) { + fn send_message(&mut self, device: u8, message: &[u8]) { let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); if let Some(conn) = &mut self.output_conns[slot] { - let status = 0x90 | (channel & 0x0F); - let _ = conn.send(&[status, note & 0x7F, velocity & 0x7F]); + let _ = conn.send(message); } } + pub fn send_note_on(&mut self, device: u8, channel: u8, note: u8, velocity: u8) { + let status = 0x90 | (channel & 0x0F); + self.send_message(device, &[status, note & 0x7F, velocity & 0x7F]); + } + pub fn send_note_off(&mut self, device: u8, channel: u8, note: u8) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let status = 0x80 | (channel & 0x0F); - let _ = conn.send(&[status, note & 0x7F, 0]); - } + let status = 0x80 | (channel & 0x0F); + self.send_message(device, &[status, note & 0x7F, 0]); } pub fn send_cc(&mut self, device: u8, channel: u8, cc: u8, value: u8) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let status = 0xB0 | (channel & 0x0F); - let _ = conn.send(&[status, cc & 0x7F, value & 0x7F]); - } + let status = 0xB0 | (channel & 0x0F); + self.send_message(device, &[status, cc & 0x7F, value & 0x7F]); } pub fn send_pitch_bend(&mut self, device: u8, channel: u8, value: u16) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let status = 0xE0 | (channel & 0x0F); - let lsb = (value & 0x7F) as u8; - let msb = ((value >> 7) & 0x7F) as u8; - let _ = conn.send(&[status, lsb, msb]); - } + let status = 0xE0 | (channel & 0x0F); + let lsb = (value & 0x7F) as u8; + let msb = ((value >> 7) & 0x7F) as u8; + self.send_message(device, &[status, lsb, msb]); } pub fn send_pressure(&mut self, device: u8, channel: u8, value: u8) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let status = 0xD0 | (channel & 0x0F); - let _ = conn.send(&[status, value & 0x7F]); - } + let status = 0xD0 | (channel & 0x0F); + self.send_message(device, &[status, value & 0x7F]); } pub fn send_program_change(&mut self, device: u8, channel: u8, program: u8) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let status = 0xC0 | (channel & 0x0F); - let _ = conn.send(&[status, program & 0x7F]); - } + let status = 0xC0 | (channel & 0x0F); + self.send_message(device, &[status, program & 0x7F]); } pub fn send_realtime(&mut self, device: u8, msg: u8) { - let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1); - if let Some(conn) = &mut self.output_conns[slot] { - let _ = conn.send(&[msg]); - } + self.send_message(device, &[msg]); } } diff --git a/src/model/mod.rs b/src/model/mod.rs index 00e4c9c..086b7d5 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,4 +5,7 @@ pub use cagire_project::{ load, save, Bank, LaunchQuantization, Pattern, PatternSpeed, Project, SyncMode, MAX_BANKS, MAX_PATTERNS, }; -pub use script::{CcMemory, Dictionary, ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Value, Variables}; +pub use script::{ + CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Value, + Variables, +}; diff --git a/src/model/script.rs b/src/model/script.rs index 22aefde..0692e24 100644 --- a/src/model/script.rs +++ b/src/model/script.rs @@ -1,6 +1,8 @@ use cagire_forth::Forth; -pub use cagire_forth::{CcMemory, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables}; +pub use cagire_forth::{ + CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables, +}; pub struct ScriptEngine { forth: Forth, diff --git a/src/state/color_scheme.rs b/src/state/color_scheme.rs index 54eaff9..0fe90de 100644 --- a/src/state/color_scheme.rs +++ b/src/state/color_scheme.rs @@ -1,65 +1,47 @@ -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use crate::theme::ThemeColors; +use crate::theme::{ThemeColors, THEMES}; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub enum ColorScheme { - #[default] - CatppuccinMocha, - CatppuccinLatte, - Nord, - Dracula, - GruvboxDark, - Monokai, - PitchBlack, -} +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ColorScheme(usize); impl ColorScheme { pub fn label(self) -> &'static str { - match self { - Self::CatppuccinMocha => "Catppuccin Mocha", - Self::CatppuccinLatte => "Catppuccin Latte", - Self::Nord => "Nord", - Self::Dracula => "Dracula", - Self::GruvboxDark => "Gruvbox Dark", - Self::Monokai => "Monokai", - Self::PitchBlack => "Pitch Black", - } + THEMES[self.0].label } pub fn next(self) -> Self { - match self { - Self::CatppuccinMocha => Self::CatppuccinLatte, - Self::CatppuccinLatte => Self::Nord, - Self::Nord => Self::Dracula, - Self::Dracula => Self::GruvboxDark, - Self::GruvboxDark => Self::Monokai, - Self::Monokai => Self::PitchBlack, - Self::PitchBlack => Self::CatppuccinMocha, - } + Self((self.0 + 1) % THEMES.len()) } pub fn prev(self) -> Self { - match self { - Self::CatppuccinMocha => Self::PitchBlack, - Self::CatppuccinLatte => Self::CatppuccinMocha, - Self::Nord => Self::CatppuccinLatte, - Self::Dracula => Self::Nord, - Self::GruvboxDark => Self::Dracula, - Self::Monokai => Self::GruvboxDark, - Self::PitchBlack => Self::Monokai, - } + Self((self.0 + THEMES.len() - 1) % THEMES.len()) } pub fn to_theme(self) -> ThemeColors { - match self { - Self::CatppuccinMocha => ThemeColors::catppuccin_mocha(), - Self::CatppuccinLatte => ThemeColors::catppuccin_latte(), - Self::Nord => ThemeColors::nord(), - Self::Dracula => ThemeColors::dracula(), - Self::GruvboxDark => ThemeColors::gruvbox_dark(), - Self::Monokai => ThemeColors::monokai(), - Self::PitchBlack => ThemeColors::pitch_black(), - } + (THEMES[self.0].colors)() + } +} + +impl Serialize for ColorScheme { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(THEMES[self.0].id) + } +} + +impl<'de> Deserialize<'de> for ColorScheme { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + THEMES + .iter() + .position(|t| t.id == s) + .map(ColorScheme) + .ok_or_else(|| de::Error::custom(format!("unknown theme: {s}"))) } } diff --git a/src/views/render.rs b/src/views/render.rs index 9591a76..2931e4a 100644 --- a/src/views/render.rs +++ b/src/views/render.rs @@ -12,7 +12,6 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Cell, Clear, Paragraph, Row, Table}; use ratatui::Frame; -use cagire_forth::Forth; use crate::app::App; use crate::engine::{LinkState, SequencerSnapshot}; use crate::model::{SourceSpan, StepContext, Value}; @@ -23,12 +22,17 @@ use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime} use crate::widgets::{ ConfirmModal, ModalFrame, NavMinimap, NavTile, SampleBrowser, TextInputModal, }; +use cagire_forth::Forth; use super::{ dict_view, engine_view, help_view, main_view, options_view, patterns_view, title_view, }; -fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor, cache: &std::cell::RefCell>) -> String { +fn compute_stack_display( + lines: &[String], + editor: &cagire_ratatui::Editor, + cache: &std::cell::RefCell>, +) -> String { let cursor_line = editor.cursor().0; let mut hasher = DefaultHasher::new(); @@ -46,7 +50,11 @@ fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor, cach } } - let partial: Vec<&str> = lines.iter().take(cursor_line + 1).map(|s| s.as_str()).collect(); + let partial: Vec<&str> = lines + .iter() + .take(cursor_line + 1) + .map(|s| s.as_str()) + .collect(); let script = partial.join("\n"); let result = if script.trim().is_empty() { @@ -70,7 +78,7 @@ fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor, cach speed: 1.0, fill: false, nudge_secs: 0.0, - cc_memory: None, + cc_access: None, #[cfg(feature = "desktop")] mouse_x: 0.5, #[cfg(feature = "desktop")] @@ -240,7 +248,11 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &Sequenc } fn header_height(width: u16) -> u16 { - if width >= 80 { 1 } else { 2 } + if width >= 80 { + 1 + } else { + 2 + } } fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) { @@ -284,11 +296,8 @@ fn render_header( .areas(area); (t, l, tp, b, p, s) } else { - let [line1, line2] = Layout::vertical([ - Constraint::Length(1), - Constraint::Length(1), - ]) - .areas(area); + let [line1, line2] = + Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area); let [t, l, tp, s] = Layout::horizontal([ Constraint::Min(12), @@ -298,11 +307,8 @@ fn render_header( ]) .areas(line1); - let [b, p] = Layout::horizontal([ - Constraint::Fill(1), - Constraint::Fill(2), - ]) - .areas(line2); + let [b, p] = + Layout::horizontal([Constraint::Fill(1), Constraint::Fill(2)]).areas(line2); (t, l, tp, b, p, s) }; @@ -323,7 +329,11 @@ fn render_header( // Fill indicator let fill = app.live_keys.fill(); - let fill_fg = if fill { theme.status.fill_on } else { theme.status.fill_off }; + let fill_fg = if fill { + theme.status.fill_on + } else { + theme.status.fill_off + }; let fill_style = Style::new().bg(theme.status.fill_bg).fg(fill_fg); frame.render_widget( Paragraph::new(if fill { "F" } else { "·" }) @@ -350,7 +360,9 @@ fn render_header( .as_deref() .map(|n| format!(" {n} ")) .unwrap_or_else(|| format!(" Bank {:02} ", app.editor_ctx.bank + 1)); - let bank_style = Style::new().bg(theme.header.bank_bg).fg(theme.ui.text_primary); + let bank_style = Style::new() + .bg(theme.header.bank_bg) + .fg(theme.ui.text_primary); frame.render_widget( Paragraph::new(bank_name) .style(bank_style) @@ -381,7 +393,9 @@ fn render_header( " {} · {} steps{}{}{} ", pattern_name, pattern.length, speed_info, page_info, iter_info ); - let pattern_style = Style::new().bg(theme.header.pattern_bg).fg(theme.ui.text_primary); + let pattern_style = Style::new() + .bg(theme.header.pattern_bg) + .fg(theme.ui.text_primary); frame.render_widget( Paragraph::new(pattern_text) .style(pattern_style) @@ -394,7 +408,9 @@ fn render_header( let peers = link.peers(); let voices = app.metrics.active_voices; let stats_text = format!(" CPU {cpu_pct:.0}% V:{voices} L:{peers} "); - let stats_style = Style::new().bg(theme.header.stats_bg).fg(theme.header.stats_fg); + let stats_style = Style::new() + .bg(theme.header.stats_bg) + .fg(theme.header.stats_fg); frame.render_widget( Paragraph::new(stats_text) .style(stats_style) @@ -528,11 +544,12 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term ConfirmModal::new("Confirm", &format!("Delete step {}?", step + 1), *selected) .render_centered(frame, term); } - Modal::ConfirmDeleteSteps { steps, selected, .. } => { + Modal::ConfirmDeleteSteps { + steps, selected, .. + } => { let nums: Vec = steps.iter().map(|s| format!("{:02}", s + 1)).collect(); let label = format!("Delete steps {}?", nums.join(", ")); - ConfirmModal::new("Confirm", &label, *selected) - .render_centered(frame, term); + ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term); } Modal::ConfirmResetPattern { pattern, selected, .. @@ -637,7 +654,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term let step_name = step.and_then(|s| s.name.as_ref()); let title = match (source_idx, step_name) { - (Some(src), Some(name)) => format!("Step {:02}: {} → {:02}", step_idx + 1, name, src + 1), + (Some(src), Some(name)) => { + format!("Step {:02}: {} → {:02}", step_idx + 1, name, src + 1) + } (None, Some(name)) => format!("Step {:02}: {}", step_idx + 1, name), (Some(src), None) => format!("Step {:02} → {:02}", step_idx + 1, src + 1), (None, None) => format!("Step {:02}", step_idx + 1), @@ -816,7 +835,11 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term ]); frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area); } else if app.editor_ctx.show_stack { - let stack_text = compute_stack_display(text_lines, &app.editor_ctx.editor, &app.editor_ctx.stack_cache); + let stack_text = compute_stack_display( + text_lines, + &app.editor_ctx.editor, + &app.editor_ctx.stack_cache, + ); let hint = Line::from(vec![ Span::styled("Esc", key), Span::styled(" save ", dim), @@ -910,7 +933,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term Style::default() .fg(theme.hint.key) .add_modifier(Modifier::BOLD), - Style::default().fg(theme.ui.text_primary).bg(theme.ui.surface), + Style::default() + .fg(theme.ui.text_primary) + .bg(theme.ui.surface), ) } else { ( @@ -962,7 +987,11 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term .skip(*scroll) .take(visible_rows) .map(|(i, (key, name, desc))| { - let bg = if i % 2 == 0 { theme.table.row_even } else { theme.table.row_odd }; + let bg = if i % 2 == 0 { + theme.table.row_even + } else { + theme.table.row_odd + }; Row::new(vec![ Cell::from(*key).style(Style::default().fg(theme.modal.confirm)), Cell::from(*name).style(Style::default().fg(theme.modal.input)), @@ -1004,7 +1033,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term Span::styled("Esc/?", Style::default().fg(theme.hint.key)), Span::styled(" close", Style::default().fg(theme.hint.text)), ]); - frame.render_widget(Paragraph::new(keybind_hint).alignment(Alignment::Right), hint_area); + frame.render_widget( + Paragraph::new(keybind_hint).alignment(Alignment::Right), + hint_area, + ); } } } diff --git a/tests/forth/harness.rs b/tests/forth/harness.rs index f50d88b..2214b8e 100644 --- a/tests/forth/harness.rs +++ b/tests/forth/harness.rs @@ -18,7 +18,7 @@ pub fn default_ctx() -> StepContext { speed: 1.0, fill: false, nudge_secs: 0.0, - cc_memory: None, + cc_access: None, } } diff --git a/tests/forth/midi.rs b/tests/forth/midi.rs index 7c106db..f17b09f 100644 --- a/tests/forth/midi.rs +++ b/tests/forth/midi.rs @@ -1,6 +1,10 @@ use crate::harness::{default_ctx, expect_outputs, forth}; -use cagire::forth::{CcMemory, StepContext}; -use std::sync::{Arc, Mutex}; +use cagire::forth::{CcAccess, StepContext}; +use cagire::midi::CcMemory; +use std::sync::Arc; + +#[allow(unused_imports)] +use cagire::forth::Value; #[test] fn test_midi_channel_set() { @@ -42,16 +46,13 @@ fn test_ccval_returns_zero_without_cc_memory() { #[test] fn test_ccval_reads_from_cc_memory() { - let cc_memory: CcMemory = Arc::new(Mutex::new([[[0u8; 128]; 16]; 4])); - { - let mut mem = cc_memory.lock().unwrap(); - mem[0][0][1] = 64; // device 0, channel 1 (0-indexed), CC 1, value 64 - mem[0][5][74] = 127; // device 0, channel 6 (0-indexed), CC 74, value 127 - } + let cc_memory = CcMemory::new(); + cc_memory.set_cc(0, 0, 1, 64); // device 0, channel 1 (0-indexed), CC 1, value 64 + cc_memory.set_cc(0, 5, 74, 127); // device 0, channel 6 (0-indexed), CC 74, value 127 let f = forth(); let ctx = StepContext { - cc_memory: Some(Arc::clone(&cc_memory)), + cc_access: Some(Arc::new(cc_memory.clone()) as Arc), ..default_ctx() }; @@ -122,7 +123,10 @@ fn test_midi_full_defaults() { fn test_midi_bend_center() { let outputs = expect_outputs("0.0 bend m.", 1); // 0.0 -> 8192 (center) - assert!(outputs[0].contains("/midi/bend/8191/chan/0") || outputs[0].contains("/midi/bend/8192/chan/0")); + assert!( + outputs[0].contains("/midi/bend/8191/chan/0") + || outputs[0].contains("/midi/bend/8192/chan/0") + ); } #[test]