//! The name forge: browse what the name generator makes for each culture. //! //! Each forging is `derive(universe, "forge", n)`, so stepping back with ← //! regenerates exactly what you saw before. use ratatui::Frame; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Color, Style, Stylize}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Paragraph, Wrap}; use super::{Scene, Transition}; use crate::app::Ctx; use crate::names::{self, Artifact, Culture}; use crate::seed; const PER_LIST: usize = 4; const ARTIFACTS: usize = 5; struct Column { culture: Culture, people: Vec, places: Vec, } pub struct Forge { forging: u64, columns: Vec, artifacts: Vec, } impl Forge { pub fn new(ctx: &Ctx) -> Self { let mut forge = Forge { forging: 0, columns: Vec::new(), artifacts: Vec::new() }; forge.forge(ctx.seed); forge } fn forge(&mut self, universe: u64) { let mut rng = seed::rng(seed::derive(universe, "forge", self.forging)); self.columns = Culture::ALL .iter() .map(|&culture| Column { culture, people: (0..PER_LIST).map(|_| culture.person(&mut rng)).collect(), places: (0..PER_LIST).map(|_| culture.place(&mut rng)).collect(), }) .collect(); self.artifacts = (0..ARTIFACTS).map(|_| names::artifact(&mut rng)).collect(); } } fn colour(culture: Culture) -> Color { match culture { Culture::Qaari => Color::Yellow, Culture::Cant => Color::Red, Culture::Houses => Color::Magenta, Culture::Drift => Color::Cyan, } } fn render_column(frame: &mut Frame, column: &Column, area: Rect) { let tint = colour(column.culture); let heading = |text: &'static str| Line::from(text).fg(tint).dim(); let mut lines = vec![ Line::from(column.culture.blurb()).dark_gray().italic(), Line::default(), heading("PEOPLE"), ]; lines.extend(column.people.iter().map(|n| Line::from(n.as_str()))); lines.push(Line::default()); lines.push(heading("PLACES")); lines.extend(column.places.iter().map(|n| Line::from(n.as_str()))); let block = Block::bordered() .border_style(Style::new().fg(tint).dim()) .title(Line::from(format!(" {} ", column.culture.name())).fg(tint).bold()); frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: true }).block(block), area); } impl Scene for Forge { fn key(&mut self, key: KeyEvent, ctx: &mut Ctx) -> Transition { match key.code { KeyCode::Char(' ') | KeyCode::Right | KeyCode::Enter => { self.forging += 1; self.forge(ctx.seed); } KeyCode::Left | KeyCode::Backspace if self.forging > 0 => { self.forging -= 1; self.forge(ctx.seed); } KeyCode::Esc => return Transition::Pop, KeyCode::Char(c) if c.eq_ignore_ascii_case(&'q') => return Transition::Pop, _ => {} } Transition::None } fn render(&self, frame: &mut Frame, ctx: &Ctx) { let [header, cultures, artifacts, footer] = Layout::vertical([ Constraint::Length(2), Constraint::Fill(1), Constraint::Length(ARTIFACTS as u16 + 2), Constraint::Length(1), ]) .areas(frame.area()); frame.render_widget( Paragraph::new(Line::from(vec![ Span::from(" THE NAME FORGE").bold(), Span::from(format!(" forging {} of universe {}", self.forging, ctx.seed)) .dark_gray(), ])), header, ); // Four across on wide terminals, two by two otherwise. let cells: Vec = if cultures.width >= 120 { Layout::horizontal([Constraint::Fill(1); 4]).split(cultures).to_vec() } else { Layout::vertical([Constraint::Fill(1); 2]) .split(cultures) .iter() .flat_map(|row| Layout::horizontal([Constraint::Fill(1); 2]).split(*row).to_vec()) .collect() }; for (column, cell) in self.columns.iter().zip(cells) { render_column(frame, column, cell); } let width = self.artifacts.iter().map(|a| a.unidentified.len()).max().unwrap_or(0); let lines: Vec = self .artifacts .iter() .map(|a| { Line::from(vec![ Span::from(format!("{: