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.
This commit is contained in:
Griffin Smith 2019-08-03 20:26:07 -04:00
parent 48fb3f6624
commit e2d2f011c6
4 changed files with 115 additions and 21 deletions

View file

@ -1,5 +1,6 @@
use crate::display::utils::clone_times; use crate::display::utils::clone_times;
use crate::display::utils::times; use crate::display::utils::times;
use crate::types::pos;
use crate::types::BoundingBox; use crate::types::BoundingBox;
use crate::types::Dimensions; use crate::types::Dimensions;
use crate::types::Neighbors; use crate::types::Neighbors;
@ -215,12 +216,21 @@ pub fn draw_box<W: Write>(
bbox: BoundingBox, bbox: BoundingBox,
style: BoxStyle, style: BoxStyle,
) -> io::Result<()> { ) -> io::Result<()> {
write!( let box_str = make_box(style, bbox.dimensions);
out, if bbox.position.x == 0 {
"{}{}", write!(out, "{}{}", bbox.position.cursor_goto(), box_str)?;
bbox.position.cursor_goto(), } else {
make_box(style, bbox.dimensions) 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)] #[cfg(test)]

View file

@ -3,6 +3,7 @@ use super::DrawWithNeighbors;
use crate::display::draw_box::draw_box; use crate::display::draw_box::draw_box;
use crate::display::utils::clone_times; use crate::display::utils::clone_times;
use crate::entities::entity::Entity; use crate::entities::entity::Entity;
use crate::types::menu::MenuInfo;
use crate::types::Neighbors; use crate::types::Neighbors;
use crate::types::{pos, BoundingBox, Direction, Position, Positioned}; use crate::types::{pos, BoundingBox, Direction, Position, Positioned};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
@ -192,6 +193,41 @@ impl<W: Write> Viewport<W> {
self.cursor_state = CursorState::Game; self.cursor_state = CursorState::Game;
Ok(()) 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<W> Positioned for Viewport<W> { impl<W> Positioned for Viewport<W> {
@ -218,7 +254,6 @@ impl<W: Write> Write for Viewport<W> {
mod tests { mod tests {
use super::*; use super::*;
use crate::types::Dimensions; use crate::types::Dimensions;
// use proptest::prelude::*;
#[test] #[test]
fn test_visible() { fn test_visible() {
@ -243,19 +278,26 @@ mod tests {
.visible(&Position { x: 1, y: 1 })); .visible(&Position { x: 1, y: 1 }));
} }
// proptest! { #[test]
// #[test] fn test_write_menu() {
// fn nothing_is_visible_in_viewport_off_screen(pos: Position, outer: BoundingBox) { let buf: Vec<u8> = Vec::new();
// 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)); 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"));
}
} }

31
src/types/menu.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::types::Dimensions;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MenuInfo {
pub prompt: String,
pub options: Vec<String>,
}
impl MenuInfo {
pub fn new(prompt: String, options: Vec<String>) -> 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,
}
}
}

View file

@ -5,10 +5,13 @@ use std::cmp::max;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops; use std::ops;
use std::rc::Rc; use std::rc::Rc;
pub mod collision; pub mod collision;
pub mod command; pub mod command;
pub mod direction; pub mod direction;
pub mod entity_map; pub mod entity_map;
pub mod menu;
pub use collision::Collision; pub use collision::Collision;
pub use direction::Direction; pub use direction::Direction;
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 /// Returns a bounding box representing the *inside* of this box if it was
/// drawn on the screen. /// drawn on the screen.
pub fn inner(self) -> BoundingBox { pub fn inner(self) -> BoundingBox {