diff options
Diffstat (limited to 'src/display')
-rw-r--r-- | src/display/color.rs | 163 | ||||
-rw-r--r-- | src/display/draw_box.rs | 274 | ||||
-rw-r--r-- | src/display/mod.rs | 52 | ||||
-rw-r--r-- | src/display/utils.rs | 9 | ||||
-rw-r--r-- | src/display/viewport.rs | 303 |
5 files changed, 0 insertions, 801 deletions
diff --git a/src/display/color.rs b/src/display/color.rs deleted file mode 100644 index afe0039998b8..000000000000 --- a/src/display/color.rs +++ /dev/null @@ -1,163 +0,0 @@ -use serde::de::{self, Unexpected, Visitor}; -use std::fmt; -use std::marker::PhantomData; -use termion::color; - -#[derive(Debug)] -pub struct Color(Box<dyn color::Color>); - -unsafe impl Sync for Color {} -unsafe impl Send for Color {} - -impl Color { - pub fn new<C: color::Color + 'static>(c: C) -> Self { - Color(Box::new(c)) - } -} - -impl PartialEq for Color { - fn eq(&self, other: &Self) -> bool { - format!("{}{}", color::Fg(self), color::Bg(self)) - == format!("{}{}", color::Fg(other), color::Bg(other)) - } -} - -impl Eq for Color {} - -impl color::Color for Color { - fn write_fg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_fg(f) - } - - fn write_bg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_bg(f) - } -} - -impl<'a> color::Color for &'a Color { - fn write_fg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_fg(f) - } - - fn write_bg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_bg(f) - } -} - -impl Default for Color { - fn default() -> Self { - Color::new(color::Reset) - } -} - -pub struct ColorVisitor { - marker: PhantomData<fn() -> Color>, -} - -impl ColorVisitor { - fn new() -> Self { - ColorVisitor { - marker: PhantomData, - } - } -} - -impl<'de> Visitor<'de> for ColorVisitor { - type Value = Color; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("A color") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: de::Error, - { - match v.to_lowercase().as_ref() { - "black" => Ok(Color(Box::new(color::Black))), - "blue" => Ok(Color(Box::new(color::Blue))), - "cyan" => Ok(Color(Box::new(color::Cyan))), - "green" => Ok(Color(Box::new(color::Green))), - "light black" | "light_black" => { - Ok(Color(Box::new(color::LightBlack))) - } - "light blue" | "light_blue" => { - Ok(Color(Box::new(color::LightBlue))) - } - "light cyan" | "light_cyan" => { - Ok(Color(Box::new(color::LightCyan))) - } - "light green" | "light_green" => { - Ok(Color(Box::new(color::LightGreen))) - } - "light magenta" | "light_magenta" => { - Ok(Color(Box::new(color::LightMagenta))) - } - "light red" | "light_red" => Ok(Color(Box::new(color::LightRed))), - "light white" | "light_white" => { - Ok(Color(Box::new(color::LightWhite))) - } - "light yellow" | "light_yellow" => { - Ok(Color(Box::new(color::LightYellow))) - } - "magenta" => Ok(Color(Box::new(color::Magenta))), - "red" => Ok(Color(Box::new(color::Red))), - "white" => Ok(Color(Box::new(color::White))), - "yellow" => Ok(Color(Box::new(color::Yellow))), - _ => Err(de::Error::invalid_value( - Unexpected::Str(v), - &"a valid color", - )), - } - } - - fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> - where - A: de::MapAccess<'de>, - { - let mut red = None; - let mut green = None; - let mut blue = None; - while let Some((k, v)) = map.next_entry()? { - match k { - "red" => { - red = Some(v); - } - "green" => { - green = Some(v); - } - "blue" => { - blue = Some(v); - } - _ => { - return Err(de::Error::unknown_field( - k, - &["red", "green", "blue"], - )); - } - } - } - - match (red, green, blue) { - (Some(r), Some(g), Some(b)) => { - Ok(Color(Box::new(color::Rgb(r, g, b)))) - } - (None, _, _) => Err(de::Error::missing_field("red")), - (_, None, _) => Err(de::Error::missing_field("green")), - (_, _, None) => Err(de::Error::missing_field("blue")), - } - } - - fn visit_u8<E: de::Error>(self, v: u8) -> Result<Self::Value, E> { - Ok(Color(Box::new(color::AnsiValue(v)))) - } -} - -impl<'de> serde::Deserialize<'de> for Color { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(ColorVisitor::new()) - } -} diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs deleted file mode 100644 index e4d34a7acda9..000000000000 --- a/src/display/draw_box.rs +++ /dev/null @@ -1,274 +0,0 @@ -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; -use itertools::Itertools; -use proptest::prelude::Arbitrary; -use proptest::strategy; -use proptest_derive::Arbitrary; -use std::io::{self, Write}; - -// Box Drawing -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// U+250x ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ -// U+251x ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ -// U+252x ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ -// U+253x ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ -// U+254x ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ -// U+255x ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ -// U+256x ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ -// U+257x ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ - -static BOX: char = '☐'; - -static BOX_CHARS: [[char; 16]; 8] = [ - // 0 - [ - // 0 1 2 3 4 5 6 7 8 9 - '─', '━', '│', '┃', '┄', '┅', '┆', '┇', '┈', '┉', - // 10 - '┊', '┋', '┌', '┍', '┎', '┏', - ], - // 1 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┐', '┑', '┒', '┓', '└', '┕', '┖', '┗', '┘', '┙', - '┚', '┛', '├', '┝', '┞', '┟', - ], - // 2 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┠', '┡', '┢', '┣', '┤', '┥', '┦', '┧', '┨', '┩', - '┪', '┫', '┬', '┭', '┮', '┯', - ], - // 3 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┰', '┱', '┲', '┳', '┴', '┵', '┶', '┷', '┸', '┹', - '┺', '┻', '┼', '┽', '┾', '┿', - ], - // 4 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╀', '╁', '╂', '╃', '╄', '╅', '╆', '╇', '╈', '╉', - '╊', '╋', '╌', '╍', '╎', '╏', - ], - // 5 - [ - // 0 1 2 3 4 5 6 7 8 9 - '═', '║', '╒', '╓', '╔', '╕', '╖', '╗', '╘', '╙', - '╚', '╛', '╜', '╝', '╞', '╟', - ], - // 6 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╠', '╡', '╢', '╣', '╤', '╥', '╦', '╧', '╨', '╩', - '╪', '╫', '╬', '╭', '╮', '╯', - ], - // 7 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╰', '╱', '╲', '╳', '╴', '╵', '╶', '╷', '╸', '╹', - '╺', '╻', '╼', '╽', '╾', '╿', - ], -]; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BoxStyle { - Thin, - Thick, - Dotted, - ThickDotted, - Dashed, - ThickDashed, - Double, -} - -impl Arbitrary for BoxStyle { - type Parameters = (); - type Strategy = strategy::Just<Self>; - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - // TODO - strategy::Just(BoxStyle::Thin) - } -} - -pub trait Stylable { - fn style(&self, style: BoxStyle) -> char; -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -enum Corner { - TopRight, - TopLeft, - BottomRight, - BottomLeft, -} - -impl Stylable for Corner { - fn style(&self, style: BoxStyle) -> char { - use BoxStyle::*; - use Corner::*; - - match (self, style) { - (TopRight, Thin) => BOX_CHARS[1][0], - (TopLeft, Thin) => BOX_CHARS[0][12], - (BottomRight, Thin) => BOX_CHARS[1][8], - (BottomLeft, Thin) => BOX_CHARS[1][4], - _ => unimplemented!(), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -enum Line { - H, - V, -} - -impl Stylable for Line { - fn style(&self, style: BoxStyle) -> char { - use BoxStyle::*; - use Line::*; - match (self, style) { - (H, Thin) => BOX_CHARS[0][0], - (V, Thin) => BOX_CHARS[0][2], - _ => unimplemented!(), - } - } -} - -impl Stylable for Neighbors<Option<BoxStyle>> { - fn style(&self, _style: BoxStyle) -> char { - use BoxStyle::*; - match (self.left, self.right, self.top, self.bottom) { - (None, None, None, None) => BOX, - (Some(Thin), None, None, None) => BOX_CHARS[7][4], - (None, Some(Thin), None, None) => BOX_CHARS[7][6], - (None, None, Some(Thin), None) => BOX_CHARS[7][5], - (None, None, None, Some(Thin)) => BOX_CHARS[7][7], - (Some(Thin), Some(Thin), None, None) => Line::H.style(Thin), - (Some(Thin), None, Some(Thin), None) => { - Corner::BottomRight.style(Thin) - } - (Some(Thin), None, None, Some(Thin)) => { - Corner::TopRight.style(Thin) - } - (None, Some(Thin), Some(Thin), None) => { - Corner::BottomLeft.style(Thin) - } - (None, Some(Thin), None, Some(Thin)) => Corner::TopLeft.style(Thin), - (None, None, Some(Thin), Some(Thin)) => Line::V.style(Thin), - (None, Some(Thin), Some(Thin), Some(Thin)) => BOX_CHARS[1][12], - (Some(Thin), None, Some(Thin), Some(Thin)) => BOX_CHARS[2][4], - (Some(Thin), Some(Thin), None, Some(Thin)) => BOX_CHARS[2][12], - (Some(Thin), Some(Thin), Some(Thin), None) => BOX_CHARS[3][4], - (Some(Thin), Some(Thin), Some(Thin), Some(Thin)) => { - BOX_CHARS[3][12] - } - neighs => panic!("unimplemented: {:?}", neighs), - } - } -} - -#[must_use] -pub fn make_box(style: BoxStyle, dims: Dimensions) -> String { - if dims.h == 0 || dims.w == 0 { - "".to_string() - } else if dims.h == 1 && dims.w == 1 { - BOX.to_string() - } else if dims.h == 1 { - times(Line::H.style(style), dims.w) - } else if dims.w == 1 { - (0..dims.h).map(|_| Line::V.style(style)).join("\n\r") - } else { - let h_line: String = times(Line::H.style(style), dims.w - 2); - let v_line = Line::V.style(style); - let v_walls: String = clone_times( - format!( - "{}{}{}\n\r", - v_line, - times::<_, String>(' ', dims.w - 2), - v_line - ), - dims.h - 2, - ); - - format!( - "{}{}{}\n\r{}{}{}{}", - Corner::TopLeft.style(style), - h_line, - Corner::TopRight.style(style), - v_walls, - Corner::BottomLeft.style(style), - h_line, - Corner::BottomRight.style(style), - ) - } -} - -/// Draw the box described by the given BoundingBox's position and dimensions to -/// the given output, with the given style -pub fn draw_box<W: Write>( - out: &mut W, - bbox: BoundingBox, - style: BoxStyle, -) -> io::Result<()> { - 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)] -mod tests { - use super::*; - use proptest::prelude::*; - - #[test] - fn make_thin_box() { - let res = make_box(BoxStyle::Thin, Dimensions { w: 10, h: 10 }); - assert_eq!( - res, - "┌────────┐ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r└────────┘" - ); - } - - proptest! { - #[test] - fn box_has_height_lines(dims: Dimensions, style: BoxStyle) { - let res = make_box(style, dims); - prop_assume!((dims.w > 0 && dims.h > 0)); - assert_eq!(res.split("\n\r").count(), dims.h as usize); - } - - #[test] - fn box_lines_have_width_length(dims: Dimensions, style: BoxStyle) { - let res = make_box(style, dims); - prop_assume!(dims.w == 0 && dims.h == 0 || (dims.w > 0 && dims.h > 0)); - assert!(res.split("\n\r").all(|l| l.chars().count() == dims.w as usize)); - } - } -} diff --git a/src/display/mod.rs b/src/display/mod.rs deleted file mode 100644 index 6e37a03d8c55..000000000000 --- a/src/display/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub mod color; -pub mod draw_box; -pub mod utils; -pub mod viewport; -use crate::entities::entity::Entity; -use crate::types::Neighbors; -use crate::types::Positioned; -pub use draw_box::{make_box, BoxStyle}; -use std::io::{self, Write}; -use termion::{clear, cursor, style}; -pub use viewport::Viewport; - -pub fn clear<T: Write>(out: &mut T) -> io::Result<()> { - write!(out, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1)) -} - -pub trait Draw: Positioned { - /// Draw this entity, assuming the character is already at the correct - /// position - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()>; -} - -impl<T: Draw> Draw for &T { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - (**self).do_draw(out) - } -} - -impl<T: Draw> Draw for Box<T> { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - (**self).do_draw(out) - } -} - -pub trait DrawWithNeighbors: Positioned { - #[allow(clippy::borrowed_box)] - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()>; -} - -impl<T: Draw> DrawWithNeighbors for T { - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - _neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()> { - self.do_draw(out) - } -} diff --git a/src/display/utils.rs b/src/display/utils.rs deleted file mode 100644 index acd4416cb884..000000000000 --- a/src/display/utils.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::iter::FromIterator; - -pub fn times<A: Copy, B: FromIterator<A>>(elem: A, n: u16) -> B { - (0..n).map(|_| elem).collect() -} - -pub fn clone_times<A: Clone, B: FromIterator<A>>(elem: A, n: u16) -> B { - (0..n).map(|_| elem.clone()).collect() -} diff --git a/src/display/viewport.rs b/src/display/viewport.rs deleted file mode 100644 index c44316cdaad5..000000000000 --- a/src/display/viewport.rs +++ /dev/null @@ -1,303 +0,0 @@ -use super::BoxStyle; -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}; -use std::io::{self, Write}; - -pub enum CursorState { - Game, - Prompt(Position), -} - -impl Default for CursorState { - fn default() -> Self { - CursorState::Game - } -} - -pub struct Viewport<W> { - /// The box describing the visible part of the viewport. - /// - /// Generally the size of the terminal, and positioned at 0, 0 - pub outer: BoundingBox, - - /// The box describing the game part of the viewport. - pub game: BoundingBox, - - /// The box describing the inner part of the viewport - /// - /// Its position is relative to `outer.inner()`, and its size should - /// generally not be smaller than outer - pub inner: BoundingBox, - - /// The actual screen that the viewport writes to - pub out: W, - - cursor_state: CursorState, - - /// Reset the cursor back to this position after every draw - pub game_cursor_position: Position, -} - -impl<W> Viewport<W> { - pub fn new(outer: BoundingBox, inner: BoundingBox, out: W) -> Self { - Viewport { - outer, - inner, - out, - game: outer.move_tr_corner(Position { x: 0, y: 1 }), - cursor_state: Default::default(), - game_cursor_position: pos(0, 0), - } - } - - /// Returns true if the (inner-relative) position of the given entity is - /// visible within this viewport - pub fn visible<E: Positioned>(&self, ent: &E) -> bool { - self.on_screen(ent.position()).within(self.game.inner()) - } - - /// Convert the given inner-relative position to one on the actual screen - fn on_screen(&self, pos: Position) -> Position { - pos + self.inner.position + self.game.inner().position - } -} - -impl<W> Debug for Viewport<W> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}", - self.outer, self.inner - ) - } -} - -impl<W: Write> Viewport<W> { - /// Draw the given entity to the viewport at its position, if visible - #[allow(clippy::borrowed_box)] - pub fn draw<T: DrawWithNeighbors>( - &mut self, - entity: &T, - neighbors: &Neighbors<Vec<&Box<dyn Entity>>>, - ) -> io::Result<()> { - if !self.visible(entity) { - return Ok(()); - } - self.cursor_goto(entity.position())?; - entity.do_draw_with_neighbors(self, neighbors)?; - self.reset_cursor() - } - - fn reset_cursor(&mut self) -> io::Result<()> { - self.cursor_goto(self.game_cursor_position) - } - - /// Move the cursor to the given inner-relative position - pub fn cursor_goto(&mut self, pos: Position) -> io::Result<()> { - write!(self, "{}", self.on_screen(pos).cursor_goto()) - } - - /// Clear whatever single character is drawn at the given inner-relative - /// position, if visible - pub fn clear(&mut self, pos: Position) -> io::Result<()> { - write!(self, "{} ", self.on_screen(pos).cursor_goto(),)?; - self.reset_cursor() - } - - /// Initialize this viewport by drawing its outer box to the screen - pub fn init(&mut self) -> io::Result<()> { - draw_box(self, self.game, BoxStyle::Thin) - } - - /// Write a message to the message area on the screen - /// - /// Will overwrite any message already present, and if the given message is - /// longer than the screen will truncate. This means callers should handle - /// message buffering and ellipsisization - pub fn write_message(&mut self, msg: &str) -> io::Result<usize> { - let msg_to_write = if msg.len() <= self.outer.dimensions.w as usize { - msg - } else { - &msg[0..self.outer.dimensions.w as usize] - }; - write!( - self, - "{}{}{}", - self.outer.position.cursor_goto(), - msg_to_write, - clone_times::<_, String>( - " ".to_string(), - self.outer.dimensions.w - msg.len() as u16 - ), - )?; - self.reset_cursor()?; - Ok(msg_to_write.len()) - } - - pub fn clear_message(&mut self) -> io::Result<()> { - write!( - self, - "{}{}", - self.outer.position.cursor_goto(), - clone_times::<_, String>( - " ".to_string(), - self.outer.dimensions.w as u16 - ) - )?; - self.reset_cursor() - } - - /// Write a prompt requesting text input to the message area on the screen. - /// - /// Will overwrite any message already present, and if the given message is - /// longer than the screen will truncate. This means callers should handle - /// message buffering and ellipsisization - pub fn write_prompt<'a, 'b>(&'a mut self, msg: &'b str) -> io::Result<()> { - let len = self.write_message(msg)? + 1; - let pos = self.outer.position + pos(len as i16, 0); - self.cursor_state = CursorState::Prompt(pos); - write!(self, "{}", pos.cursor_goto())?; - self.flush() - } - - pub fn push_prompt_chr(&mut self, chr: char) -> io::Result<()> { - if let CursorState::Prompt(pos) = self.cursor_state { - write!(self, "{}", chr)?; - self.cursor_state = CursorState::Prompt(pos + Direction::Right); - } - Ok(()) - } - - pub fn pop_prompt_chr(&mut self) -> io::Result<()> { - if let CursorState::Prompt(pos) = self.cursor_state { - let new_pos = pos + Direction::Left; - write!( - self, - "{} {}", - new_pos.cursor_goto(), - new_pos.cursor_goto() - )?; - self.cursor_state = CursorState::Prompt(new_pos); - } - Ok(()) - } - - pub fn clear_prompt(&mut self) -> io::Result<()> { - self.clear_message()?; - 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> { - fn position(&self) -> Position { - self.outer.position - } -} - -impl<W: Write> Write for Viewport<W> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - self.out.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.out.flush() - } - - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.out.write_all(buf) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::Dimensions; - - #[test] - fn test_visible() { - assert!(Viewport::new( - BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - BoundingBox { - position: Position { x: -10, y: -10 }, - dimensions: Dimensions { w: 15, h: 15 }, - }, - () - ) - .visible(&Position { x: 13, y: 13 })); - - assert!(!Viewport::new( - BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - BoundingBox { - position: Position { x: -10, y: -10 }, - dimensions: Dimensions { w: 15, h: 15 }, - }, - (), - ) - .visible(&Position { x: 1, y: 1 })); - } - - #[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")); - } -} |