use crate::render::Cell;
use crate::style::Color;
use crate::widget::theme::SEPARATOR_COLOR;
use crate::widget::traits::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SkeletonShape {
#[default]
Rectangle,
Circle,
Paragraph,
}
pub struct Skeleton {
width: u16,
height: u16,
shape: SkeletonShape,
lines: u16,
frame: u8,
animate: bool,
color: Color,
wave_char: char,
props: WidgetProps,
}
impl Skeleton {
pub fn new() -> Self {
Self {
width: 0,
height: 1,
shape: SkeletonShape::Rectangle,
lines: 3,
frame: 0,
animate: true,
color: SEPARATOR_COLOR,
wave_char: '░',
props: WidgetProps::new(),
}
}
pub fn width(mut self, width: u16) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: u16) -> Self {
self.height = height;
self
}
pub fn shape(mut self, shape: SkeletonShape) -> Self {
self.shape = shape;
self
}
pub fn rectangle(mut self) -> Self {
self.shape = SkeletonShape::Rectangle;
self
}
pub fn circle(mut self) -> Self {
self.shape = SkeletonShape::Circle;
self
}
pub fn paragraph(mut self) -> Self {
self.shape = SkeletonShape::Paragraph;
self
}
pub fn lines(mut self, lines: u16) -> Self {
self.lines = lines;
self
}
pub fn no_animate(mut self) -> Self {
self.animate = false;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn frame(mut self, frame: u8) -> Self {
self.frame = frame;
self
}
fn skeleton_char(&self) -> char {
if self.animate {
match self.frame % 4 {
0 => '░',
1 => '▒',
2 => '▓',
_ => '▒',
}
} else {
self.wave_char
}
}
}
impl Default for Skeleton {
fn default() -> Self {
Self::new()
}
}
impl View for Skeleton {
crate::impl_view_meta!("Skeleton");
fn render(&self, ctx: &mut RenderContext) {
let area = ctx.area;
let ch = self.skeleton_char();
match self.shape {
SkeletonShape::Rectangle => {
let width = if self.width > 0 {
self.width.min(area.width)
} else {
area.width
};
let height = self.height.min(area.height);
for y in 0..height {
for x in 0..width {
let mut cell = Cell::new(ch);
cell.fg = Some(self.color);
ctx.set(x, y, cell);
}
}
}
SkeletonShape::Circle => {
let size = self.height.max(1).min(area.height);
if size == 1 {
let mut cell = Cell::new('●');
cell.fg = Some(self.color);
ctx.set(0, 0, cell);
} else if size == 2 {
let chars = ['╭', '╮', '╰', '╯'];
for (i, c) in chars.iter().enumerate() {
let x = (i % 2) as u16;
let y = (i / 2) as u16;
let mut cell = Cell::new(*c);
cell.fg = Some(self.color);
ctx.set(x, y, cell);
}
} else {
let mut tl = Cell::new('╭');
tl.fg = Some(self.color);
ctx.set(0, 0, tl);
for x in 1..size - 1 {
let mut cell = Cell::new('─');
cell.fg = Some(self.color);
ctx.set(x, 0, cell);
}
let mut tr = Cell::new('╮');
tr.fg = Some(self.color);
ctx.set(size - 1, 0, tr);
for y in 1..size - 1 {
let mut left = Cell::new('│');
left.fg = Some(self.color);
ctx.set(0, y, left);
for x in 1..size - 1 {
let mut cell = Cell::new(ch);
cell.fg = Some(self.color);
ctx.set(x, y, cell);
}
let mut right = Cell::new('│');
right.fg = Some(self.color);
ctx.set(size - 1, y, right);
}
let mut bl = Cell::new('╰');
bl.fg = Some(self.color);
ctx.set(0, size - 1, bl);
for x in 1..size - 1 {
let mut cell = Cell::new('─');
cell.fg = Some(self.color);
ctx.set(x, size - 1, cell);
}
let mut br = Cell::new('╯');
br.fg = Some(self.color);
ctx.set(size - 1, size - 1, br);
}
}
SkeletonShape::Paragraph => {
let width = if self.width > 0 {
self.width.min(area.width)
} else {
area.width
};
let lines = self.lines.min(area.height);
for line in 0..lines {
let line_width = if line == lines - 1 {
width * 2 / 3 } else if line % 2 == 1 {
width.saturating_sub(4) } else {
width
};
for x in 0..line_width {
let mut cell = Cell::new(ch);
cell.fg = Some(self.color);
ctx.set(x, line, cell);
}
}
}
}
}
}
impl_styled_view!(Skeleton);
impl_props_builders!(Skeleton);
pub fn skeleton() -> Skeleton {
Skeleton::new()
}
pub fn skeleton_text() -> Skeleton {
Skeleton::new().height(1)
}
pub fn skeleton_avatar() -> Skeleton {
Skeleton::new().circle().height(3)
}
pub fn skeleton_paragraph() -> Skeleton {
Skeleton::new().paragraph().lines(3)
}