about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/display/draw_box.rs22
-rw-r--r--src/display/viewport.rs74
-rw-r--r--src/types/menu.rs31
-rw-r--r--src/types/mod.rs11
4 files changed, 116 insertions, 22 deletions
diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs
index 1b3958cefca8..e4d34a7acda9 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<W: Write>(
     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 9d17bc87dcff..c44316cdaad5 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<W: Write> Viewport<W> {
         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<W> Positioned for Viewport<W> {
@@ -218,7 +254,6 @@ impl<W: Write> Write for Viewport<W> {
 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<u8> = 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 000000000000..63abc837788e
--- /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<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,
+        }
+    }
+}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 31d2ecd29770..d417e873d8ca 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 {