diff --git a/src/ui/controls.rs b/src/ui/controls.rs new file mode 100644 index 0000000..5e6d4f2 --- /dev/null +++ b/src/ui/controls.rs @@ -0,0 +1,40 @@ +pub struct ControlsEntry { + pub key: String, + pub action: String, +} + +pub struct Controls { + pub entries: Vec +} + +impl Controls { + + pub fn new() -> Controls { + Controls { + entries: vec![] + } + } + + pub fn add(&mut self, key: String, action: String) -> &mut Self { + self.entries.push(ControlsEntry { + key, + action + }); + self + } + + pub fn render(&self) -> tui::widgets::Paragraph { + let key_style = tui::style::Style::default() + .add_modifier(tui::style::Modifier::BOLD) + .fg(tui::style::Color::Green) + .bg(tui::style::Color::Black); + let control_text = self.entries.iter().fold(Vec::new(), |mut spans, entry| { + spans.push(tui::text::Span::styled(entry.key.clone(), key_style)); + spans.push(tui::text::Span::raw(format!(" {}", entry.action))); + spans.push(tui::text::Span::raw(" ")); + spans + }); + tui::widgets::Paragraph::new(tui::text::Spans::from(control_text)) + .style(tui::style::Style::default().fg(tui::style::Color::White)) + } +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f1feef8..9304d1a 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,11 +1,12 @@ mod chat; +mod controls; use crossterm::event::{read, Event, KeyCode}; use tui::{backend::Backend, Frame, layout::Layout}; use crate::{game::Game, entity::EntityId}; -use self::chat::{Chat, ChatLine}; +use self::{chat::{Chat, ChatLine}, controls::Controls}; /** * |........................| @@ -28,6 +29,7 @@ pub struct App<'a> { guest_list_state: tui::widgets::ListState, status: AppStatus, conversation: Chat, + conversation_scroll: u16, } impl<'a> App<'a> { @@ -38,6 +40,7 @@ impl<'a> App<'a> { guest_list_state: tui::widgets::ListState::default(), status: AppStatus::Initial, conversation: Chat::new(), + conversation_scroll: 0, } } @@ -71,11 +74,17 @@ impl<'a> App<'a> { } }, KeyCode::Enter => { - let selected = self.guest_list_state.selected().unwrap(); - 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)); + if self.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)); + }, + None => {} + } + } }, KeyCode::Char('.') => { self.game.step(); @@ -101,6 +110,14 @@ impl<'a> App<'a> { KeyCode::Char('a') => { self.ask_business(*guest); }, + KeyCode::Up => { + self.conversation_scroll += 1; + }, + KeyCode::Down => { + if self.conversation_scroll > 0 { + self.conversation_scroll -= 1; + } + }, _ => {} } }, @@ -111,6 +128,18 @@ impl<'a> App<'a> { } } + fn default_layout(&self) -> Layout { + Layout::default() + .direction(tui::layout::Direction::Vertical) + .constraints( + [ + tui::layout::Constraint::Min(3), + tui::layout::Constraint::Length(2) + ] + .as_ref(), + ) + } + pub fn draw(&mut self, f: &mut Frame) { match &self.status { AppStatus::Initial => { @@ -129,16 +158,7 @@ impl<'a> App<'a> { pub fn draw_initial(&mut self, _f: &mut Frame) {} pub fn draw_guest_selection(&mut self, f: &mut Frame) { - let chunks = Layout::default() - .direction(tui::layout::Direction::Vertical) - .constraints( - [ - tui::layout::Constraint::Min(3), - tui::layout::Constraint::Length(2) - ] - .as_ref(), - ) - .split(f.size()); + let chunks = self.default_layout().split(f.size()); let main_window = tui::widgets::List::new(self.guest_list.clone()) .block(tui::widgets::Block::default().title("Guests").borders(tui::widgets::Borders::ALL)) @@ -146,31 +166,20 @@ impl<'a> App<'a> { .highlight_style(tui::style::Style::default().add_modifier(tui::style::Modifier::ITALIC)) .highlight_symbol(">>"); - let key_style = tui::style::Style::default() - .add_modifier(tui::style::Modifier::BOLD) - .fg(tui::style::Color::Green) - .bg(tui::style::Color::Black); - let control_text = tui::text::Spans::from(vec![ - tui::text::Span::styled("↑↓", key_style), - tui::text::Span::raw(" select guest"), - tui::text::Span::raw(" "), - tui::text::Span::styled("⏎", key_style), - tui::text::Span::raw(" talk to guest"), - tui::text::Span::raw(" "), - tui::text::Span::styled(".", key_style), - tui::text::Span::raw(" pass one day"), - tui::text::Span::raw(" "), - tui::text::Span::styled("Esc", key_style), - tui::text::Span::raw(" quit"), - - ]); - let controls = tui::widgets::Paragraph::new(control_text) - .style(tui::style::Style::default().fg(tui::style::Color::White)); + let mut binding = Controls::new(); + let controls = binding + .add("↑↓".to_owned(), "select guest".to_owned()) + .add("⏎".to_owned(), "talk to guest".to_owned()) + .add(".".to_owned(), "pass one day".to_owned()) + .add("Esc".to_owned(), "quit".to_owned()) + .render(); f.render_stateful_widget(main_window, chunks[0], &mut self.guest_list_state); f.render_widget(controls, chunks[1]); } fn draw_talk_to_guest(&self, f: &mut Frame, guest: Option) { + let chunks = self.default_layout().split(f.size()); + let guest = self.game.state.get_creature(guest.unwrap()).unwrap(); let key_style = tui::style::Style::default() @@ -181,14 +190,38 @@ impl<'a> App<'a> { 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?"), + tui::text::Span::raw("What's your business?\n\n"), + ])); + full_text.push(tui::text::Spans::from(vec![ + tui::text::Span::styled("(b) ", key_style), + tui::text::Span::raw("Let's trade?\n\n"), + ])); + full_text.push(tui::text::Spans::from(vec![ + tui::text::Span::styled("(c) ", key_style), + tui::text::Span::raw("Heard of anything interesting?\n\n"), ])); let text = tui::text::Text::from(full_text); + let scroll: u16 = ((text.lines.len() as u16) ) + .checked_sub( + (chunks[0].height as u16).checked_sub(2).unwrap_or(0) // 2 lines for the border + ) + .unwrap_or(0) + .checked_sub(self.conversation_scroll) + .unwrap_or(0); 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)); - f.render_widget(main_window, f.size()); + .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(); + + f.render_widget(main_window, chunks[0]); + f.render_widget(controls, chunks[1]); } /** @@ -208,6 +241,7 @@ impl<'a> App<'a> { "Hello, I'm ".to_owned() + &guest.name + ".", false )); + self.conversation_scroll = 0; } fn ask_business(&mut self, guest: Option) { @@ -224,5 +258,6 @@ impl<'a> App<'a> { guest.say_agenda(& self.game.state), false )); + self.conversation_scroll = 0; } } \ No newline at end of file