From 9e51daf5665ddd42cbd3609c0101bbf40cd1a54d Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Wed, 11 Jan 2023 20:56:47 +0100 Subject: [PATCH] WIP moving view into own structs --- src/main.rs | 3 +- src/ui/debug_view.rs | 6 +- src/ui/guest_selection.rs | 9 +-- src/ui/mod.rs | 144 ++++++++++++++++---------------------- src/ui/talk_to_guest.rs | 138 ++++++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 94 deletions(-) create mode 100644 src/ui/talk_to_guest.rs diff --git a/src/main.rs b/src/main.rs index 0ac93e2..684a034 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,8 +129,7 @@ fn main() -> Result<(), io::Error> { let mut app = App::new(game); loop { - terminal.draw(|f| app.draw(f))?; - if !app.step() { + if !app.do_loop(&mut terminal) { break; } } diff --git a/src/ui/debug_view.rs b/src/ui/debug_view.rs index 41c4270..6fbfd74 100644 --- a/src/ui/debug_view.rs +++ b/src/ui/debug_view.rs @@ -3,7 +3,7 @@ use tui::{backend::Backend, Frame}; use crate::{world::Terrain, game::Game}; -use super::{controls::Controls, DefaultLayout, status_line::StatusLine, AppStatus}; +use super::{controls::Controls, DefaultLayout, status_line::StatusLine, AppStatus, guest_selection::GuestSelectionView}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum DebugTab { @@ -56,12 +56,12 @@ impl<'a> DebugView<'a> { view } - pub fn control(&mut self, _game: &mut Game) -> (bool, Option) { + pub fn control(&mut self, game: &mut Game) -> (bool, Option) { match read() { Ok(Event::Key(event)) => { match event.code { KeyCode::Esc => { - return (true, Some(AppStatus::GuestSelection)) + return (true, Some(AppStatus::GuestSelection(GuestSelectionView::new(game)))); }, KeyCode::Up => { if self.selected == DebugTab::Map { diff --git a/src/ui/guest_selection.rs b/src/ui/guest_selection.rs index 640fba7..c1a154e 100644 --- a/src/ui/guest_selection.rs +++ b/src/ui/guest_selection.rs @@ -3,7 +3,7 @@ use tui::{backend::Backend, Frame}; use crate::game::Game; -use super::{AppStatus, DefaultLayout, controls::Controls, status_line::StatusLine}; +use super::{AppStatus, DefaultLayout, controls::Controls, status_line::StatusLine, talk_to_guest::TalkToGuestView, debug_view::DebugView}; pub struct GuestSelectionView<'a> { guest_list: Vec>, @@ -49,17 +49,14 @@ impl<'a> GuestSelectionView<'a> { if game.state.guests().len() > 0 { match self.guest_list_state.selected() { Some(selected) => { - // let guest_id = &self.game.state.guests()[selected]; - // self.conversation = Chat::new(); - // self.greet_guest(Some(*guest_id)); - // self.status = AppStatus::TalkToGuest(Some(*guest_id)); + return (true, Some(AppStatus::TalkToGuest(TalkToGuestView::new(game, game.state.guests()[selected])))) }, None => {} } } }, KeyCode::Char('?') => { - return (true, Some(AppStatus::Debug)) + return (true, Some(AppStatus::Debug(DebugView::new(game)))) }, KeyCode::Char('.') => { game.step(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 055a234..88fa9db 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,14 +2,16 @@ 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}; +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}; +use self::{chat::{Chat, ChatLine}, controls::Controls, debug_view::DebugView, guest_selection::GuestSelectionView, talk_to_guest::TalkToGuestView}; /** * |........................| @@ -20,27 +22,28 @@ use self::{chat::{Chat, ChatLine}, controls::Controls, debug_view::DebugView, gu * |........................| */ -pub enum AppStatus { +pub enum AppStatus<'a> { Initial, - GuestSelection, - TalkToGuest(Option), + GuestSelection(GuestSelectionView<'a>), + TalkToGuest(TalkToGuestView), BuyFromGuest(EntityId), - Debug, + Debug(DebugView<'a>), } pub struct App<'a> { game: Game, - status: AppStatus, - - // Guest Selection - guest_selection: Option>, + status: AppStatus<'a>, + next_status: Option>, + // Conversation conversation: Chat, conversation_scroll: u16, - - // Debug - debug_view: Option>, + + // // Guest Selection + // guest_selection: Option>, + // // Debug + // debug_view: Option>, } pub struct DefaultLayout { @@ -74,85 +77,60 @@ impl<'a> App<'a> { pub fn new(state: Game) -> App<'a> { App { game: state, - guest_selection: None, status: AppStatus::Initial, conversation: Chat::new(), conversation_scroll: 0, - debug_view: None, + next_status: None, + // guest_selection: None, + // debug_view: None, } } - pub fn step(&mut self) -> bool { - match &self.status { + 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 => { - self.guest_selection = Some(GuestSelectionView::new(&self.game)); - self.status = AppStatus::GuestSelection; - true + ret.1 = Some(AppStatus::GuestSelection(GuestSelectionView::new(&self.game))); }, - AppStatus::GuestSelection => { - let ret = self.guest_selection.as_mut().unwrap().control(&mut self.game); - if let Some(next) = ret.1 { - self.debug_view = None; - self.status = next; - } - ret.0 + AppStatus::GuestSelection(ref mut guest_selection) => { + ret = guest_selection.control(&mut self.game); }, - AppStatus::TalkToGuest(guest) => { - match read() { - Ok(Event::Key(event)) => { - match event.code { - KeyCode::Esc => { - self.status = AppStatus::GuestSelection; - }, - KeyCode::Char('a') => { - self.ask_business(*guest); - }, - KeyCode::Char('b') => { - self.status = AppStatus::BuyFromGuest(guest.unwrap()); - }, - KeyCode::Up => { - self.conversation_scroll += 1; - }, - KeyCode::Down => { - if self.conversation_scroll > 0 { - self.conversation_scroll -= 1; - } - }, - _ => {} - } - }, - _ => {} - } - true + 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)); - }, - _ => {} - } - }, - _ => {} - } - true + // match read() { + // Ok(Event::Key(event)) => { + // match event.code { + // KeyCode::Esc => { + // self.status = AppStatus::TalkToGuest(Some(*guest_id)); + // }, + // _ => {} + // } + // }, + // _ => {} + // } }, - AppStatus::Debug => { - let ret = self.debug_view.as_mut().unwrap().control(&mut self.game); - if let Some(next) = ret.1 { - self.debug_view = None; - self.status = next; - } - ret.0 + AppStatus::Debug(debug_view) => { + ret = debug_view.control(&mut self.game); } } + self.next_status = ret.1; + ret.0 } fn open_debug(&mut self) { - self.debug_view = Some(DebugView::new(&self.game)); - self.status = AppStatus::Debug; + self.status = AppStatus::Debug(DebugView::new(&self.game)); } // fn default_layout(&self) -> Layout { @@ -160,21 +138,21 @@ impl<'a> App<'a> { // } pub fn draw(&mut self, f: &mut Frame) { - match &self.status { + match &mut self.status { AppStatus::Initial => { self.draw_initial(f); }, - AppStatus::GuestSelection => { - self.guest_selection.as_mut().unwrap().draw(f, &self.game); + AppStatus::GuestSelection(view) => { + view.draw(f, &self.game); }, - AppStatus::TalkToGuest(guest_id) => { - self.draw_talk_to_guest(f, *guest_id); + AppStatus::TalkToGuest(view) => { + view.draw(f, &self.game); }, AppStatus::BuyFromGuest(guest_id) => { - self.draw_buy_from_guest(f, *guest_id); + // self.draw_buy_from_guest(f, *guest_id); }, - AppStatus::Debug => { - self.debug_view.as_mut().unwrap().draw(f, &self.game); + AppStatus::Debug(view) => { + view.draw(f, &self.game); }, } diff --git a/src/ui/talk_to_guest.rs b/src/ui/talk_to_guest.rs new file mode 100644 index 0000000..afd80d6 --- /dev/null +++ b/src/ui/talk_to_guest.rs @@ -0,0 +1,138 @@ +use crossterm::event::{read, Event, KeyCode}; +use tui::{backend::Backend, Frame}; + +use crate::{game::Game, entity::{EntityId, EntityType}}; + +use super::{AppStatus, DefaultLayout, controls::Controls, status_line::StatusLine, chat::{Chat, ChatLine}, guest_selection::GuestSelectionView}; + +pub struct TalkToGuestView { + guest_id: EntityId, + conversation: Chat, + conversation_scroll: u16, +} + +impl TalkToGuestView { + pub fn new(game: &Game, guest_id: EntityId) -> TalkToGuestView { + let mut view = TalkToGuestView { + guest_id, + conversation: Chat::new(), + conversation_scroll: 0, + }; + view.greet_guest(game); + view + } + + pub fn control(&mut self, game: &mut Game) -> (bool, Option) { + match read() { + Ok(Event::Key(event)) => { + match event.code { + KeyCode::Esc => { + return (true, Some(AppStatus::GuestSelection(GuestSelectionView::new(game)))); + }, + KeyCode::Char('a') => { + self.ask_business(game); + }, + KeyCode::Char('b') => { + // self.status = AppStatus::BuyFromGuest(guest.unwrap()); + }, + KeyCode::Up => { + self.conversation_scroll += 1; + }, + KeyCode::Down => { + if self.conversation_scroll > 0 { + self.conversation_scroll -= 1; + } + }, + _ => {} + } + }, + _ => {} + } + return (true, None); + } + + pub fn draw(&mut self, f: &mut Frame, game: &Game) { + let chunks = DefaultLayout::default(f.size()); + + let guest = game.state.get_creature(self.guest_id).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(); + + StatusLine::draw(f, chunks.status, game); + f.render_widget(main_window, chunks.main); + f.render_widget(controls, chunks.controls); + } + + /** + * Conversation + */ + fn greet_guest(&mut self, game: &Game) { + let guest = game.state.get_creature(self.guest_id).unwrap(); + self.conversation.add_line(ChatLine::new( + (EntityType::Creature, 0), + "You".to_owned(), + "Greetings traveller!".to_owned(), + false + )); + self.conversation.add_line(ChatLine::new( + self.guest_id, + 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, game: &mut Game) { + let guest = game.state.get_creature(self.guest_id).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(& game.state), + false + )); + self.conversation_scroll = 0; + } + +} \ No newline at end of file