Monster commit: native version
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
|
||||
use ratatui::Frame;
|
||||
@@ -7,6 +7,7 @@ use ratatui::Frame;
|
||||
use crate::app::App;
|
||||
use crate::model::{Word, WORDS};
|
||||
use crate::state::DictFocus;
|
||||
use crate::theme::{dict, search};
|
||||
|
||||
const CATEGORIES: &[&str] = &[
|
||||
// Forth core
|
||||
@@ -61,10 +62,10 @@ fn render_header(frame: &mut Frame, area: Rect) {
|
||||
pushes 7. This page lists all words with their signature ( inputs -- outputs ).";
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(Color::Rgb(60, 60, 70)))
|
||||
.border_style(Style::new().fg(dict::BORDER_NORMAL))
|
||||
.title("Dictionary");
|
||||
let para = Paragraph::new(desc)
|
||||
.style(Style::new().fg(Color::Rgb(140, 145, 155)))
|
||||
.style(Style::new().fg(dict::HEADER_DESC))
|
||||
.wrap(Wrap { trim: false })
|
||||
.block(block);
|
||||
frame.render_widget(para, area);
|
||||
@@ -79,20 +80,20 @@ fn render_categories(frame: &mut Frame, app: &App, area: Rect, dimmed: bool) {
|
||||
.map(|(i, name)| {
|
||||
let is_selected = i == app.ui.dict_category;
|
||||
let style = if dimmed {
|
||||
Style::new().fg(Color::Rgb(80, 80, 90))
|
||||
Style::new().fg(dict::CATEGORY_DIMMED)
|
||||
} else if is_selected && focused {
|
||||
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(dict::CATEGORY_FOCUSED).add_modifier(Modifier::BOLD)
|
||||
} else if is_selected {
|
||||
Style::new().fg(Color::Cyan)
|
||||
Style::new().fg(dict::CATEGORY_SELECTED)
|
||||
} else {
|
||||
Style::new().fg(Color::White)
|
||||
Style::new().fg(dict::CATEGORY_NORMAL)
|
||||
};
|
||||
let prefix = if is_selected && !dimmed { "> " } else { " " };
|
||||
ListItem::new(format!("{prefix}{name}")).style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let border_color = if focused { Color::Yellow } else { Color::Rgb(60, 60, 70) };
|
||||
let border_color = if focused { dict::BORDER_FOCUSED } else { dict::BORDER_NORMAL };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
@@ -142,12 +143,12 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
let mut lines: Vec<RLine> = Vec::new();
|
||||
|
||||
for word in &words {
|
||||
let name_bg = Color::Rgb(40, 50, 60);
|
||||
let name_bg = dict::WORD_BG;
|
||||
let name_style = Style::new()
|
||||
.fg(Color::Green)
|
||||
.fg(dict::WORD_NAME)
|
||||
.bg(name_bg)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let alias_style = Style::new().fg(Color::DarkGray).bg(name_bg);
|
||||
let alias_style = Style::new().fg(dict::ALIAS).bg(name_bg);
|
||||
let name_text = if word.aliases.is_empty() {
|
||||
format!(" {}", word.name)
|
||||
} else {
|
||||
@@ -167,19 +168,19 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
]));
|
||||
}
|
||||
|
||||
let stack_style = Style::new().fg(Color::Magenta);
|
||||
let stack_style = Style::new().fg(dict::STACK_SIG);
|
||||
lines.push(RLine::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(word.stack.to_string(), stack_style),
|
||||
]));
|
||||
|
||||
let desc_style = Style::new().fg(Color::White);
|
||||
let desc_style = Style::new().fg(dict::DESCRIPTION);
|
||||
lines.push(RLine::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(word.desc.to_string(), desc_style),
|
||||
]));
|
||||
|
||||
let example_style = Style::new().fg(Color::Rgb(120, 130, 140));
|
||||
let example_style = Style::new().fg(dict::EXAMPLE);
|
||||
lines.push(RLine::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(format!("e.g. {}", word.example), example_style),
|
||||
@@ -205,7 +206,7 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
let category = CATEGORIES[app.ui.dict_category];
|
||||
format!("{category} ({} words)", words.len())
|
||||
};
|
||||
let border_color = if focused { Color::Yellow } else { Color::Rgb(60, 60, 70) };
|
||||
let border_color = if focused { dict::BORDER_FOCUSED } else { dict::BORDER_NORMAL };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
@@ -216,9 +217,9 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
|
||||
fn render_search_bar(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let style = if app.ui.dict_search_active {
|
||||
Style::new().fg(Color::Yellow)
|
||||
Style::new().fg(search::ACTIVE)
|
||||
} else {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
Style::new().fg(search::INACTIVE)
|
||||
};
|
||||
let cursor = if app.ui.dict_search_active { "_" } else { "" };
|
||||
let text = format!(" /{}{}", app.ui.dict_search_query, cursor);
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
use cagire_ratatui::ListSelect;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph, Row, Table};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::state::{DeviceKind, EngineSection, SettingKind};
|
||||
use crate::theme::{engine, meter};
|
||||
use crate::widgets::{Orientation, Scope, Spectrum};
|
||||
|
||||
const HEADER_COLOR: Color = Color::Rgb(100, 160, 180);
|
||||
const DIVIDER_COLOR: Color = Color::Rgb(60, 65, 70);
|
||||
const SCROLL_INDICATOR_COLOR: Color = Color::Rgb(80, 85, 95);
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let [left_col, _, right_col] = Layout::horizontal([
|
||||
Constraint::Percentage(55),
|
||||
@@ -29,7 +26,7 @@ fn render_settings_section(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Engine ")
|
||||
.border_style(Style::new().fg(Color::Magenta));
|
||||
.border_style(Style::new().fg(engine::BORDER_MAGENTA));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
@@ -125,7 +122,7 @@ fn render_settings_section(frame: &mut Frame, app: &App, area: Rect) {
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(SCROLL_INDICATOR_COLOR);
|
||||
let indicator_style = Style::new().fg(engine::SCROLL_INDICATOR);
|
||||
let indicator_x = padded.x + padded.width.saturating_sub(1);
|
||||
|
||||
if scroll_offset > 0 {
|
||||
@@ -158,14 +155,14 @@ fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Scope ")
|
||||
.border_style(Style::new().fg(Color::Green));
|
||||
.border_style(Style::new().fg(engine::BORDER_GREEN));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
let scope = Scope::new(&app.metrics.scope)
|
||||
.orientation(Orientation::Horizontal)
|
||||
.color(Color::Green);
|
||||
.color(meter::LOW);
|
||||
frame.render_widget(scope, inner);
|
||||
}
|
||||
|
||||
@@ -173,7 +170,7 @@ fn render_spectrum(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Spectrum ")
|
||||
.border_style(Style::new().fg(Color::Cyan));
|
||||
.border_style(Style::new().fg(engine::BORDER_CYAN));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
@@ -210,16 +207,16 @@ fn render_section_header(frame: &mut Frame, title: &str, focused: bool, area: Re
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
let header_style = if focused {
|
||||
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(engine::HEADER_FOCUSED).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new().fg(HEADER_COLOR).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(engine::HEADER).add_modifier(Modifier::BOLD)
|
||||
};
|
||||
|
||||
frame.render_widget(Paragraph::new(title).style(header_style), header_area);
|
||||
|
||||
let divider = "─".repeat(area.width as usize);
|
||||
frame.render_widget(
|
||||
Paragraph::new(divider).style(Style::new().fg(DIVIDER_COLOR)),
|
||||
Paragraph::new(divider).style(Style::new().fg(engine::DIVIDER)),
|
||||
divider_area,
|
||||
);
|
||||
}
|
||||
@@ -254,7 +251,7 @@ fn render_devices(frame: &mut Frame, app: &App, area: Rect) {
|
||||
section_focused,
|
||||
);
|
||||
|
||||
let sep_style = Style::new().fg(Color::Rgb(60, 65, 75));
|
||||
let sep_style = Style::new().fg(engine::SEPARATOR);
|
||||
let sep_lines: Vec<Line> = (0..separator.height)
|
||||
.map(|_| Line::from(Span::styled("│", sep_style)))
|
||||
.collect();
|
||||
@@ -289,11 +286,11 @@ fn render_device_column(
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).areas(area);
|
||||
|
||||
let label_style = if focused {
|
||||
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(engine::FOCUSED).add_modifier(Modifier::BOLD)
|
||||
} else if section_focused {
|
||||
Style::new().fg(Color::Rgb(150, 155, 165))
|
||||
Style::new().fg(engine::LABEL_FOCUSED)
|
||||
} else {
|
||||
Style::new().fg(Color::Rgb(100, 105, 115))
|
||||
Style::new().fg(engine::LABEL_DIM)
|
||||
};
|
||||
|
||||
let arrow = if focused { "> " } else { " " };
|
||||
@@ -318,10 +315,10 @@ fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
render_section_header(frame, "SETTINGS", section_focused, header_area);
|
||||
|
||||
let highlight = Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD);
|
||||
let normal = Style::new().fg(Color::White);
|
||||
let label_style = Style::new().fg(Color::Rgb(120, 125, 135));
|
||||
let value_style = Style::new().fg(Color::Rgb(180, 180, 190));
|
||||
let highlight = Style::new().fg(engine::FOCUSED).add_modifier(Modifier::BOLD);
|
||||
let normal = Style::new().fg(engine::NORMAL);
|
||||
let label_style = Style::new().fg(engine::LABEL);
|
||||
let value_style = Style::new().fg(engine::VALUE);
|
||||
|
||||
let channels_focused = section_focused && app.audio.setting_kind == SettingKind::Channels;
|
||||
let buffer_focused = section_focused && app.audio.setting_kind == SettingKind::BufferSize;
|
||||
@@ -438,8 +435,8 @@ fn render_samples(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let header_text = format!("SAMPLES {path_count} paths · {sample_count} indexed");
|
||||
render_section_header(frame, &header_text, section_focused, header_area);
|
||||
|
||||
let dim = Style::new().fg(Color::Rgb(80, 85, 95));
|
||||
let path_style = Style::new().fg(Color::Rgb(120, 125, 135));
|
||||
let dim = Style::new().fg(engine::DIM);
|
||||
let path_style = Style::new().fg(engine::PATH);
|
||||
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
if app.audio.config.sample_paths.is_empty() {
|
||||
@@ -470,15 +467,15 @@ fn render_samples(frame: &mut Frame, app: &App, area: Rect) {
|
||||
frame.render_widget(Paragraph::new(lines), content_area);
|
||||
|
||||
let hint_style = if section_focused {
|
||||
Style::new().fg(Color::Rgb(180, 180, 100))
|
||||
Style::new().fg(engine::HINT_ACTIVE)
|
||||
} else {
|
||||
Style::new().fg(Color::Rgb(60, 60, 70))
|
||||
Style::new().fg(engine::HINT_INACTIVE)
|
||||
};
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("A", hint_style),
|
||||
Span::styled(":add ", Style::new().fg(Color::Rgb(80, 85, 95))),
|
||||
Span::styled(":add ", Style::new().fg(engine::DIM)),
|
||||
Span::styled("D", hint_style),
|
||||
Span::styled(":remove", Style::new().fg(Color::Rgb(80, 85, 95))),
|
||||
Span::styled(":remove", Style::new().fg(engine::DIM)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint), hint_area);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use minimad::{Composite, CompositeStyle, Compound, Line};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
|
||||
use ratatui::Frame;
|
||||
use tui_big_text::{BigText, PixelSize};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::theme::{dict, markdown, search, ui};
|
||||
use crate::views::highlight;
|
||||
|
||||
// To add a new help topic: drop a .md file in docs/ and add one line here.
|
||||
@@ -37,9 +38,9 @@ fn render_topics(frame: &mut Frame, app: &App, area: Rect) {
|
||||
.map(|(i, (name, _))| {
|
||||
let selected = i == app.ui.help_topic;
|
||||
let style = if selected {
|
||||
Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(dict::CATEGORY_SELECTED).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new().fg(Color::White)
|
||||
Style::new().fg(ui::TEXT_PRIMARY)
|
||||
};
|
||||
let prefix = if selected { "> " } else { " " };
|
||||
ListItem::new(format!("{prefix}{name}")).style(style)
|
||||
@@ -63,13 +64,13 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
||||
.areas(area);
|
||||
let big_title = BigText::builder()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(Style::new().cyan().bold())
|
||||
.style(Style::new().fg(markdown::H1).bold())
|
||||
.lines(vec!["CAGIRE".into()])
|
||||
.centered()
|
||||
.build();
|
||||
let subtitle = Paragraph::new(RLine::from(Span::styled(
|
||||
"A Forth Sequencer",
|
||||
Style::new().fg(Color::White),
|
||||
Style::new().fg(ui::TEXT_PRIMARY),
|
||||
)))
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
let [big_area, subtitle_area] =
|
||||
@@ -126,9 +127,9 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
fn render_search_bar(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let style = if app.ui.help_search_active {
|
||||
Style::new().fg(Color::Yellow)
|
||||
Style::new().fg(search::ACTIVE)
|
||||
} else {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
Style::new().fg(search::INACTIVE)
|
||||
};
|
||||
let cursor = if app.ui.help_search_active { "█" } else { "" };
|
||||
let text = format!(" /{}{cursor}", app.ui.help_search_query);
|
||||
@@ -145,7 +146,7 @@ fn highlight_line<'a>(line: RLine<'a>, query: &str) -> RLine<'a> {
|
||||
}
|
||||
let content = span.content.to_string();
|
||||
let base_style = span.style;
|
||||
let hl_style = base_style.bg(Color::Yellow).fg(Color::Black);
|
||||
let hl_style = base_style.bg(search::MATCH_BG).fg(search::MATCH_FG);
|
||||
let mut start = 0;
|
||||
let lower_bytes = lower.as_bytes();
|
||||
let query_bytes = query.as_bytes();
|
||||
@@ -185,7 +186,7 @@ pub fn find_match(query: &str) -> Option<(usize, usize)> {
|
||||
}
|
||||
|
||||
fn code_border_style() -> Style {
|
||||
Style::new().fg(Color::Rgb(60, 60, 70))
|
||||
Style::new().fg(markdown::CODE_BORDER)
|
||||
}
|
||||
|
||||
fn preprocess_underscores(md: &str) -> String {
|
||||
@@ -270,14 +271,14 @@ fn parse_markdown(md: &str) -> Vec<RLine<'static>> {
|
||||
fn composite_to_line(composite: Composite) -> RLine<'static> {
|
||||
let base_style = match composite.style {
|
||||
CompositeStyle::Header(1) => Style::new()
|
||||
.fg(Color::Cyan)
|
||||
.fg(markdown::H1)
|
||||
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
|
||||
CompositeStyle::Header(2) => Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD),
|
||||
CompositeStyle::Header(_) => Style::new().fg(Color::Magenta).add_modifier(Modifier::BOLD),
|
||||
CompositeStyle::ListItem(_) => Style::new().fg(Color::White),
|
||||
CompositeStyle::Quote => Style::new().fg(Color::Rgb(150, 150, 150)),
|
||||
CompositeStyle::Code => Style::new().fg(Color::Green),
|
||||
CompositeStyle::Paragraph => Style::new().fg(Color::White),
|
||||
CompositeStyle::Header(2) => Style::new().fg(markdown::H2).add_modifier(Modifier::BOLD),
|
||||
CompositeStyle::Header(_) => Style::new().fg(markdown::H3).add_modifier(Modifier::BOLD),
|
||||
CompositeStyle::ListItem(_) => Style::new().fg(markdown::LIST),
|
||||
CompositeStyle::Quote => Style::new().fg(markdown::QUOTE),
|
||||
CompositeStyle::Code => Style::new().fg(markdown::CODE),
|
||||
CompositeStyle::Paragraph => Style::new().fg(markdown::TEXT),
|
||||
};
|
||||
|
||||
let prefix = match composite.style {
|
||||
@@ -308,7 +309,7 @@ fn compound_to_spans(compound: Compound, base: Style, out: &mut Vec<Span<'static
|
||||
style = style.add_modifier(Modifier::ITALIC);
|
||||
}
|
||||
if compound.code {
|
||||
style = Style::new().fg(Color::Green);
|
||||
style = Style::new().fg(markdown::CODE);
|
||||
}
|
||||
if compound.strikeout {
|
||||
style = style.add_modifier(Modifier::CROSSED_OUT);
|
||||
@@ -316,7 +317,7 @@ fn compound_to_spans(compound: Compound, base: Style, out: &mut Vec<Span<'static
|
||||
|
||||
let src = compound.src.to_string();
|
||||
let link_style = Style::new()
|
||||
.fg(Color::Rgb(120, 200, 180))
|
||||
.fg(markdown::LINK)
|
||||
.add_modifier(Modifier::UNDERLINED);
|
||||
|
||||
let mut rest = src.as_str();
|
||||
@@ -336,7 +337,7 @@ fn compound_to_spans(compound: Compound, base: Style, out: &mut Vec<Span<'static
|
||||
out.push(Span::styled(text.to_string(), link_style));
|
||||
out.push(Span::styled(
|
||||
format!(" ({url})"),
|
||||
Style::new().fg(Color::Rgb(100, 100, 100)),
|
||||
Style::new().fg(markdown::LINK_URL),
|
||||
));
|
||||
}
|
||||
rest = &rest[url_start + url_end + 1..];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
|
||||
use crate::model::{SourceSpan, WordCompile, WORDS};
|
||||
use crate::theme::syntax;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
@@ -24,61 +25,34 @@ pub enum TokenKind {
|
||||
|
||||
impl TokenKind {
|
||||
pub fn style(self) -> Style {
|
||||
match self {
|
||||
TokenKind::Emit => Style::default()
|
||||
.fg(Color::Rgb(255, 255, 255))
|
||||
.bg(Color::Rgb(140, 50, 50))
|
||||
.add_modifier(Modifier::BOLD),
|
||||
TokenKind::Number => Style::default()
|
||||
.fg(Color::Rgb(255, 200, 120))
|
||||
.bg(Color::Rgb(60, 40, 15)),
|
||||
TokenKind::String => Style::default()
|
||||
.fg(Color::Rgb(150, 230, 150))
|
||||
.bg(Color::Rgb(20, 55, 20)),
|
||||
TokenKind::Comment => Style::default()
|
||||
.fg(Color::Rgb(100, 100, 100))
|
||||
.bg(Color::Rgb(18, 18, 18)),
|
||||
TokenKind::Keyword => Style::default()
|
||||
.fg(Color::Rgb(230, 130, 230))
|
||||
.bg(Color::Rgb(55, 25, 55)),
|
||||
TokenKind::StackOp => Style::default()
|
||||
.fg(Color::Rgb(130, 190, 240))
|
||||
.bg(Color::Rgb(20, 40, 70)),
|
||||
TokenKind::Operator => Style::default()
|
||||
.fg(Color::Rgb(220, 220, 140))
|
||||
.bg(Color::Rgb(45, 45, 20)),
|
||||
TokenKind::Sound => Style::default()
|
||||
.fg(Color::Rgb(100, 240, 220))
|
||||
.bg(Color::Rgb(15, 60, 55)),
|
||||
TokenKind::Param => Style::default()
|
||||
.fg(Color::Rgb(190, 160, 240))
|
||||
.bg(Color::Rgb(45, 30, 70)),
|
||||
TokenKind::Context => Style::default()
|
||||
.fg(Color::Rgb(240, 190, 120))
|
||||
.bg(Color::Rgb(60, 45, 20)),
|
||||
TokenKind::Note => Style::default()
|
||||
.fg(Color::Rgb(120, 220, 170))
|
||||
.bg(Color::Rgb(20, 55, 40)),
|
||||
TokenKind::Interval => Style::default()
|
||||
.fg(Color::Rgb(170, 220, 120))
|
||||
.bg(Color::Rgb(35, 55, 20)),
|
||||
TokenKind::Variable => Style::default()
|
||||
.fg(Color::Rgb(220, 150, 190))
|
||||
.bg(Color::Rgb(60, 30, 50)),
|
||||
TokenKind::Vary => Style::default()
|
||||
.fg(Color::Rgb(230, 230, 100))
|
||||
.bg(Color::Rgb(55, 55, 15)),
|
||||
TokenKind::Generator => Style::default()
|
||||
.fg(Color::Rgb(100, 220, 180))
|
||||
.bg(Color::Rgb(15, 55, 45)),
|
||||
TokenKind::Default => Style::default()
|
||||
.fg(Color::Rgb(160, 160, 160))
|
||||
.bg(Color::Rgb(25, 25, 25)),
|
||||
let (fg, bg) = match self {
|
||||
TokenKind::Emit => syntax::EMIT,
|
||||
TokenKind::Number => syntax::NUMBER,
|
||||
TokenKind::String => syntax::STRING,
|
||||
TokenKind::Comment => syntax::COMMENT,
|
||||
TokenKind::Keyword => syntax::KEYWORD,
|
||||
TokenKind::StackOp => syntax::STACK_OP,
|
||||
TokenKind::Operator => syntax::OPERATOR,
|
||||
TokenKind::Sound => syntax::SOUND,
|
||||
TokenKind::Param => syntax::PARAM,
|
||||
TokenKind::Context => syntax::CONTEXT,
|
||||
TokenKind::Note => syntax::NOTE,
|
||||
TokenKind::Interval => syntax::INTERVAL,
|
||||
TokenKind::Variable => syntax::VARIABLE,
|
||||
TokenKind::Vary => syntax::VARY,
|
||||
TokenKind::Generator => syntax::GENERATOR,
|
||||
TokenKind::Default => syntax::DEFAULT,
|
||||
};
|
||||
let style = Style::default().fg(fg).bg(bg);
|
||||
if matches!(self, TokenKind::Emit) {
|
||||
style.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gap_style() -> Style {
|
||||
Style::default().bg(Color::Rgb(25, 25, 25))
|
||||
Style::default().bg(syntax::GAP_BG)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,9 +205,6 @@ pub fn highlight_line_with_runtime(
|
||||
let tokens = tokenize_line(line);
|
||||
let mut result = Vec::new();
|
||||
let mut last_end = 0;
|
||||
|
||||
let executed_bg = Color::Rgb(40, 35, 50);
|
||||
let selected_bg = Color::Rgb(80, 60, 20);
|
||||
let gap_style = TokenKind::gap_style();
|
||||
|
||||
for token in tokens {
|
||||
@@ -253,9 +224,9 @@ pub fn highlight_line_with_runtime(
|
||||
style = style.add_modifier(Modifier::UNDERLINED);
|
||||
}
|
||||
if is_selected {
|
||||
style = style.bg(selected_bg).add_modifier(Modifier::BOLD);
|
||||
style = style.bg(syntax::SELECTED_BG).add_modifier(Modifier::BOLD);
|
||||
} else if is_executed {
|
||||
style = style.bg(executed_bg);
|
||||
style = style.bg(syntax::EXECUTED_BG);
|
||||
}
|
||||
|
||||
result.push((style, line[token.start..token.end].to_string()));
|
||||
|
||||
@@ -5,6 +5,7 @@ use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::engine::SequencerSnapshot;
|
||||
use crate::theme::{meter, selection, tile, ui};
|
||||
use crate::widgets::{Orientation, Scope, Spectrum, VuMeter};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
||||
@@ -67,7 +68,7 @@ fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot,
|
||||
if area.width < 50 {
|
||||
let msg = Paragraph::new("Terminal too narrow")
|
||||
.alignment(Alignment::Center)
|
||||
.style(Style::new().fg(Color::Rgb(120, 125, 135)));
|
||||
.style(Style::new().fg(ui::TEXT_MUTED));
|
||||
frame.render_widget(msg, area);
|
||||
return;
|
||||
}
|
||||
@@ -147,41 +148,27 @@ fn render_tile(
|
||||
};
|
||||
|
||||
let link_color = step.and_then(|s| s.source).map(|src| {
|
||||
const BRIGHT: [(u8, u8, u8); 5] = [
|
||||
(180, 140, 220),
|
||||
(220, 140, 170),
|
||||
(220, 180, 130),
|
||||
(130, 180, 220),
|
||||
(170, 220, 140),
|
||||
];
|
||||
const DIM: [(u8, u8, u8); 5] = [
|
||||
(90, 70, 120),
|
||||
(120, 70, 85),
|
||||
(120, 90, 65),
|
||||
(65, 90, 120),
|
||||
(85, 120, 70),
|
||||
];
|
||||
let i = src % 5;
|
||||
(BRIGHT[i], DIM[i])
|
||||
(tile::LINK_BRIGHT[i], tile::LINK_DIM[i])
|
||||
});
|
||||
|
||||
let (bg, fg) = match (is_playing, is_active, is_selected, is_linked, in_selection) {
|
||||
(true, true, _, _, _) => (Color::Rgb(195, 85, 65), Color::White),
|
||||
(true, false, _, _, _) => (Color::Rgb(180, 120, 45), Color::Black),
|
||||
(true, true, _, _, _) => (tile::PLAYING_ACTIVE_BG, tile::PLAYING_ACTIVE_FG),
|
||||
(true, false, _, _, _) => (tile::PLAYING_INACTIVE_BG, tile::PLAYING_INACTIVE_FG),
|
||||
(false, true, true, true, _) => {
|
||||
let (r, g, b) = link_color.unwrap().0;
|
||||
(Color::Rgb(r, g, b), Color::Black)
|
||||
(Color::Rgb(r, g, b), selection::CURSOR_FG)
|
||||
}
|
||||
(false, true, true, false, _) => (Color::Rgb(0, 220, 180), Color::Black),
|
||||
(false, true, _, _, true) => (Color::Rgb(0, 170, 140), Color::Black),
|
||||
(false, true, true, false, _) => (tile::ACTIVE_SELECTED_BG, selection::CURSOR_FG),
|
||||
(false, true, _, _, true) => (tile::ACTIVE_IN_RANGE_BG, selection::CURSOR_FG),
|
||||
(false, true, false, true, _) => {
|
||||
let (r, g, b) = link_color.unwrap().1;
|
||||
(Color::Rgb(r, g, b), Color::White)
|
||||
(Color::Rgb(r, g, b), tile::ACTIVE_FG)
|
||||
}
|
||||
(false, true, false, false, _) => (Color::Rgb(45, 106, 95), Color::White),
|
||||
(false, false, true, _, _) => (Color::Rgb(80, 180, 255), Color::Black),
|
||||
(false, false, _, _, true) => (Color::Rgb(60, 140, 200), Color::Black),
|
||||
(false, false, false, _, _) => (Color::Rgb(45, 48, 55), Color::Rgb(120, 125, 135)),
|
||||
(false, true, false, false, _) => (tile::ACTIVE_BG, tile::ACTIVE_FG),
|
||||
(false, false, true, _, _) => (selection::SELECTED, selection::CURSOR_FG),
|
||||
(false, false, _, _, true) => (selection::IN_RANGE, selection::CURSOR_FG),
|
||||
(false, false, false, _, _) => (tile::INACTIVE_BG, tile::INACTIVE_FG),
|
||||
};
|
||||
|
||||
let source_idx = step.and_then(|s| s.source);
|
||||
@@ -246,7 +233,7 @@ fn render_tile(
|
||||
fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let scope = Scope::new(&app.metrics.scope)
|
||||
.orientation(Orientation::Horizontal)
|
||||
.color(Color::Green);
|
||||
.color(meter::LOW);
|
||||
frame.render_widget(scope, area);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::Frame;
|
||||
@@ -7,17 +7,13 @@ use ratatui::Frame;
|
||||
use crate::app::App;
|
||||
use crate::engine::LinkState;
|
||||
use crate::state::OptionsFocus;
|
||||
|
||||
const LABEL_COLOR: Color = Color::Rgb(120, 125, 135);
|
||||
const HEADER_COLOR: Color = Color::Rgb(100, 160, 180);
|
||||
const DIVIDER_COLOR: Color = Color::Rgb(60, 65, 70);
|
||||
const SCROLL_INDICATOR_COLOR: Color = Color::Rgb(80, 85, 95);
|
||||
use crate::theme::{hint, link_status, modal, ui, values};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Options ")
|
||||
.border_style(Style::new().fg(Color::Cyan));
|
||||
.border_style(Style::new().fg(modal::INPUT));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
@@ -36,11 +32,11 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let enabled = link.is_enabled();
|
||||
let peers = link.peers();
|
||||
let (status_text, status_color) = if !enabled {
|
||||
("DISABLED", Color::Rgb(120, 60, 60))
|
||||
("DISABLED", link_status::DISABLED)
|
||||
} else if peers > 0 {
|
||||
("CONNECTED", Color::Rgb(60, 120, 60))
|
||||
("CONNECTED", link_status::CONNECTED)
|
||||
} else {
|
||||
("LISTENING", Color::Rgb(120, 120, 60))
|
||||
("LISTENING", link_status::LISTENING)
|
||||
};
|
||||
let peer_text = if enabled && peers > 0 {
|
||||
if peers == 1 {
|
||||
@@ -55,14 +51,14 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let link_header = Line::from(vec![
|
||||
Span::styled(
|
||||
"ABLETON LINK",
|
||||
Style::new().fg(HEADER_COLOR).add_modifier(Modifier::BOLD),
|
||||
Style::new().fg(ui::HEADER).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
status_text,
|
||||
Style::new().fg(status_color).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(peer_text, Style::new().fg(LABEL_COLOR)),
|
||||
Span::styled(peer_text, Style::new().fg(ui::TEXT_MUTED)),
|
||||
]);
|
||||
|
||||
// Prepare values
|
||||
@@ -72,10 +68,8 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let beat_str = format!("{:.2}", link.beat());
|
||||
let phase_str = format!("{:.2}", link.phase());
|
||||
|
||||
let tempo_style = Style::new()
|
||||
.fg(Color::Rgb(220, 180, 100))
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let value_style = Style::new().fg(Color::Rgb(140, 145, 155));
|
||||
let tempo_style = Style::new().fg(values::TEMPO).add_modifier(Modifier::BOLD);
|
||||
let value_style = Style::new().fg(values::VALUE);
|
||||
|
||||
// Build flat list of all lines
|
||||
let lines: Vec<Line> = vec![
|
||||
@@ -178,7 +172,7 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
frame.render_widget(Paragraph::new(visible_lines), padded);
|
||||
|
||||
// Render scroll indicators
|
||||
let indicator_style = Style::new().fg(SCROLL_INDICATOR_COLOR);
|
||||
let indicator_style = Style::new().fg(ui::TEXT_DIM);
|
||||
let indicator_x = padded.x + padded.width.saturating_sub(1);
|
||||
|
||||
if scroll_offset > 0 {
|
||||
@@ -201,21 +195,21 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
fn render_section_header(title: &str) -> Line<'static> {
|
||||
Line::from(Span::styled(
|
||||
title.to_string(),
|
||||
Style::new().fg(HEADER_COLOR).add_modifier(Modifier::BOLD),
|
||||
Style::new().fg(ui::HEADER).add_modifier(Modifier::BOLD),
|
||||
))
|
||||
}
|
||||
|
||||
fn render_divider(width: usize) -> Line<'static> {
|
||||
Line::from(Span::styled(
|
||||
"─".repeat(width),
|
||||
Style::new().fg(DIVIDER_COLOR),
|
||||
Style::new().fg(ui::BORDER),
|
||||
))
|
||||
}
|
||||
|
||||
fn render_option_line<'a>(label: &'a str, value: &'a str, focused: bool) -> Line<'a> {
|
||||
let highlight = Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD);
|
||||
let normal = Style::new().fg(Color::White);
|
||||
let label_style = Style::new().fg(LABEL_COLOR);
|
||||
let highlight = Style::new().fg(hint::KEY).add_modifier(Modifier::BOLD);
|
||||
let normal = Style::new().fg(ui::TEXT_PRIMARY);
|
||||
let label_style = Style::new().fg(ui::TEXT_MUTED);
|
||||
|
||||
let prefix = if focused { "> " } else { " " };
|
||||
let prefix_style = if focused { highlight } else { normal };
|
||||
@@ -238,7 +232,7 @@ fn render_option_line<'a>(label: &'a str, value: &'a str, focused: bool) -> Line
|
||||
}
|
||||
|
||||
fn render_readonly_line<'a>(label: &'a str, value: &'a str, value_style: Style) -> Line<'a> {
|
||||
let label_style = Style::new().fg(LABEL_COLOR);
|
||||
let label_style = Style::new().fg(ui::TEXT_MUTED);
|
||||
let label_width = 20;
|
||||
let padded_label = format!("{label:<label_width$}");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Paragraph};
|
||||
use ratatui::Frame;
|
||||
@@ -8,6 +8,7 @@ use crate::app::App;
|
||||
use crate::engine::SequencerSnapshot;
|
||||
use crate::model::{MAX_BANKS, MAX_PATTERNS};
|
||||
use crate::state::PatternsColumn;
|
||||
use crate::theme::{list, selection, ui};
|
||||
|
||||
const MIN_ROW_HEIGHT: u16 = 1;
|
||||
|
||||
@@ -31,11 +32,7 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
let [title_area, inner] =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);
|
||||
|
||||
let title_color = if is_focused {
|
||||
Color::Rgb(100, 160, 180)
|
||||
} else {
|
||||
Color::Rgb(70, 75, 85)
|
||||
};
|
||||
let title_color = if is_focused { ui::HEADER } else { ui::UNFOCUSED };
|
||||
let title = Paragraph::new("Banks")
|
||||
.style(Style::new().fg(title_color))
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
@@ -94,12 +91,12 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
let is_staged = banks_with_staged.contains(&idx);
|
||||
|
||||
let (bg, fg, prefix) = match (is_cursor, is_playing, is_staged) {
|
||||
(true, _, _) => (Color::Cyan, Color::Black, ""),
|
||||
(false, true, _) => (Color::Rgb(45, 80, 45), Color::Green, "> "),
|
||||
(false, false, true) => (Color::Rgb(80, 60, 100), Color::Magenta, "+ "),
|
||||
(false, false, false) if is_selected => (Color::Rgb(60, 65, 75), Color::White, ""),
|
||||
(false, false, false) if is_edit => (Color::Rgb(45, 106, 95), Color::White, ""),
|
||||
(false, false, false) => (Color::Reset, Color::Rgb(120, 125, 135), ""),
|
||||
(true, _, _) => (selection::CURSOR, selection::CURSOR_FG, ""),
|
||||
(false, true, _) => (list::PLAYING_BG, list::PLAYING_FG, "> "),
|
||||
(false, false, true) => (list::STAGED_PLAY_BG, list::STAGED_PLAY_FG, "+ "),
|
||||
(false, false, false) if is_selected => (list::HOVER_BG, list::HOVER_FG, ""),
|
||||
(false, false, false) if is_edit => (list::EDIT_BG, list::EDIT_FG, ""),
|
||||
(false, false, false) => (ui::BG, ui::TEXT_MUTED, ""),
|
||||
};
|
||||
|
||||
let name = app.project_state.project.banks[idx]
|
||||
@@ -139,7 +136,7 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(Color::Rgb(120, 125, 135));
|
||||
let indicator_style = Style::new().fg(ui::TEXT_MUTED);
|
||||
if scroll_offset > 0 {
|
||||
let indicator = Paragraph::new("▲")
|
||||
.style(indicator_style)
|
||||
@@ -163,11 +160,7 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
let [title_area, inner] =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);
|
||||
|
||||
let title_color = if is_focused {
|
||||
Color::Rgb(100, 160, 180)
|
||||
} else {
|
||||
Color::Rgb(70, 75, 85)
|
||||
};
|
||||
let title_color = if is_focused { ui::HEADER } else { ui::UNFOCUSED };
|
||||
|
||||
let bank = app.patterns_nav.bank_cursor;
|
||||
let bank_name = app.project_state.project.banks[bank].name.as_deref();
|
||||
@@ -256,13 +249,13 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
let is_staged_stop = staged_to_stop.contains(&idx);
|
||||
|
||||
let (bg, fg, prefix) = match (is_cursor, is_playing, is_staged_play, is_staged_stop) {
|
||||
(true, _, _, _) => (Color::Cyan, Color::Black, ""),
|
||||
(false, true, _, true) => (Color::Rgb(120, 60, 80), Color::Magenta, "- "),
|
||||
(false, true, _, false) => (Color::Rgb(45, 80, 45), Color::Green, "> "),
|
||||
(false, false, true, _) => (Color::Rgb(80, 60, 100), Color::Magenta, "+ "),
|
||||
(false, false, false, _) if is_selected => (Color::Rgb(60, 65, 75), Color::White, ""),
|
||||
(false, false, false, _) if is_edit => (Color::Rgb(45, 106, 95), Color::White, ""),
|
||||
(false, false, false, _) => (Color::Reset, Color::Rgb(120, 125, 135), ""),
|
||||
(true, _, _, _) => (selection::CURSOR, selection::CURSOR_FG, ""),
|
||||
(false, true, _, true) => (list::STAGED_STOP_BG, list::STAGED_STOP_FG, "- "),
|
||||
(false, true, _, false) => (list::PLAYING_BG, list::PLAYING_FG, "> "),
|
||||
(false, false, true, _) => (list::STAGED_PLAY_BG, list::STAGED_PLAY_FG, "+ "),
|
||||
(false, false, false, _) if is_selected => (list::HOVER_BG, list::HOVER_FG, ""),
|
||||
(false, false, false, _) if is_edit => (list::EDIT_BG, list::EDIT_FG, ""),
|
||||
(false, false, false, _) => (ui::BG, ui::TEXT_MUTED, ""),
|
||||
};
|
||||
|
||||
let pattern = &app.project_state.project.banks[bank].patterns[idx];
|
||||
@@ -321,7 +314,7 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(Color::Rgb(120, 125, 135));
|
||||
let indicator_style = Style::new().fg(ui::TEXT_MUTED);
|
||||
if scroll_offset > 0 {
|
||||
let indicator = Paragraph::new("▲")
|
||||
.style(indicator_style)
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::engine::{LinkState, SequencerSnapshot};
|
||||
use crate::model::{SourceSpan, StepContext, Value};
|
||||
use crate::page::Page;
|
||||
use crate::state::{FlashKind, Modal, PanelFocus, PatternField, SidePanel, StackCache};
|
||||
use crate::theme::{browser, flash, header, hint, modal, search, status, table, ui};
|
||||
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
|
||||
use crate::widgets::{
|
||||
ConfirmModal, ModalFrame, NavMinimap, NavTile, SampleBrowser, TextInputModal,
|
||||
@@ -132,10 +133,15 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &Sequenc
|
||||
let term = frame.area();
|
||||
|
||||
let bg_color = if app.ui.event_flash > 0.0 {
|
||||
let i = (app.ui.event_flash * app.ui.flash_brightness * 60.0) as u8;
|
||||
Color::Rgb(i, i, i)
|
||||
let t = (app.ui.event_flash * app.ui.flash_brightness).min(1.0);
|
||||
let (base_r, base_g, base_b) = ui::BG_RGB;
|
||||
let (tgt_r, tgt_g, tgt_b) = flash::EVENT_RGB;
|
||||
let r = base_r + ((tgt_r as f32 - base_r as f32) * t) as u8;
|
||||
let g = base_g + ((tgt_g as f32 - base_g as f32) * t) as u8;
|
||||
let b = base_b + ((tgt_b as f32 - base_b as f32) * t) as u8;
|
||||
Color::Rgb(r, g, b)
|
||||
} else {
|
||||
Color::Reset
|
||||
ui::BG
|
||||
};
|
||||
|
||||
let blank = " ".repeat(term.width as usize);
|
||||
@@ -294,11 +300,11 @@ fn render_header(
|
||||
|
||||
// Transport block
|
||||
let (transport_bg, transport_text) = if app.playback.playing {
|
||||
(Color::Rgb(30, 80, 30), " ▶ PLAYING ")
|
||||
(status::PLAYING_BG, " ▶ PLAYING ")
|
||||
} else {
|
||||
(Color::Rgb(80, 30, 30), " ■ STOPPED ")
|
||||
(status::STOPPED_BG, " ■ STOPPED ")
|
||||
};
|
||||
let transport_style = Style::new().bg(transport_bg).fg(Color::White);
|
||||
let transport_style = Style::new().bg(transport_bg).fg(ui::TEXT_PRIMARY);
|
||||
frame.render_widget(
|
||||
Paragraph::new(transport_text)
|
||||
.style(transport_style)
|
||||
@@ -308,15 +314,8 @@ fn render_header(
|
||||
|
||||
// Fill indicator
|
||||
let fill = app.live_keys.fill();
|
||||
let fill_style = if fill {
|
||||
Style::new()
|
||||
.bg(Color::Rgb(30, 30, 35))
|
||||
.fg(Color::Rgb(100, 220, 100))
|
||||
} else {
|
||||
Style::new()
|
||||
.bg(Color::Rgb(30, 30, 35))
|
||||
.fg(Color::Rgb(60, 60, 70))
|
||||
};
|
||||
let fill_fg = if fill { status::FILL_ON } else { status::FILL_OFF };
|
||||
let fill_style = Style::new().bg(status::FILL_BG).fg(fill_fg);
|
||||
frame.render_widget(
|
||||
Paragraph::new(if fill { "F" } else { "·" })
|
||||
.style(fill_style)
|
||||
@@ -326,8 +325,8 @@ fn render_header(
|
||||
|
||||
// Tempo block
|
||||
let tempo_style = Style::new()
|
||||
.bg(Color::Rgb(60, 30, 60))
|
||||
.fg(Color::White)
|
||||
.bg(header::TEMPO_BG)
|
||||
.fg(ui::TEXT_PRIMARY)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(" {:.1} BPM ", link.tempo()))
|
||||
@@ -342,7 +341,7 @@ 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(Color::Rgb(30, 60, 70)).fg(Color::White);
|
||||
let bank_style = Style::new().bg(header::BANK_BG).fg(ui::TEXT_PRIMARY);
|
||||
frame.render_widget(
|
||||
Paragraph::new(bank_name)
|
||||
.style(bank_style)
|
||||
@@ -373,7 +372,7 @@ fn render_header(
|
||||
" {} · {} steps{}{}{} ",
|
||||
pattern_name, pattern.length, speed_info, page_info, iter_info
|
||||
);
|
||||
let pattern_style = Style::new().bg(Color::Rgb(30, 50, 50)).fg(Color::White);
|
||||
let pattern_style = Style::new().bg(header::PATTERN_BG).fg(ui::TEXT_PRIMARY);
|
||||
frame.render_widget(
|
||||
Paragraph::new(pattern_text)
|
||||
.style(pattern_style)
|
||||
@@ -386,9 +385,7 @@ 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(Color::Rgb(35, 35, 40))
|
||||
.fg(Color::Rgb(150, 150, 160));
|
||||
let stats_style = Style::new().bg(header::STATS_BG).fg(header::STATS_FG);
|
||||
frame.render_widget(
|
||||
Paragraph::new(stats_text)
|
||||
.style(stats_style)
|
||||
@@ -415,10 +412,10 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
page_indicator.to_string(),
|
||||
Style::new().fg(Color::White).add_modifier(Modifier::DIM),
|
||||
Style::new().fg(ui::TEXT_PRIMARY).add_modifier(Modifier::DIM),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(msg.clone(), Style::new().fg(Color::Yellow)),
|
||||
Span::styled(msg.clone(), Style::new().fg(modal::CONFIRM)),
|
||||
])
|
||||
} else {
|
||||
let bindings: Vec<(&str, &str)> = match app.page {
|
||||
@@ -480,7 +477,7 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let mut spans = vec![
|
||||
Span::styled(
|
||||
page_indicator.to_string(),
|
||||
Style::new().fg(Color::White).add_modifier(Modifier::DIM),
|
||||
Style::new().fg(ui::TEXT_PRIMARY).add_modifier(Modifier::DIM),
|
||||
),
|
||||
Span::raw(" ".repeat(base_gap + if extra > 0 { 1 } else { 0 })),
|
||||
];
|
||||
@@ -488,11 +485,11 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
for (i, (key, action)) in bindings.into_iter().enumerate() {
|
||||
spans.push(Span::styled(
|
||||
key.to_string(),
|
||||
Style::new().fg(Color::Yellow),
|
||||
Style::new().fg(hint::KEY),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
format!(":{action}"),
|
||||
Style::new().fg(Color::Rgb(120, 125, 135)),
|
||||
Style::new().fg(hint::TEXT),
|
||||
));
|
||||
|
||||
if i < n - 1 {
|
||||
@@ -542,8 +539,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
use crate::state::file_browser::FileBrowserMode;
|
||||
use crate::widgets::FileBrowserModal;
|
||||
let (title, border_color) = match state.mode {
|
||||
FileBrowserMode::Save => ("Save As", Color::Green),
|
||||
FileBrowserMode::Load => ("Load From", Color::Blue),
|
||||
FileBrowserMode::Save => ("Save As", flash::SUCCESS_FG),
|
||||
FileBrowserMode::Load => ("Load From", browser::DIRECTORY),
|
||||
};
|
||||
let entries: Vec<(String, bool, bool)> = state
|
||||
.entries
|
||||
@@ -561,7 +558,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
Modal::RenameBank { bank, name } => {
|
||||
TextInputModal::new(&format!("Rename Bank {:02}", bank + 1), name)
|
||||
.width(40)
|
||||
.border_color(Color::Magenta)
|
||||
.border_color(modal::RENAME)
|
||||
.render_centered(frame, term);
|
||||
}
|
||||
Modal::RenamePattern {
|
||||
@@ -574,13 +571,13 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
name,
|
||||
)
|
||||
.width(40)
|
||||
.border_color(Color::Magenta)
|
||||
.border_color(modal::RENAME)
|
||||
.render_centered(frame, term);
|
||||
}
|
||||
Modal::RenameStep { step, name, .. } => {
|
||||
TextInputModal::new(&format!("Name Step {:02}", step + 1), name)
|
||||
.width(40)
|
||||
.border_color(Color::Cyan)
|
||||
.border_color(modal::INPUT)
|
||||
.render_centered(frame, term);
|
||||
}
|
||||
Modal::SetPattern { field, input } => {
|
||||
@@ -591,14 +588,14 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
TextInputModal::new(title, input)
|
||||
.hint(hint)
|
||||
.width(45)
|
||||
.border_color(Color::Yellow)
|
||||
.border_color(modal::CONFIRM)
|
||||
.render_centered(frame, term);
|
||||
}
|
||||
Modal::SetTempo(input) => {
|
||||
TextInputModal::new("Set Tempo (20-300 BPM)", input)
|
||||
.hint("Enter BPM")
|
||||
.width(30)
|
||||
.border_color(Color::Magenta)
|
||||
.border_color(modal::RENAME)
|
||||
.render_centered(frame, term);
|
||||
}
|
||||
Modal::AddSamplePath(state) => {
|
||||
@@ -611,7 +608,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
FileBrowserModal::new("Add Sample Path", &state.input, &entries)
|
||||
.selected(state.selected)
|
||||
.scroll_offset(state.scroll_offset)
|
||||
.border_color(Color::Magenta)
|
||||
.border_color(modal::RENAME)
|
||||
.width(60)
|
||||
.height(18)
|
||||
.render_centered(frame, term);
|
||||
@@ -636,14 +633,14 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let inner = ModalFrame::new(&title)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.border_color(Color::Rgb(120, 125, 135))
|
||||
.border_color(modal::PREVIEW)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let script = pattern.resolve_script(step_idx).unwrap_or("");
|
||||
if script.is_empty() {
|
||||
let empty = Paragraph::new("(empty)")
|
||||
.alignment(Alignment::Center)
|
||||
.style(Style::new().fg(Color::Rgb(80, 85, 95)));
|
||||
.style(Style::new().fg(ui::TEXT_DIM));
|
||||
let centered_area = Rect {
|
||||
y: inner.y + inner.height / 2,
|
||||
height: 1,
|
||||
@@ -698,10 +695,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
|
||||
let flash_kind = app.ui.flash_kind();
|
||||
let border_color = match flash_kind {
|
||||
Some(FlashKind::Error) => Color::Red,
|
||||
Some(FlashKind::Info) => Color::White,
|
||||
Some(FlashKind::Success) => Color::Green,
|
||||
None => Color::Rgb(100, 160, 180),
|
||||
Some(FlashKind::Error) => flash::ERROR_FG,
|
||||
Some(FlashKind::Info) => ui::TEXT_PRIMARY,
|
||||
Some(FlashKind::Success) => flash::SUCCESS_FG,
|
||||
None => modal::EDITOR,
|
||||
};
|
||||
|
||||
let title = if let Some(ref name) = step.and_then(|s| s.name.as_ref()) {
|
||||
@@ -768,9 +765,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
|
||||
if let Some(sa) = search_area {
|
||||
let style = if app.editor_ctx.editor.search_active() {
|
||||
Style::default().fg(Color::Yellow)
|
||||
Style::default().fg(search::ACTIVE)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
Style::default().fg(search::INACTIVE)
|
||||
};
|
||||
let cursor = if app.editor_ctx.editor.search_active() {
|
||||
"_"
|
||||
@@ -783,9 +780,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
|
||||
if let Some(kind) = flash_kind {
|
||||
let bg = match kind {
|
||||
FlashKind::Error => Color::Rgb(60, 10, 10),
|
||||
FlashKind::Info => Color::Rgb(30, 30, 40),
|
||||
FlashKind::Success => Color::Rgb(10, 30, 10),
|
||||
FlashKind::Error => flash::ERROR_BG,
|
||||
FlashKind::Info => flash::INFO_BG,
|
||||
FlashKind::Success => flash::SUCCESS_BG,
|
||||
};
|
||||
let flash_block = Block::default().style(Style::default().bg(bg));
|
||||
frame.render_widget(flash_block, editor_area);
|
||||
@@ -794,8 +791,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.editor
|
||||
.render(frame, editor_area, &highlighter);
|
||||
|
||||
let dim = Style::default().fg(Color::DarkGray);
|
||||
let key = Style::default().fg(Color::Yellow);
|
||||
let dim = Style::default().fg(hint::TEXT);
|
||||
let key = Style::default().fg(hint::KEY);
|
||||
|
||||
if app.editor_ctx.editor.search_active() {
|
||||
let hint = Line::from(vec![
|
||||
@@ -863,7 +860,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
|
||||
let block = Block::bordered()
|
||||
.title(format!(" Pattern B{:02}:P{:02} ", bank + 1, pattern + 1))
|
||||
.border_style(Style::default().fg(Color::Cyan));
|
||||
.border_style(Style::default().fg(modal::INPUT));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(Clear, area);
|
||||
@@ -898,14 +895,14 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let (label_style, value_style) = if *selected {
|
||||
(
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.fg(hint::KEY)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default().fg(Color::White).bg(Color::DarkGray),
|
||||
Style::default().fg(ui::TEXT_PRIMARY).bg(ui::SURFACE),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(Color::Gray),
|
||||
Style::default().fg(Color::White),
|
||||
Style::default().fg(ui::TEXT_MUTED),
|
||||
Style::default().fg(ui::TEXT_PRIMARY),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -920,17 +917,17 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" nav ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("←→", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" change ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("Enter", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" save ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("Esc", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" cancel", Style::default().fg(Color::DarkGray)),
|
||||
let hint_line = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" nav ", Style::default().fg(hint::TEXT)),
|
||||
Span::styled("←→", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" change ", Style::default().fg(hint::TEXT)),
|
||||
Span::styled("Enter", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" save ", Style::default().fg(hint::TEXT)),
|
||||
Span::styled("Esc", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" cancel", Style::default().fg(hint::TEXT)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint), hint_area);
|
||||
frame.render_widget(Paragraph::new(hint_line), hint_area);
|
||||
}
|
||||
Modal::KeybindingsHelp { scroll } => {
|
||||
let width = (term.width * 80 / 100).clamp(60, 100);
|
||||
@@ -940,7 +937,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let inner = ModalFrame::new(&title)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.border_color(Color::Rgb(100, 160, 180))
|
||||
.border_color(modal::EDITOR)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let bindings = super::keybindings::bindings_for(app.page);
|
||||
@@ -952,15 +949,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 {
|
||||
Color::Rgb(25, 25, 30)
|
||||
} else {
|
||||
Color::Rgb(35, 35, 42)
|
||||
};
|
||||
let bg = if i % 2 == 0 { table::ROW_EVEN } else { table::ROW_ODD };
|
||||
Row::new(vec![
|
||||
Cell::from(*key).style(Style::default().fg(Color::Yellow)),
|
||||
Cell::from(*name).style(Style::default().fg(Color::Cyan)),
|
||||
Cell::from(*desc).style(Style::default().fg(Color::White)),
|
||||
Cell::from(*key).style(Style::default().fg(modal::CONFIRM)),
|
||||
Cell::from(*name).style(Style::default().fg(modal::INPUT)),
|
||||
Cell::from(*desc).style(Style::default().fg(ui::TEXT_PRIMARY)),
|
||||
])
|
||||
.style(Style::default().bg(bg))
|
||||
})
|
||||
@@ -990,15 +983,15 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
width: inner.width,
|
||||
height: 1,
|
||||
};
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" scroll ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("PgUp/Dn", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" page ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("Esc/?", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(" close", Style::default().fg(Color::DarkGray)),
|
||||
let keybind_hint = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" scroll ", Style::default().fg(hint::TEXT)),
|
||||
Span::styled("PgUp/Dn", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" page ", Style::default().fg(hint::TEXT)),
|
||||
Span::styled("Esc/?", Style::default().fg(hint::KEY)),
|
||||
Span::styled(" close", Style::default().fg(hint::TEXT)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
|
||||
frame.render_widget(Paragraph::new(keybind_hint).alignment(Alignment::Right), hint_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
use tui_big_text::{BigText, PixelSize};
|
||||
|
||||
use crate::state::ui::UiState;
|
||||
use crate::theme::title;
|
||||
|
||||
pub fn render(frame: &mut Frame, area: Rect, ui: &UiState) {
|
||||
frame.render_widget(&ui.sparkles, area);
|
||||
|
||||
let author_style = Style::new().fg(Color::Rgb(180, 140, 200));
|
||||
let link_style = Style::new().fg(Color::Rgb(120, 200, 180));
|
||||
let license_style = Style::new().fg(Color::Rgb(200, 160, 100));
|
||||
let author_style = Style::new().fg(title::AUTHOR);
|
||||
let link_style = Style::new().fg(title::LINK);
|
||||
let license_style = Style::new().fg(title::LICENSE);
|
||||
|
||||
let big_title = BigText::builder()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(Style::new().cyan().bold())
|
||||
.style(Style::new().fg(title::BIG_TITLE).bold())
|
||||
.lines(vec!["CAGIRE".into()])
|
||||
.centered()
|
||||
.build();
|
||||
@@ -25,7 +26,7 @@ pub fn render(frame: &mut Frame, area: Rect, ui: &UiState) {
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
"A Forth Music Sequencer",
|
||||
Style::new().fg(Color::White),
|
||||
Style::new().fg(title::SUBTITLE),
|
||||
)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled("by BuboBubo", author_style)),
|
||||
@@ -37,7 +38,7 @@ pub fn render(frame: &mut Frame, area: Rect, ui: &UiState) {
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
"Press any key to continue",
|
||||
Style::new().fg(Color::Rgb(140, 160, 170)),
|
||||
Style::new().fg(title::PROMPT),
|
||||
)),
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user