From e2d2f011c6373894b3cdcfbdb98fbc783504561a Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Sat, 3 Aug 2019 20:26:07 -0400 Subject: Add method for writing option menus to viewport Add a method for writing single-choice menus to the viewport, within a box. Unused for now. --- src/display/draw_box.rs | 22 +++++++++++---- src/display/viewport.rs | 74 ++++++++++++++++++++++++++++++++++++++----------- src/types/menu.rs | 31 +++++++++++++++++++++ src/types/mod.rs | 11 ++++++++ 4 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 src/types/menu.rs diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs index 1b3958cefc..e4d34a7acd 100644 --- a/src/display/draw_box.rs +++ b/src/display/draw_box.rs @@ -1,5 +1,6 @@ use crate::display::utils::clone_times; use crate::display::utils::times; +use crate::types::pos; use crate::types::BoundingBox; use crate::types::Dimensions; use crate::types::Neighbors; @@ -215,12 +216,21 @@ pub fn draw_box( bbox: BoundingBox, style: BoxStyle, ) -> io::Result<()> { - write!( - out, - "{}{}", - bbox.position.cursor_goto(), - make_box(style, bbox.dimensions) - ) + let box_str = make_box(style, bbox.dimensions); + if bbox.position.x == 0 { + write!(out, "{}{}", bbox.position.cursor_goto(), box_str)?; + } else { + for (i, line) in box_str.split("\n\r").enumerate() { + debug!("line: {:?}!", line); + write!( + out, + "{}{}", + (bbox.position + pos(0, i as i16)).cursor_goto(), + line + )?; + } + } + Ok(()) } #[cfg(test)] diff --git a/src/display/viewport.rs b/src/display/viewport.rs index 9d17bc87dc..c44316cdaa 100644 --- a/src/display/viewport.rs +++ b/src/display/viewport.rs @@ -3,6 +3,7 @@ use super::DrawWithNeighbors; use crate::display::draw_box::draw_box; use crate::display::utils::clone_times; use crate::entities::entity::Entity; +use crate::types::menu::MenuInfo; use crate::types::Neighbors; use crate::types::{pos, BoundingBox, Direction, Position, Positioned}; use std::fmt::{self, Debug}; @@ -192,6 +193,41 @@ impl Viewport { self.cursor_state = CursorState::Game; Ok(()) } + + pub fn write_menu(&mut self, menu: &MenuInfo) -> io::Result<()> { + let menu_dims = menu.dimensions(); + + // TODO: check if the menu is too big + + let menu_position = self.game.position + pos(1, 1); + + let menu_box = BoundingBox { + dimensions: menu_dims, + position: menu_position, + }; + + debug!("writing menu at: {:?}", menu_box); + + draw_box(self, menu_box, BoxStyle::Thin)?; + + write!( + self, + "{}{}", + (menu_position + pos(2, 2)).cursor_goto(), + menu.prompt + )?; + + for (idx, option) in menu.options.iter().enumerate() { + write!( + self, + "{}{}", + (menu_position + pos(2, 4 + idx as i16)).cursor_goto(), + option + )?; + } + + Ok(()) + } } impl Positioned for Viewport { @@ -218,7 +254,6 @@ impl Write for Viewport { mod tests { use super::*; use crate::types::Dimensions; - // use proptest::prelude::*; #[test] fn test_visible() { @@ -243,19 +278,26 @@ mod tests { .visible(&Position { x: 1, y: 1 })); } - // proptest! { - // #[test] - // fn nothing_is_visible_in_viewport_off_screen(pos: Position, outer: BoundingBox) { - // let invisible_viewport = Viewport { - // outer, - // inner: BoundingBox { - // position: Position {x: -(outer.dimensions.w as i16), y: -(outer.dimensions.h as i16)}, - // dimensions: outer.dimensions, - // }, - // out: () - // }; - - // assert!(!invisible_viewport.visible(&pos)); - // } - // } + #[test] + fn test_write_menu() { + let buf: Vec = Vec::new(); + + let mut viewport = Viewport::new( + BoundingBox::at_origin(Dimensions::default()), + BoundingBox::at_origin(Dimensions::default()), + buf, + ); + + let menu = MenuInfo::new( + "Test menu".to_string(), + vec!["option 1".to_string(), "option 2".to_string()], + ); + + viewport.write_menu(&menu).unwrap(); + + let res = std::str::from_utf8(&viewport.out).unwrap(); + assert!(res.contains("Test menu")); + assert!(res.contains("option 1")); + assert!(res.contains("option 2")); + } } diff --git a/src/types/menu.rs b/src/types/menu.rs new file mode 100644 index 0000000000..63abc83778 --- /dev/null +++ b/src/types/menu.rs @@ -0,0 +1,31 @@ +use crate::types::Dimensions; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MenuInfo { + pub prompt: String, + pub options: Vec, +} + +impl MenuInfo { + pub fn new(prompt: String, options: Vec) -> Self { + MenuInfo { prompt, options } + } + + /// Returns the inner dimensions of a box necessary to draw this menu. Will + /// not trim either dimension to the size of the terminal + pub fn dimensions(&self) -> Dimensions { + Dimensions { + w: self + .options + .iter() + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(self.prompt.len()) as u16 + + 4, + h: self.options.len() as u16 + + if self.prompt.is_empty() { 0 } else { 2 } + + 4, + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 31d2ecd297..d417e873d8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,10 +5,13 @@ use std::cmp::max; use std::cmp::Ordering; use std::ops; use std::rc::Rc; + pub mod collision; pub mod command; pub mod direction; pub mod entity_map; +pub mod menu; + pub use collision::Collision; pub use direction::Direction; pub use direction::Direction::*; @@ -78,6 +81,14 @@ impl BoundingBox { }) } + pub fn ll_corner(self) -> Position { + self.position + + (Position { + x: 0, + y: self.dimensions.h as i16, + }) + } + /// Returns a bounding box representing the *inside* of this box if it was /// drawn on the screen. pub fn inner(self) -> BoundingBox { -- cgit 1.4.1