WIP: menu
This commit is contained in:
186
src/views/dict_view.rs
Normal file
186
src/views/dict_view.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::model::{Word, WordCompile, WORDS};
|
||||
use crate::state::DictFocus;
|
||||
|
||||
const CATEGORIES: &[&str] = &[
|
||||
"Stack",
|
||||
"Arithmetic",
|
||||
"Comparison",
|
||||
"Logic",
|
||||
"Sound",
|
||||
"Variables",
|
||||
"Randomness",
|
||||
"Probability",
|
||||
"Context",
|
||||
"Music",
|
||||
"Time",
|
||||
"Parameters",
|
||||
];
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let [header_area, body_area] =
|
||||
Layout::vertical([Constraint::Length(5), Constraint::Fill(1)]).areas(area);
|
||||
|
||||
render_header(frame, header_area);
|
||||
|
||||
let [cat_area, words_area] =
|
||||
Layout::horizontal([Constraint::Length(16), Constraint::Fill(1)]).areas(body_area);
|
||||
|
||||
render_categories(frame, app, cat_area);
|
||||
render_words(frame, app, words_area);
|
||||
}
|
||||
|
||||
fn render_header(frame: &mut Frame, area: Rect) {
|
||||
use ratatui::widgets::Wrap;
|
||||
let desc = "Forth uses a stack: values are pushed, functions (called words) consume and \
|
||||
produce values. Read left to right: 3 4 + -> push 3, push 4, + pops both, \
|
||||
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)))
|
||||
.title("Dictionary");
|
||||
let para = Paragraph::new(desc)
|
||||
.style(Style::new().fg(Color::Rgb(140, 145, 155)))
|
||||
.wrap(Wrap { trim: false })
|
||||
.block(block);
|
||||
frame.render_widget(para, area);
|
||||
}
|
||||
|
||||
fn render_categories(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let focused = app.ui.dict_focus == DictFocus::Categories;
|
||||
|
||||
let items: Vec<ListItem> = CATEGORIES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, name)| {
|
||||
let is_selected = i == app.ui.dict_category;
|
||||
let style = if is_selected && focused {
|
||||
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
} else if is_selected {
|
||||
Style::new().fg(Color::Cyan)
|
||||
} else {
|
||||
Style::new().fg(Color::White)
|
||||
};
|
||||
let prefix = if is_selected { "> " } else { " " };
|
||||
ListItem::new(format!("{prefix}{name}")).style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let border_color = if focused { Color::Yellow } else { Color::Rgb(60, 60, 70) };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
.title("Categories");
|
||||
let list = List::new(items).block(block);
|
||||
frame.render_widget(list, area);
|
||||
}
|
||||
|
||||
fn render_words(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let focused = app.ui.dict_focus == DictFocus::Words;
|
||||
let category = CATEGORIES[app.ui.dict_category];
|
||||
let words: Vec<&Word> = WORDS
|
||||
.iter()
|
||||
.filter(|w| word_category(w.name, &w.compile) == category)
|
||||
.collect();
|
||||
|
||||
let content_width = area.width.saturating_sub(2) as usize;
|
||||
|
||||
let mut lines: Vec<RLine> = Vec::new();
|
||||
|
||||
for word in &words {
|
||||
let name_bg = Color::Rgb(40, 50, 60);
|
||||
let name_style = Style::new()
|
||||
.fg(Color::Green)
|
||||
.bg(name_bg)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let name_line = format!(" {}", word.name);
|
||||
let padding = " ".repeat(content_width.saturating_sub(name_line.chars().count()));
|
||||
lines.push(RLine::from(Span::styled(
|
||||
format!("{name_line}{padding}"),
|
||||
name_style,
|
||||
)));
|
||||
|
||||
let stack_style = Style::new().fg(Color::Magenta);
|
||||
lines.push(RLine::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(word.stack.to_string(), stack_style),
|
||||
]));
|
||||
|
||||
let desc_style = Style::new().fg(Color::White);
|
||||
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));
|
||||
lines.push(RLine::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(format!("e.g. {}", word.example), example_style),
|
||||
]));
|
||||
|
||||
lines.push(RLine::from(""));
|
||||
}
|
||||
|
||||
let visible_height = area.height.saturating_sub(2) as usize;
|
||||
let total_lines = lines.len();
|
||||
let max_scroll = total_lines.saturating_sub(visible_height);
|
||||
let scroll = app.ui.dict_scroll.min(max_scroll);
|
||||
|
||||
let visible: Vec<RLine> = lines
|
||||
.into_iter()
|
||||
.skip(scroll)
|
||||
.take(visible_height)
|
||||
.collect();
|
||||
|
||||
let title = format!("{category} ({} words)", words.len());
|
||||
let border_color = if focused { Color::Yellow } else { Color::Rgb(60, 60, 70) };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
.title(title);
|
||||
let para = Paragraph::new(visible).block(block);
|
||||
frame.render_widget(para, area);
|
||||
}
|
||||
|
||||
fn word_category(name: &str, compile: &WordCompile) -> &'static str {
|
||||
const STACK: &[&str] = &["dup", "drop", "swap", "over", "rot", "nip", "tuck"];
|
||||
const ARITH: &[&str] = &[
|
||||
"+", "-", "*", "/", "mod", "neg", "abs", "floor", "ceil", "round", "min", "max",
|
||||
];
|
||||
const CMP: &[&str] = &["=", "<>", "<", ">", "<=", ">="];
|
||||
const LOGIC: &[&str] = &["and", "or", "not"];
|
||||
const SOUND: &[&str] = &["sound", "s", "emit"];
|
||||
const VAR: &[&str] = &["get", "set"];
|
||||
const RAND: &[&str] = &["rand", "rrand", "seed", "coin", "chance", "choose", "cycle"];
|
||||
const MUSIC: &[&str] = &["mtof", "ftom"];
|
||||
const TIME: &[&str] = &[
|
||||
"at", "window", "pop", "div", "each", "tempo!", "[", "]", "?",
|
||||
];
|
||||
|
||||
match compile {
|
||||
WordCompile::Simple if STACK.contains(&name) => "Stack",
|
||||
WordCompile::Simple if ARITH.contains(&name) => "Arithmetic",
|
||||
WordCompile::Simple if CMP.contains(&name) => "Comparison",
|
||||
WordCompile::Simple if LOGIC.contains(&name) => "Logic",
|
||||
WordCompile::Simple if SOUND.contains(&name) => "Sound",
|
||||
WordCompile::Alias(_) => "Sound",
|
||||
WordCompile::Simple if VAR.contains(&name) => "Variables",
|
||||
WordCompile::Simple if RAND.contains(&name) => "Randomness",
|
||||
WordCompile::Probability(_) => "Probability",
|
||||
WordCompile::Context(_) => "Context",
|
||||
WordCompile::Simple if MUSIC.contains(&name) => "Music",
|
||||
WordCompile::Simple if TIME.contains(&name) => "Time",
|
||||
WordCompile::Param => "Parameters",
|
||||
_ => "Other",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn category_count() -> usize {
|
||||
CATEGORIES.len()
|
||||
}
|
||||
Reference in New Issue
Block a user