diff --git a/Cargo.toml b/Cargo.toml index a7665da..1f9d7eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" [dependencies] noise = { version = "0.8.2", features = ["images"] } rand = "0.8.5" -image = "0.24.5" \ No newline at end of file +image = "0.24.5" +tui = "0.19" +crossterm = "0.25" \ No newline at end of file diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..5c69264 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,88 @@ +use crossterm::event::{read, Event, KeyCode}; +use tui::{Terminal, backend::Backend, Frame}; + +use crate::state::{self, GameState}; + +/** + * |........................| + * | Conversations/Selection| + * | | + * |........................| + * | Available Options | + * |........................| + */ + +enum AppStatus { + Initial, + GuestSelection, +} + +pub struct App<'a> { + state: GameState, + guest_list: Vec>, + guest_state: tui::widgets::ListState, + status: AppStatus, +} + +impl<'a> App<'a> { + pub fn new(state: GameState) -> App<'a> { + App { + state: state, + guest_list: vec![], + guest_state: tui::widgets::ListState::default(), + status: AppStatus::Initial, + } + } + + pub fn step(&mut self) -> bool { + match self.status { + AppStatus::Initial => { + // determine guests + self.guest_list = self.state.people.iter().map(|guest| { + tui::widgets::ListItem::new(guest.name.clone()) + }).collect(); + self.guest_state = tui::widgets::ListState::default(); + self.guest_state.select(Some(0)); + self.status = AppStatus::GuestSelection; + true + }, + AppStatus::GuestSelection => { + match read() { + Ok(Event::Key(event)) => { + match event.code { + KeyCode::Up => { + let selected = self.guest_state.selected().unwrap(); + if selected > 0 { + self.guest_state.select(Some(selected - 1)); + } + }, + KeyCode::Down => { + let selected = self.guest_state.selected().unwrap(); + if selected < self.guest_list.len() - 1 { + self.guest_state.select(Some(selected + 1)); + } + }, + KeyCode::Esc => { + return false; + }, + _ => {} + } + }, + _ => {} + } + true + } + } + } + + pub fn draw(&mut self, f: &mut Frame) { + + let main_window = tui::widgets::List::new(self.guest_list.clone()) + .block(tui::widgets::Block::default().title("Guests").borders(tui::widgets::Borders::ALL)) + .style(tui::style::Style::default().fg(tui::style::Color::White)) + .highlight_style(tui::style::Style::default().add_modifier(tui::style::Modifier::ITALIC)) + .highlight_symbol(">>"); + + f.render_stateful_widget(main_window, f.size(), &mut self.guest_state); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f0ea740..3903c7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,32 @@ mod events; mod time; mod generators; mod person; +mod app; +use app::App; use noise::{Perlin, ScalePoint, Add, NoiseFn, ScaleBias}; use noise::utils::{NoiseMapBuilder, PlaneMapBuilder}; use image::{RgbImage, Rgb}; use rand::prelude::*; +use tui::Frame; +use tui::backend::Backend; +use tui::style::{Style, Color, Modifier}; +use tui::widgets::List; use world::World; +use std::{io, thread, time::Duration}; +use tui::{ + backend::CrosstermBackend, + widgets::{Widget, Block, Borders}, + layout::{Layout, Constraint, Direction}, + Terminal +}; +use crossterm::{ + event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; + const N_TOWNS: usize = 10; const WORLD_SIZE: usize = 256; @@ -89,22 +108,65 @@ fn build_world() -> World { world } -fn main() { +fn draw_state(f: &mut Frame, state: &state::GameState) { + let mut items: Vec = vec![]; - let mut state = state::GameState::new(build_world()); + for e in state.events.iter() { + items.push(tui::widgets::ListItem::new(format!("{}", e.description()))); + } - state.step_n(36000); + let main_window = List::new(items) + .block(Block::default().title("List").borders(Borders::ALL)) + .style(Style::default().fg(Color::White)) + .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) + .highlight_symbol(">>"); + + f.render_widget(main_window, f.size()); +} + +fn main() -> Result<(), io::Error> { + + let mut world_state = state::GameState::new(build_world()); + + world_state.step_n(36000); let mut rng = rand::thread_rng(); for _ in 0..N_TOWNS { - state.step_n(rng.gen_range(0..360*3)); - state.found_town(); + world_state.step_n(rng.gen_range(0..360*3)); + world_state.found_town(); } - for event in &state.events { - println!("{}", event.description()); + + // setup terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut app = App::new(world_state); + loop { + terminal.draw(|f| app.draw(f))?; + if !app.step() { + break; + } } + + // restore terminal + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + Ok(()) + + // for event in &state.events { + // println!("{}", event.description()); + // } } \ No newline at end of file diff --git a/src/person.rs b/src/person.rs index 1c38e7f..3a54ad9 100644 --- a/src/person.rs +++ b/src/person.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{rc::Rc, fmt::Display}; use crate::{time::Time, world::Town, generators::PersonNameGenerator}; @@ -26,4 +26,10 @@ impl Person { location: [0, 0], } } +} + +impl Display for Person { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } } \ No newline at end of file