use super::*; use palette::{FromColor, Srgb}; #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct TextChar { pub ch: char, pub fg: Srgb, pub bg: Option>, } impl TextChar { #[inline] pub fn new(ch: char, fg: C1, bg: Option) -> Self where Srgb: FromColor, Srgb: FromColor, { Self { ch, fg: Srgb::from_color(fg), bg: bg.map(|x| Srgb::from_color(x)), } } #[inline] pub fn from_char(ch: impl Into) -> Self { Self { ch: ch.into(), fg: Srgb::new(255, 255, 255), bg: None, } } #[inline] pub fn from_state(ch: impl Into, state: State) -> Self { Self { ch: ch.into(), fg: match state { State::Idle => Srgb::new(128, 128, 128), State::Active => Srgb::new(220, 255, 255), State::Dormant => Srgb::new(100, 60, 60), }, bg: None, } } } impl Default for TextChar { fn default() -> Self { Self { ch: ' ', fg: Srgb::new(255, 255, 255), bg: None, } } } #[derive(Clone)] pub struct TextSurface { width: usize, height: usize, chars: Vec, } impl TextSurface { pub fn new(width: usize, height: usize) -> Self { // TODO: return None if width or height don't fit in an i32 Self { width, height, chars: vec![TextChar::default(); width * height], } } /// Returns the [`TextChar`] at `(x, y)`, if it exists. /// /// ## Example /// /// ``` /// # use stackline::prelude::*; /// let surface = TextSurface::new(1, 1); /// /// assert_eq!(surface.get(0, 0), Some(TextChar::default())); /// ``` pub fn get(&self, x: usize, y: usize) -> Option { if self.in_bounds(x, y) { Some(self.chars[y * self.width + x]) } else { None } } /// Sets the [`TextChar`] at `(x, y)` to `c`, if `(x, y)` is within the bounds of this `TextSurface`. /// Returns `None` if `(x, y)` is outside of the bounds. /// /// ## Example /// /// ``` /// # use stackline::prelude::*; /// let mut surface = TextSurface::new(2, 2); /// /// surface.set(0, 0, TextChar::from_char('a')).unwrap(); /// assert_eq!(surface.get(0, 0,), Some(TextChar::from_char('a'))); /// ``` pub fn set(&mut self, x: usize, y: usize, c: TextChar) -> Option<()> { if self.in_bounds(x, y) { self.chars[y * self.width + x] = c; Some(()) } else { None } } /// Returns `true` iff `(x, y)` is within the bounds of this `TextSurface`. pub fn in_bounds(&self, x: usize, y: usize) -> bool { x < self.width && y < self.height } pub fn width(&self) -> usize { self.width } pub fn height(&self) -> usize { self.height } pub fn iter(&self) -> impl Iterator + '_ { self.chars.iter().copied() } // TODO: resize } impl std::fmt::Display for TextSurface { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use colored::Colorize; let alternate = f.alternate(); for y in 0..self.height { for x in 0..self.width { let ch = self.chars[y * self.width + x]; if alternate { write!(f, "{}", ch.ch)?; } else { let mut string = String::from(ch.ch).truecolor(ch.fg.red, ch.fg.green, ch.fg.blue); if let Some(bg) = ch.bg { string = string.on_truecolor(bg.red, bg.green, bg.blue); } write!(f, "{}", string)?; } } writeln!(f)?; } Ok(()) } }