You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
4.0 KiB

use super::*;
use palette::{FromColor, Srgb};
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct TextChar {
pub ch: char,
pub fg: Srgb<u8>,
pub bg: Option<Srgb<u8>>,
}
impl TextChar {
#[inline]
pub fn new<C1, C2>(ch: char, fg: C1, bg: Option<C2>) -> Self
where
Srgb<u8>: FromColor<C1>,
Srgb<u8>: FromColor<C2>,
{
Self {
ch,
fg: Srgb::from_color(fg),
bg: bg.map(|x| Srgb::from_color(x)),
}
}
#[inline]
pub fn from_char(ch: impl Into<char>) -> Self {
Self {
ch: ch.into(),
fg: Srgb::new(255, 255, 255),
bg: None,
}
}
#[inline]
pub fn from_state(ch: impl Into<char>, 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),
},
2 years ago
bg: None,
}
}
}
impl Default for TextChar {
fn default() -> Self {
Self {
ch: ' ',
fg: Srgb::new(255, 255, 255),
2 years ago
bg: None,
}
}
}
#[derive(Clone)]
pub struct TextSurface {
width: usize,
height: usize,
chars: Vec<TextChar>,
}
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<TextChar> {
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<Item=TextChar> + '_ {
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(())
}
}