use itertools::Itertools;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use ratatui_core::style::Color;
use ratatui_core::widgets::Widget;
const RATATUI_MASCOT: &str = indoc::indoc! {"
hhh
hhhhhh
hhhhhhh
hhhhhhhh
hhhhhhhhh
hhhhhhhhhh
hhhhhhhhhhhh
hhhhhhhhhhhhh
hhhhhhhhhhhhh ██████
hhhhhhhhhhh ████████
hhhhh ███████████
hhh ██ee████████
h █████████████
████ █████████████
█████████████████
████████████████
████████████████
███ ██████████
▒▒ █████████
▒░░▒ █████████
▒░░░░▒ ██████████
▒░░▓░░░▒ █████████
▒░░▓▓░░░░▒ ████████
▒░░░░░░░░░░▒ ██████████
▒░░░░░░░░░░░░▒ ██████████
▒░░░░░░░▓▓░░░░░▒ █████████
▒░░░░░░░░░▓▓░░░░░▒ ████ ███
▒░░░░░░░░░░░░░░░░░░▒ ██ ███
▒░░░░░░░░░░░░░░░░░░░░▒ █ ███
▒░░░░░░░░░░░░░░░░░░░░░▒ ███
▒░░░░░░░░░░░░░░░░░░░░░▒ ███
▒░░░░░░░░░░░░░░░░░░░░░▒ █"
};
const EMPTY: char = ' ';
const RAT: char = '█';
const HAT: char = 'h';
const EYE: char = 'e';
const TERM: char = '░';
const TERM_BORDER: char = '▒';
const TERM_CURSOR: char = '▓';
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MascotEyeColor {
#[default]
Default,
Red,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RatatuiMascot {
eye_state: MascotEyeColor,
rat_color: Color,
rat_eye_color: Color,
rat_eye_blink: Color,
hat_color: Color,
term_color: Color,
term_border_color: Color,
term_cursor_color: Color,
}
impl Default for RatatuiMascot {
fn default() -> Self {
Self {
rat_color: Color::Indexed(252), hat_color: Color::Indexed(231), rat_eye_color: Color::Indexed(236), rat_eye_blink: Color::Indexed(196), term_color: Color::Indexed(232), term_border_color: Color::Indexed(237), term_cursor_color: Color::Indexed(248), eye_state: MascotEyeColor::Default,
}
}
}
impl RatatuiMascot {
pub fn new() -> Self {
Self {
..Default::default()
}
}
#[must_use]
pub const fn set_eye(self, rat_eye: MascotEyeColor) -> Self {
Self {
eye_state: rat_eye,
..self
}
}
const fn color_for(&self, c: char) -> Option<Color> {
match c {
RAT => Some(self.rat_color),
HAT => Some(self.hat_color),
EYE => Some(match self.eye_state {
MascotEyeColor::Default => self.rat_eye_color,
MascotEyeColor::Red => self.rat_eye_blink,
}),
TERM => Some(self.term_color),
TERM_CURSOR => Some(self.term_cursor_color),
TERM_BORDER => Some(self.term_border_color),
_ => None,
}
}
}
impl Widget for RatatuiMascot {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;
}
for (y, (line1, line2)) in RATATUI_MASCOT.lines().tuples().enumerate() {
for (x, (ch1, ch2)) in line1.chars().zip(line2.chars()).enumerate() {
let x = area.left() + x as u16;
let y = area.top() + y as u16;
if x >= area.right() || y >= area.bottom() {
continue;
}
let cell = &mut buf[(x, y)];
let (fg, bg) = match (ch1, ch2) {
(EMPTY, EMPTY) => (None, None),
(c, EMPTY) | (EMPTY, c) => (self.color_for(c), None),
(TERM, TERM_BORDER) => (self.color_for(TERM_BORDER), self.color_for(TERM)),
(TERM, c) | (c, TERM) => (self.color_for(c), self.color_for(TERM)),
(c1, c2) => (self.color_for(c1), self.color_for(c2)),
};
let symbol = match (ch1, ch2) {
(EMPTY, EMPTY) => None,
(TERM, TERM) => Some(EMPTY),
(_, EMPTY | TERM) => Some('▀'),
(EMPTY | TERM, _) => Some('▄'),
(c, d) if c == d => Some('█'),
(_, _) => Some('▀'),
};
if let Some(fg) = fg {
cell.fg = fg;
}
if let Some(bg) = bg {
cell.bg = bg;
}
if let Some(symb) = symbol {
cell.set_char(symb);
}
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use super::*;
#[test]
fn new_mascot() {
let mascot = RatatuiMascot::new();
assert_eq!(mascot.eye_state, MascotEyeColor::Default);
}
#[test]
fn set_eye_color() {
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 16));
let mascot = RatatuiMascot::new().set_eye(MascotEyeColor::Red);
mascot.render(buf.area, &mut buf);
assert_eq!(mascot.eye_state, MascotEyeColor::Red);
assert_eq!(buf[(21, 5)].bg, Color::Indexed(196));
}
#[test]
fn render_mascot() {
let mascot = RatatuiMascot::new();
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 16));
mascot.render(buf.area, &mut buf);
assert_eq!(buf.area.as_size(), (32, 16).into());
assert_eq!(buf[(21, 5)].bg, Color::Indexed(236));
assert_eq!(
buf.content
.iter()
.map(ratatui_core::buffer::Cell::symbol)
.collect::<String>(),
Buffer::with_lines([
" ▄▄███ ",
" ▄███████ ",
" ▄█████████ ",
" ████████████ ",
" ▀███████████▀ ▄▄██████",
" ▀███▀▄█▀▀████████ ",
" ▄▄▄▄▀▄████████████ ",
" ████████████████ ",
" ▀███▀██████████ ",
" ▄▀▀▄ █████████ ",
" ▄▀ ▄ ▀▄▀█████████ ",
" ▄▀ ▀▀ ▀▄▀███████ ",
" ▄▀ ▄▄ ▀▄▀█████████ ",
" ▄▀ ▀▀ ▀▄▀██▀ ███ ",
"█ ▀▄▀ ▄██ ",
" ▀▄ ▀▄▀█ ",
])
.content
.iter()
.map(ratatui_core::buffer::Cell::symbol)
.collect::<String>()
);
}
#[test]
fn render_in_minimal_buffer() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
let mascot = RatatuiMascot::new();
mascot.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" "]));
}
#[test]
fn render_in_zero_size_buffer() {
let mut buffer = Buffer::empty(Rect::ZERO);
let mascot = RatatuiMascot::new();
mascot.render(buffer.area, &mut buffer);
}
}