mod chat; mod controls; mod debug_view; mod guest_selection; mod talk_to_guest; mod status_line; use crossterm::event::{read, Event, KeyCode}; use tui::{backend::Backend, Frame, layout::Layout, Terminal}; use crate::{game::Game, entity::{EntityId, EntityType}, world::Terrain}; use self::{chat::{Chat, ChatLine}, controls::Controls, debug_view::DebugView, guest_selection::GuestSelectionView, talk_to_guest::TalkToGuestView}; /** * |........................| * | Conversations/Selection| * | | * |........................| * | Available Options | * |........................| */ pub enum AppStatus<'a> { Initial, GuestSelection(GuestSelectionView<'a>), TalkToGuest(TalkToGuestView), BuyFromGuest(EntityId), Debug(DebugView<'a>), } pub struct App<'a> { game: Game, status: AppStatus<'a>, next_status: Option>, // Conversation conversation: Chat, conversation_scroll: u16, // // Guest Selection // guest_selection: Option>, // // Debug // debug_view: Option>, } pub struct DefaultLayout { pub status: tui::layout::Rect, pub main: tui::layout::Rect, pub controls: tui::layout::Rect, } impl DefaultLayout { pub fn default(rect: tui::layout::Rect) -> DefaultLayout { let chunks = Layout::default() .direction(tui::layout::Direction::Vertical) .constraints( [ tui::layout::Constraint::Length(1), tui::layout::Constraint::Min(3), tui::layout::Constraint::Length(2) ] .as_ref(), ).split(rect); DefaultLayout { status: chunks[0], main: chunks[1], controls: chunks[2], } } } impl<'a> App<'a> { pub fn new(state: Game) -> App<'a> { App { game: state, status: AppStatus::Initial, conversation: Chat::new(), conversation_scroll: 0, next_status: None, // guest_selection: None, // debug_view: None, } } pub fn do_loop(&'a mut self, terminal: &mut Terminal) -> bool { terminal.draw(|f| self.draw(f)).unwrap(); self.step() } pub fn step(&'a mut self) -> bool { if let Some(next_status) = self.next_status.take() { self.status = next_status; self.next_status = None; } let mut ret = (true, None); match &mut self.status { AppStatus::Initial => { ret.1 = Some(AppStatus::GuestSelection(GuestSelectionView::new(&self.game))); }, AppStatus::GuestSelection(ref mut guest_selection) => { ret = guest_selection.control(&mut self.game); }, AppStatus::TalkToGuest(talk_view) => { ret = talk_view.control(&mut self.game); }, AppStatus::BuyFromGuest(guest_id) => { // match read() { // Ok(Event::Key(event)) => { // match event.code { // KeyCode::Esc => { // self.status = AppStatus::TalkToGuest(Some(*guest_id)); // }, // _ => {} // } // }, // _ => {} // } }, AppStatus::Debug(debug_view) => { ret = debug_view.control(&mut self.game); } } self.next_status = ret.1; ret.0 } fn open_debug(&mut self) { self.status = AppStatus::Debug(DebugView::new(&self.game)); } // fn default_layout(&self) -> Layout { // DefaultLayout::default() // } pub fn draw(&mut self, f: &mut Frame) { match &mut self.status { AppStatus::Initial => { self.draw_initial(f); }, AppStatus::GuestSelection(view) => { view.draw(f, &self.game); }, AppStatus::TalkToGuest(view) => { view.draw(f, &self.game); }, AppStatus::BuyFromGuest(guest_id) => { // self.draw_buy_from_guest(f, *guest_id); }, AppStatus::Debug(view) => { view.draw(f, &self.game); }, } } fn draw_status(&self, f: &mut Frame, rect: tui::layout::Rect) { let tavern = self.game.state.world.get_site( self.game.state.tavern.unwrap() ).unwrap(); let spans = tui::text::Spans::from(vec![ tui::text::Span::raw("Date: "), tui::text::Span::raw(format!("{}", self.game.state.time)), tui::text::Span::raw(" "), tui::text::Span::raw("Funds: "), tui::text::Span::raw(format!("{} gold coins", tavern.coins)), ]); let status_text = tui::widgets::Paragraph::new(spans) .block(tui::widgets::Block::default().borders(tui::widgets::Borders::LEFT | tui::widgets::Borders::RIGHT)) .style(tui::style::Style::default().fg(tui::style::Color::White)); f.render_widget(status_text, rect); } pub fn draw_initial(&mut self, _f: &mut Frame) {} fn draw_talk_to_guest(&self, f: &mut Frame, guest: Option) { let chunks = DefaultLayout::default(f.size()); let guest = self.game.state.get_creature(guest.unwrap()).unwrap(); let key_style = tui::style::Style::default() .add_modifier(tui::style::Modifier::BOLD) .fg(tui::style::Color::Green) .bg(tui::style::Color::Black); let mut full_text = self.conversation.to_spans(); full_text.push(tui::text::Spans::from(vec![ tui::text::Span::styled("(a) ", key_style), tui::text::Span::raw("What's your business?"), ])); full_text.push(tui::text::Spans::from(vec![ tui::text::Span::styled("(b) ", key_style), tui::text::Span::raw("What do you have to sell?"), ])); full_text.push(tui::text::Spans::from(vec![ tui::text::Span::styled("(c) ", key_style), tui::text::Span::raw("Heard of anything interesting?"), ])); let text = tui::text::Text::from(full_text); let scroll: u16 = ((text.lines.len() as u16) ) .saturating_sub( (chunks.main.height as u16).saturating_sub(2) // 2 lines for the border ) .saturating_sub(self.conversation_scroll); let main_window = tui::widgets::Paragraph::new(text) .block(tui::widgets::Block::default().title(guest.name.clone()).borders(tui::widgets::Borders::ALL)) .style(tui::style::Style::default().fg(tui::style::Color::White)) .scroll((scroll, 0)); let mut binding = Controls::new(); let controls = binding .add("a-z".to_owned(), "Talk".to_owned()) .add("Esc".to_owned(), "Back".to_owned()) .render(); self.draw_status(f, chunks.status); f.render_widget(main_window, chunks.main); f.render_widget(controls, chunks.controls); } fn draw_buy_from_guest(&self, f: &mut Frame, guest_id: EntityId) { let chunks = DefaultLayout::default(f.size()); let guest = self.game.state.get_creature(guest_id).unwrap(); let inventory = self.game.state.get_inventory(guest_id); let mut list_items = vec![]; for item in inventory { let item = self.game.state.get_item(item).unwrap(); list_items.push(tui::widgets::ListItem::new(format!("{} ({} gold)", item.name, item.value()))); } let main_window = tui::widgets::List::new(list_items) .block(tui::widgets::Block::default().title(guest.name.clone()).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(">>"); let mut binding = Controls::new(); let controls = binding .add("a-z".to_owned(), "Select".to_owned()) .add("Esc".to_owned(), "Back".to_owned()) .render(); self.draw_status(f, chunks.status); f.render_widget(main_window, chunks.main); f.render_widget(controls, chunks.controls); } /** * Conversation */ fn greet_guest(&mut self, guest_id: Option) { let guest = self.game.state.get_creature(guest_id.unwrap()).unwrap(); self.conversation.add_line(ChatLine::new( (EntityType::Creature, 0), "You".to_owned(), "Greetings traveller!".to_owned(), false )); self.conversation.add_line(ChatLine::new( guest_id.unwrap(), guest.name.clone(), "Hello, I'm ".to_owned() + &guest.name + ", nice to meet you! " + "I'm a " + &guest.profession.to_string() + ".", false )); self.conversation_scroll = 0; } fn ask_business(&mut self, guest: Option) { let guest = self.game.state.get_creature(guest.unwrap()).unwrap(); self.conversation.add_line(ChatLine::new( (EntityType::Creature, 0), "You".to_owned(), "What's your business?".to_owned(), false )); self.conversation.add_line(ChatLine::new( guest.entity.id, guest.name.clone(), guest.say_agenda(& self.game.state), false )); self.conversation_scroll = 0; } }