diff --git a/Cargo.lock b/Cargo.lock index d0878f0..191c66c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -844,6 +844,7 @@ dependencies = [ "cpal", "crossterm", "itertools", + "lazy_static", "rand", "ratatui", "regex", diff --git a/Cargo.toml b/Cargo.toml index 5cb12ef..0463800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ ratatui = "0.23.0" crossterm = "0.27.0" regex = "1.10.1" itertools = "0.11.0" +lazy_static = "1.4.0" diff --git a/src/lib.rs b/src/lib.rs index ac81b05..6f48230 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,13 @@ +#[macro_use] +extern crate lazy_static; + +use crate::guitar::Note; + pub mod guitar; pub mod pitch_detector; pub mod recorder; pub mod ui; -use crate::guitar::Note; - #[derive(Debug)] pub enum AppEvent { PitchDetected(Note, f64), diff --git a/src/ui/app_color.rs b/src/ui/app_color.rs index 34b42c8..c65871c 100644 --- a/src/ui/app_color.rs +++ b/src/ui/app_color.rs @@ -1,12 +1,39 @@ +use crossterm::style::available_color_count; use ratatui::style::Color; +use std::env; -// pub const BORDER: Color = Color::Rgb(220, 215, 186); -// pub const TEXT: Color = Color::Rgb(220, 215, 186); -pub const BORDER: Color = Color::Rgb(242, 232, 207); -pub const TEXT_DARK: Color = Color::Rgb(0, 18, 25); -pub const TEXT_LIGHT: Color = Color::Rgb(242, 232, 207); -pub const BACKGROUND_DARK: Color = Color::Rgb(0, 18, 25); -pub const BACKGROUND_LIGHT: Color = Color::Rgb(242, 232, 207); -pub const GREEN: Color = Color::Rgb(56, 176, 0); -pub const RED: Color = Color::Rgb(193, 18, 30); -pub const BLUE: Color = Color::Rgb(72, 202, 228); +lazy_static! { + pub static ref BORDER: Color = + color(Color::Rgb(242, 232, 207), Color::Indexed(255), Color::White); + pub static ref TEXT_DARK: Color = + color(Color::Rgb(0, 18, 25), Color::Indexed(233), Color::Black); + pub static ref TEXT_LIGHT: Color = + color(Color::Rgb(242, 232, 207), Color::Indexed(255), Color::White); + pub static ref BACKGROUND_DARK: Color = + color(Color::Rgb(0, 18, 25), Color::Indexed(233), Color::Black); + pub static ref BACKGROUND_LIGHT: Color = + color(Color::Rgb(242, 232, 207), Color::Indexed(255), Color::White); + pub static ref GREEN: Color = color(Color::Rgb(56, 176, 0), Color::Indexed(34), Color::Green); + pub static ref RED: Color = color(Color::Rgb(193, 18, 30), Color::Indexed(160), Color::Red); + pub static ref BLUE: Color = color(Color::Rgb(72, 202, 228), Color::Indexed(27), Color::Blue); +} + +fn color(truecolor: Color, color256: Color, fallback: Color) -> Color { + if truecolor_support() { + truecolor + } else if color256_support() { + color256 + } else { + fallback + } +} + +fn truecolor_support() -> bool { + let colorterm = env::var("COLORTERM").unwrap_or_default(); + + colorterm == "truecolor" || colorterm == "24bit" +} + +fn color256_support() -> bool { + available_color_count() == 256 +} diff --git a/src/ui/audio_graph.rs b/src/ui/audio_graph.rs index 862b564..85412a7 100644 --- a/src/ui/audio_graph.rs +++ b/src/ui/audio_graph.rs @@ -36,13 +36,13 @@ impl StatefulWidget for AudioGraph { let datasets = vec![Dataset::default() .marker(Marker::Braille) .graph_type(GraphType::Line) - .style(Style::default().fg(app_color::BLUE)) + .style(Style::default().fg(*app_color::BLUE)) .data(&dataset)]; let chart = Chart::new(datasets) .x_axis(Axis::default().bounds([0.0, dataset.len() as f64])) .y_axis(Axis::default().bounds([0.0, 1.0])) - .style(Style::default().bg(app_color::BACKGROUND_DARK)); + .style(Style::default().bg(*app_color::BACKGROUND_DARK)); let mut rect = Rect { width: area.width - 16, diff --git a/src/ui/instructions.rs b/src/ui/instructions.rs index 35c2c01..d1e9ea3 100644 --- a/src/ui/instructions.rs +++ b/src/ui/instructions.rs @@ -41,7 +41,7 @@ impl Widget for Instruction { }; utils::center_rect_in_container(&mut rect, &area); - let paragraph = Paragraph::new(text).style(Style::default().fg(app_color::TEXT_LIGHT)); + let paragraph = Paragraph::new(text).style(Style::default().fg(*app_color::TEXT_LIGHT)); paragraph.render(rect, buf); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index eb038ef..6136838 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -52,7 +52,7 @@ pub fn render(event_stream: Receiver) -> Result<()> { // Background f.render_widget( - Block::default().style(Style::default().bg(app_color::BACKGROUND_DARK)), + Block::default().style(Style::default().bg(*app_color::BACKGROUND_DARK)), f.size(), ); @@ -61,7 +61,7 @@ pub fn render(event_stream: Receiver) -> Result<()> { .borders(Borders::ALL) .title("Tuning strings") .border_type(BorderType::Rounded) - .border_style(Style::default().fg(app_color::BORDER)), + .border_style(Style::default().fg(*app_color::BORDER)), tuning_strings_rect, ); @@ -70,7 +70,7 @@ pub fn render(event_stream: Receiver) -> Result<()> { .borders(Borders::ALL) .title("Instructions") .border_type(BorderType::Rounded) - .border_style(Style::default().fg(app_color::BORDER)), + .border_style(Style::default().fg(*app_color::BORDER)), instructions_rect, ); @@ -79,7 +79,7 @@ pub fn render(event_stream: Receiver) -> Result<()> { .borders(Borders::ALL) .title("Tuning bar") .border_type(BorderType::Rounded) - .border_style(Style::default().fg(app_color::BORDER)), + .border_style(Style::default().fg(*app_color::BORDER)), tuning_bar_rect, ); @@ -88,7 +88,7 @@ pub fn render(event_stream: Receiver) -> Result<()> { .borders(Borders::ALL) .title("Audio graph") .border_type(BorderType::Rounded) - .border_style(Style::default().fg(app_color::BORDER)), + .border_style(Style::default().fg(*app_color::BORDER)), graph_rect, ); diff --git a/src/ui/tuning_bar.rs b/src/ui/tuning_bar.rs index f694bbe..caa4e04 100644 --- a/src/ui/tuning_bar.rs +++ b/src/ui/tuning_bar.rs @@ -28,7 +28,7 @@ impl StatefulWidget for TuningBar { let block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Double) - .border_style(Style::default().fg(app_color::BORDER)); + .border_style(Style::default().fg(*app_color::BORDER)); let mut bar_area = Rect { x: area.x, @@ -84,7 +84,7 @@ fn render_accept_range(state: &State, bar_area: &Rect, buf: &mut Buffer) { let block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Thick) - .border_style(Style::default().fg(app_color::GREEN)); + .border_style(Style::default().fg(*app_color::GREEN)); block.render(rect, buf); } @@ -97,7 +97,7 @@ fn render_current_pitch(state: &State, bar_area: &Rect, buf: &mut Buffer) { let buf_index = bucket_index; let buf_chars = ("█", "▀", "▄"); - let style = Style::default().fg(app_color::RED); + let style = Style::default().fg(*app_color::RED); buf.get_mut(bar_area.x + buf_index, bar_area.y + 1) .set_symbol(buf_chars.0) .set_style(style); @@ -136,7 +136,7 @@ fn render_pitch_difference(state: &State, bar_area: &Rect, buf: &mut Buffer) { let mut text = Paragraph::new(diff).alignment(Alignment::Center); if state.accept_range.0 <= pitch && state.accept_range.1 >= pitch { - text = text.style(Style::default().fg(app_color::GREEN)); + text = text.style(Style::default().fg(*app_color::GREEN)); } text = text.add_modifier(Modifier::BOLD); diff --git a/src/ui/tuning_notes.rs b/src/ui/tuning_notes.rs index 10673d5..c021219 100644 --- a/src/ui/tuning_notes.rs +++ b/src/ui/tuning_notes.rs @@ -47,19 +47,19 @@ impl StatefulWidget for TuningNotes { } if state.tuned_notes.contains(tuning_note) { - surround_block = surround_block.border_style(Style::default().fg(app_color::GREEN)); - paragraph_style = paragraph_style.fg(app_color::GREEN); + surround_block = surround_block.border_style(Style::default().fg(*app_color::GREEN)); + paragraph_style = paragraph_style.fg(*app_color::GREEN); spans.push(Span::from(" ✓")); } if Some(index) == state.selected_note_index { surround_block = surround_block - .border_style(Style::default().bg(app_color::BACKGROUND_LIGHT)) + .border_style(Style::default().bg(*app_color::BACKGROUND_LIGHT)) .border_type(BorderType::Thick); paragraph_style = paragraph_style - .fg(app_color::TEXT_DARK) - .bg(app_color::BACKGROUND_LIGHT) + .fg(*app_color::TEXT_DARK) + .bg(*app_color::BACKGROUND_LIGHT) .add_modifier(Modifier::BOLD); spans.insert(0, Span::from("< ")); diff --git a/src/ui/tuning_pegs.rs b/src/ui/tuning_pegs.rs index 631d227..b96a124 100644 --- a/src/ui/tuning_pegs.rs +++ b/src/ui/tuning_pegs.rs @@ -44,14 +44,14 @@ impl StatefulWidget for TuningPegs { }; utils::center_rect_in_container(&mut rect, &area); - let paragraph = Paragraph::new(text).style(Style::default().fg(app_color::TEXT_LIGHT)); + let paragraph = Paragraph::new(text).style(Style::default().fg(*app_color::TEXT_LIGHT)); paragraph.render(rect, buf); } } fn peg(state: &State, index: usize) -> Span { match state.focus_peg { - Some(x) if x == index => Span::styled("⬤", Style::default().fg(app_color::TEXT_LIGHT)), + Some(x) if x == index => Span::styled("⬤", Style::default().fg(*app_color::TEXT_LIGHT)), _ => Span::from("◯"), } }