From 62c46ce8f949a23a9e64b81c961094fc0696a1da Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 13:55:23 +0200 Subject: [PATCH] Initial view Another two hours. --- Cargo.toml | 2 + src/lib.rs | 1 + src/main.rs | 46 ++++++++++++++---- src/view.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 src/view.rs diff --git a/Cargo.toml b/Cargo.toml index 01c3b0d..83dc44d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2018" authors = ["Adrian Heine "] [dependencies] +tui = "0.15" +termion = "1.5" diff --git a/src/lib.rs b/src/lib.rs index 1ad7f26..53a7904 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod agent; +pub mod view; pub mod world; diff --git a/src/main.rs b/src/main.rs index 5995229..e22381a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,42 @@ -use gntag::agent::{Agent, Position, SimpleAgent}; +use gntag::agent::{Agent, SimpleAgent}; +use gntag::view::TerminalView; use gntag::world::ActualWorld; fn main() { - let mut agents: Vec<(_, Box)> = vec![]; - for x in 0..5 { - for y in 0..5 { - agents.push(((x, y).into(), Box::new(SimpleAgent))); - } - } - let mut world = ActualWorld::new((80, 40).into(), agents); + let mut view = TerminalView::try_new().unwrap(); loop { - world.do_step(); - println!("{}", world.tagged); + let (width, height) = view.content_size().unwrap(); + let mut agents: Vec<(_, Box)> = vec![]; + for x in (0..width).step_by(10) { + for y in (0..height).step_by(10) { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + let mut world = ActualWorld::new((width, height).into(), agents); + + let mut gen = 0; + loop { + let resized = view + .draw( + gen, + world + .agent_positions + .get(&world.tagged) + .map(|pos| (pos.x, pos.y)) + .unwrap(), + world + .agent_positions + .iter() + .map(|(_id, pos)| (pos.x, pos.y)) + .collect::>() + .as_ref(), + ) + .unwrap(); + if resized { + break; + }; + world.do_step(); + gen += 1; + } } } diff --git a/src/view.rs b/src/view.rs new file mode 100644 index 0000000..320eaee --- /dev/null +++ b/src/view.rs @@ -0,0 +1,132 @@ +use std::convert::TryInto; +use std::error::Error; +use std::io::{stdout, Stdout, Write}; +use termion::clear; +use termion::raw::{IntoRawMode, RawTerminal}; +use tui::backend::{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) + } +}