use crossterm::style::Color; use crate::{ geometry::{Pos, Rect, Size}, runes::{Rune, Runes}, }; /// A renderable region. View stores the renderable state of an area of the /// screen. Views can be combined together to achieve a finalized view that /// repsresents the entire screens next render. #[derive(Clone, Debug)] pub struct View(pub Vec>); impl std::ops::DerefMut for View { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl std::ops::Deref for View { type Target = Vec>; fn deref(&self) -> &Self::Target { &self.0 } } impl View { /// Construct a new view for a given region size. pub fn new(size: T) -> Self where T: Into, { let size: Size = size.into(); Self(vec![vec![Rune::default(); size.width]; size.height]) } /// Return an iterator for all runes in the view. pub fn iter(&self) -> impl Iterator> { self.0.iter() } /// Apply another view onto this view at a given position. pub fn apply>(&mut self, pos: P, view: &View) { let pos = pos.into(); for (y, line) in view.0.iter().enumerate() { if self.0.len() > y + pos.y { for (x, rune) in line.iter().enumerate() { if rune.content.is_some() && self.0[y].len() > x + pos.x { let rune = (self.0[y + pos.y][x + pos.x]) + *rune; let _ = std::mem::replace(&mut self.0[y + pos.y][x + pos.x], rune); } } } } } // The width of the view. pub fn width(&self) -> usize { self.0.first().map(|i| i.len()).unwrap_or_default() } /// The height of the view. pub fn height(&self) -> usize { self.0.len() } /// The region size of the view. pub fn size(&self) -> Size { (self.width(), self.height()).into() } /// Fill a region of the view with a single rune, repeating it in every /// position. pub fn fill(&mut self, rect: R, rune: U) where R: Into, U: Into, { let rect = rect.into(); let rune = rune.into(); for y in rect.pos.y..(rect.size.height + rect.pos.y).min(self.0.len()) { for x in rect.pos.x..(rect.size.width + rect.pos.x).min(self.0[y].len()) { let _ = std::mem::replace(&mut self.0[y][x], rune); } } } /// Fill the entire view context with a rune pub fn fill_all(&mut self, rune: R) where R: Into, { let rune = rune.into(); let rect = Rect::new((0, 0), self.size()); for y in rect.pos.y..(rect.size.height + rect.pos.y).min(self.0.len()) { for x in rect.pos.x..(rect.size.width + rect.pos.x).min(self.0[y].len()) { let _ = std::mem::replace(&mut self.0[y][x], rune); } } } /// Insert a string at the specific position in the view. Each chacter is /// mapped to a rune and placed starting at the position given and /// continueing to the right /// /// This function performs no wrapping of any kind. pub fn insert, S: Into>(&mut self, pos: P, value: S) { let Pos { x, y } = pos.into(); let runes: Runes = value.into(); if let Some(line) = self.0.get_mut(y) { let line_len = line.len() as i32; for (i, c) in runes .iter() .take((line_len - x as i32).max(0) as usize) .enumerate() { let rune = line[x + i] + *c; let _ = std::mem::replace(&mut line[x + i], rune); } } } #[cfg(test)] pub fn render_text(&self) -> String { self.0.iter().fold(String::new(), |mut acc, line| { acc.push_str( &line .into_iter() .map(|r| r.content.unwrap_or_default()) .collect::(), ); acc.push('\n'); acc }) } } #[cfg(test)] mod tests { use crossterm::style::Color; use crate::{geometry::Rect, runes::Rune}; use super::View; #[test] pub fn test_insert_pos() { let mut view = View::new((5, 3)); view.insert((1, 2), "test"); dbg!(&view.0); assert_eq!(view.0[2][1].content, Some('t')); assert_eq!(view.0[2][2].content, Some('e')); assert_eq!(view.0[2][3].content, Some('s')); assert_eq!(view.0[2][4].content, Some('t')); } #[test] pub fn test_fill() { let mut view = View::new((3, 3)); view.fill(Rect::new((1, 1), (2, 2)), Rune::new().content('X')); dbg!(&view.0); assert_eq!(view.0[0][0].content, None); assert_eq!(view.0[0][1].content, None); assert_eq!(view.0[0][2].content, None); assert_eq!(view.0[1][0].content, None); assert_eq!(view.0[1][1].content, Some('X')); assert_eq!(view.0[1][2].content, Some('X')); assert_eq!(view.0[2][0].content, None); assert_eq!(view.0[2][1].content, Some('X')); assert_eq!(view.0[2][2].content, Some('X')); } #[test] pub fn test_fill_overflow() { let mut view = View::new((3, 3)); view.fill(Rect::new((1, 1), (4, 4)), Rune::new().content('X')); dbg!(&view.0); assert_eq!(view.0[0][0].content, None); assert_eq!(view.0[0][1].content, None); assert_eq!(view.0[0][2].content, None); assert_eq!(view.0[1][0].content, None); assert_eq!(view.0[1][1].content, Some('X')); assert_eq!(view.0[1][2].content, Some('X')); assert_eq!(view.0[2][0].content, None); assert_eq!(view.0[2][1].content, Some('X')); assert_eq!(view.0[2][2].content, Some('X')); } #[test] pub fn test_apply() { let mut view1 = View::new((3, 4)); view1.fill(Rect::new((1, 1), (2, 2)), Rune::new().content('X')); let mut view2 = View::new((3, 4)); view2.apply((0, 1), &view1); dbg!(&view2.0); assert_eq!(view2.0[0][0].content, None); assert_eq!(view2.0[0][1].content, None); assert_eq!(view2.0[0][2].content, None); assert_eq!(view2.0[1][0].content, None); assert_eq!(view2.0[1][1].content, None); assert_eq!(view2.0[1][2].content, None); assert_eq!(view2.0[2][0].content, None); assert_eq!(view2.0[2][1].content, Some('X')); assert_eq!(view2.0[2][2].content, Some('X')); assert_eq!(view2.0[3][0].content, None); assert_eq!(view2.0[3][1].content, Some('X')); assert_eq!(view2.0[3][2].content, Some('X')); } #[test] pub fn test_apply_overflow() { let mut view0 = View::new((5, 5)); view0.fill(Rect::new((1, 1), (4, 4)), Rune::new().content('X')); let mut view = View::new((3, 3)); view.apply((0, 0), &view0); dbg!(&view.0); assert_eq!(view.0[0][0].content, None); assert_eq!(view.0[0][1].content, None); assert_eq!(view.0[0][2].content, None); assert_eq!(view.0[1][0].content, None); assert_eq!(view.0[1][1].content, Some('X')); assert_eq!(view.0[1][2].content, Some('X')); assert_eq!(view.0[2][0].content, None); assert_eq!(view.0[2][1].content, Some('X')); assert_eq!(view.0[2][2].content, Some('X')); } #[test] pub fn test_color_fill() { let mut view = View::new((3, 3)); view.fill_all(Color::Red); assert!(view .iter() .all(|rs| rs.iter().all(|r| r.bg == Some(Color::Red)))); } }