use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use std::{ error::Error, io, thread::sleep, time::{Duration, Instant}, }; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Alignment, Constraint, Corner, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, Frame, Terminal, }; use crate::generation::*; pub struct StatefulList { state: ListState, items: Vec, } impl StatefulList { fn with_items(items: Vec) -> StatefulList { StatefulList { state: ListState::default(), items, } } fn next(&mut self) { let i = match self.state.selected() { Some(i) => { if i >= self.items.len() - 1 { 0 } else { i + 1 } } None => 0, }; self.state.select(Some(i)); } fn previous(&mut self) { let i = match self.state.selected() { Some(i) => { if i == 0 { self.items.len() - 1 } else { i - 1 } } None => 0, }; self.state.select(Some(i)); } fn unselect(&mut self) { self.state.select(None); } } /// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper /// around `ListState`. Keeping track of the items state let us render the associated widget with its state /// and have access to features such as natural scrolling. /// /// Check the event handling at the bottom to see how to change the state on incoming events. /// Check the drawing logic for items on how to specify the highlighting style for selected items. pub struct App<'a> { pub items: StatefulList<(&'a str, usize)>, pub flag_cur: bool, pub layout: Layout, pub cur_gen: Gen, pub nxt_gen: Gen, // generation: Gen, } impl<'a> App<'a> { pub fn new() -> App<'a> { App { items: StatefulList::with_items(vec![ ("Glider", 1), ("achimsotherp16", 2), ("achimsp11", 3), ("achimsp144", 4), ("achimsp16", 4), ("achimsp4", 4), ("achimsp5original", 4), ("achimsp8", 4), ("aforall", 4), ("againstthegraingeryshep", 4), ("aircraftcarrier", 4), ("almostgun1", 4), ("almostgun2", 4), ("almostknightship", 4), ("alternatewickstretcher1", 4), ]), flag_cur: false, layout: Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(15), Constraint::Percentage(85)].as_ref()), cur_gen: Gen::new(), nxt_gen: Gen::new(), } } // Rotate through the event list. // This only exists to simulate some kind of "progress" // fn on_tick(&mut self) { // let event = self.events.remove(0); // self.events.push(event); // } } pub fn run_app( terminal: &mut Terminal, mut app: App, tick_rate: Duration, ) -> io::Result<()> { let mut last_tick = Instant::now(); loop { terminal.draw(|f| ui_list(f, &mut app))?; let timeout = Duration::from_millis(10); if crossterm::event::poll(timeout)? { if let Event::Key(key) = event::read()? { match key.code { KeyCode::Char('q') => return Ok(()), KeyCode::Left | KeyCode::Char('h') => app.items.unselect(), KeyCode::Down | KeyCode::Char('j') => app.items.next(), KeyCode::Up | KeyCode::Char('k') => app.items.previous(), KeyCode::Char('n') => { terminal.draw(|f| ui_game(f, &mut app))?; } KeyCode::Char('a') => 'animate: loop { terminal.draw(|f| ui_game(f, &mut app))?; sleep(Duration::from_millis(16)); if (crossterm::event::poll(Duration::from_millis(1))).unwrap() { if let Event::Key(k) = event::read().unwrap() { match k.code { KeyCode::Char('s') => break 'animate, _ => {} } } } }, _ => {} } } } } } fn ui_list(f: &mut Frame, app: &mut App) { let chunks = app.layout.split(f.size()); // Iterate through all elements in the `items` app and append some debug text to it. let items: Vec = app .items .items .iter() .map(|i| { let lines = vec![Spans::from(i.0)]; ListItem::new(lines).style(Style::default().fg(Color::Red).bg(Color::Black)) }) .collect(); // Create a List from all list items and highlight the currently selected one let items = List::new(items) .block(Block::default().borders(Borders::ALL).title("List")) .highlight_style( Style::default() .bg(Color::White) .add_modifier(Modifier::ITALIC), ) .highlight_symbol("> "); // We can now render the item list f.render_stateful_widget(items, chunks[0], &mut app.items.state); if !app.flag_cur { app.cur_gen = new_gen(&chunks[1], app); } let spans = gen_to_spans(&app.cur_gen); let create_block = |title| { Block::default() .borders(Borders::ALL) .style(Style::default().bg(Color::Black).fg(Color::Red)) .title(Span::styled( title, Style::default().add_modifier(Modifier::BOLD), )) .title_alignment(Alignment::Center) }; let paragraph = Paragraph::new(spans.clone()) .style(Style::default().bg(Color::Black).fg(Color::Blue)) .block(create_block(" Game Of Life ")) .alignment(Alignment::Center); f.render_widget(paragraph, chunks[1]); } fn ui_game(f: &mut Frame, app: &mut App) { let chunks = app.layout.split(f.size()); let items: Vec = app .items .items .iter() .map(|i| { let lines = vec![Spans::from(i.0)]; ListItem::new(lines).style(Style::default().fg(Color::Red).bg(Color::Black)) }) .collect(); // Create a List from all list items and highlight the currently selected one let items = List::new(items) .block(Block::default().borders(Borders::ALL).title("List")) .highlight_style( Style::default() .bg(Color::Blue) .add_modifier(Modifier::BOLD), ) .highlight_symbol("> "); // We can now render the item list f.render_stateful_widget(items, chunks[0], &mut app.items.state); let nxt = next_gen(app); let spans = gen_to_spans(&nxt); let create_block = |title| { Block::default() .borders(Borders::ALL) .style(Style::default().bg(Color::Black).fg(Color::Red)) .title(Span::styled( title, Style::default().add_modifier(Modifier::BOLD), )) .title_alignment(Alignment::Center) }; let paragraph = Paragraph::new(spans.clone()) .style(Style::default().bg(Color::Black).fg(Color::Blue)) .block(create_block(" Game Of Life ")) .alignment(Alignment::Center); f.render_widget(paragraph, chunks[1]); }