use std::convert::TryInto; use std::error::Error; use std::io::{stdout, Stdout, Write}; use termion::clear; use termion::raw::{IntoRawMode, RawTerminal}; pub use tui::backend::Backend; use tui::backend::TermionBackend; use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use tui::style::{Color, Modifier, Style}; use tui::text::{Span, Spans}; use tui::widgets::canvas::{Canvas, Points}; use tui::widgets::{Block, Borders, Paragraph, Wrap}; use tui::Terminal; pub struct TerminalView { terminal: Terminal, previous_coords: Vec<(f64, f64)>, inner_size: Rect, } fn get_layout(area: Rect) -> Vec { Layout::default() .direction(Direction::Horizontal) .margin(1) .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref()) .split(area) } fn get_block() -> Block<'static> { Block::default().borders(Borders::ALL).title("gntag") } fn get_inner_size(terminal: &mut Terminal) -> Result> { let mut res = None; terminal.draw(|f| { let chunks = get_layout(f.size()); res = Some(get_block().inner(chunks[0])); })?; res.ok_or_else(|| "Error getting canvas content size".into()) } impl TerminalView>> { pub fn try_new() -> Result> { let mut stdout = stdout().into_raw_mode()?; write!(stdout, "{}", clear::All)?; let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let inner_size = get_inner_size(&mut terminal)?; Ok(Self { terminal, previous_coords: vec![], inner_size, }) } } const FACTOR: isize = 3; impl TerminalView { pub fn content_size(&self) -> Result<(isize, isize), Box> { let size = self.inner_size; let width: isize = size.width.try_into()?; let height: isize = size.height.try_into()?; Ok((width * FACTOR, height * FACTOR)) } pub fn draw( &mut self, gen: usize, tagged: (isize, isize), positions: &[(isize, isize)], ) -> Result> { let mut coords = vec![]; for (x, y) in positions { coords.push((*x as f64, *y as f64)); } let prev_points = Points { coords: &self.previous_coords, color: Color::Black, }; let cur_points = Points { coords: &coords, color: Color::White, }; let content_size = self.content_size()?; let mut inner_size = self.inner_size; self.terminal.draw(|f| { let chunks = get_layout(f.size()); let block = get_block(); inner_size = block.inner(chunks[0]); let canvas = Canvas::default() .block(block) .paint(|ctx| { ctx.draw(&prev_points); ctx.layer(); ctx.draw(&cur_points); ctx.layer(); ctx.draw(&Points { coords: &[(tagged.0 as f64, tagged.1 as f64)], color: Color::Yellow, }); }) .background_color(Color::Black) .x_bounds([0.0, content_size.0 as f64]) .y_bounds([0.0, content_size.1 as f64]); f.render_widget(canvas, chunks[0]); let text = vec![ Spans::from(vec![ Span::styled( "Generation: ", Style::default().add_modifier(Modifier::BOLD), ), Span::raw(gen.to_string()), ]), Spans::from(vec![ Span::styled("Agents: ", Style::default().add_modifier(Modifier::BOLD)), Span::raw(coords.len().to_string()), ]), ]; let side = Paragraph::new(text) .block(Block::default().title("Info").borders(Borders::ALL)) .style(Style::default().fg(Color::White).bg(Color::Black)) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); f.render_widget(side, chunks[1]); })?; self.previous_coords = coords; let old_inner_size = self.inner_size; self.inner_size = inner_size; Ok(inner_size != old_inner_size) } }