scrin 0.1.1

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::style::Style;
use crate::widgets::Widget;

#[derive(Debug, Clone, PartialEq)]
pub enum BorderStyle {
    None,
    Plain,
    Rounded,
    Double,
    Thick,
    Custom {
        top_left: char,
        top_right: char,
        bottom_left: char,
        bottom_right: char,
        top: char,
        bottom: char,
        left: char,
        right: char,
    },
}

#[derive(Debug, Clone)]
pub struct Block<'a> {
    pub title: &'a str,
    pub title_right: Option<&'a str>,
    pub borders: BorderStyle,
    pub border_color: Color,
    pub bg: Option<Color>,
    pub style: Style,
    pub inner_margin: Rect,
}

impl<'a> Block<'a> {
    pub fn new(title: &'a str) -> Self {
        Self {
            title,
            title_right: None,
            borders: BorderStyle::Rounded,
            border_color: Color::rgb(48, 54, 61),
            bg: None,
            style: Style::new(),
            inner_margin: Rect::new(1, 1, 1, 1),
        }
    }

    pub fn with_borders(mut self, borders: BorderStyle) -> Self {
        self.borders = borders;
        self
    }

    pub fn with_border_color(mut self, color: Color) -> Self {
        self.border_color = color;
        self
    }

    pub fn with_bg(mut self, bg: Color) -> Self {
        self.bg = Some(bg);
        self
    }

    pub fn with_title_right(mut self, title: &'a str) -> Self {
        self.title_right = Some(title);
        self
    }

    pub fn with_inner_margin(mut self, margin: Rect) -> Self {
        self.inner_margin = margin;
        self
    }

    pub fn inner(&self, area: Rect) -> Rect {
        match self.borders {
            BorderStyle::None => area,
            _ => Rect::new(
                area.x.saturating_add(1),
                area.y.saturating_add(1),
                area.width.saturating_sub(2),
                area.height.saturating_sub(2),
            )
            .inner(self.inner_margin),
        }
    }

    fn chars_for_border(
        &self,
        style: &BorderStyle,
    ) -> (char, char, char, char, char, char, char, char) {
        match style {
            BorderStyle::None => (' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '),
            BorderStyle::Plain => ('', '', '', '', '', '', '', ''),
            BorderStyle::Rounded => ('', '', '', '', '', '', '', ''),
            BorderStyle::Double => ('', '', '', '', '', '', '', ''),
            BorderStyle::Thick => ('', '', '', '', '', '', '', ''),
            BorderStyle::Custom {
                top_left,
                top_right,
                bottom_left,
                bottom_right,
                top,
                bottom,
                left,
                right,
            } => (
                *top_left,
                *top_right,
                *bottom_left,
                *bottom_right,
                *top,
                *bottom,
                *left,
                *right,
            ),
        }
    }
}

impl<'a> Widget for Block<'a> {
    fn render(&self, buffer: &mut Buffer, area: Rect) {
        if area.width < 2 || area.height < 2 {
            return;
        }

        if let Some(bg) = self.bg {
            buffer.fill(area, ' ', Color::WHITE, Some(bg));
        }

        if self.borders == BorderStyle::None {
            return;
        }

        let (tl, tr, bl, br, top, bot, l, r) = self.chars_for_border(&self.borders);

        for x in (area.x + 1) as usize..(area.right() - 1) as usize {
            buffer.set(
                x,
                area.y as usize,
                crate::core::buffer::Cell {
                    ch: top,
                    fg: self.border_color,
                    bg: self.bg,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
            buffer.set(
                x,
                (area.bottom() - 1) as usize,
                crate::core::buffer::Cell {
                    ch: bot,
                    fg: self.border_color,
                    bg: self.bg,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
        }

        for y in (area.y + 1) as usize..(area.bottom() - 1) as usize {
            buffer.set(
                area.x as usize,
                y,
                crate::core::buffer::Cell {
                    ch: l,
                    fg: self.border_color,
                    bg: self.bg,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
            buffer.set(
                (area.right() - 1) as usize,
                y,
                crate::core::buffer::Cell {
                    ch: r,
                    fg: self.border_color,
                    bg: self.bg,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
        }

        buffer.set(
            area.x as usize,
            area.y as usize,
            crate::core::buffer::Cell {
                ch: tl,
                fg: self.border_color,
                bg: self.bg,
                bold: true,
                italic: false,
                underlined: false,
            },
        );
        buffer.set(
            (area.right() - 1) as usize,
            area.y as usize,
            crate::core::buffer::Cell {
                ch: tr,
                fg: self.border_color,
                bg: self.bg,
                bold: true,
                italic: false,
                underlined: false,
            },
        );
        buffer.set(
            area.x as usize,
            (area.bottom() - 1) as usize,
            crate::core::buffer::Cell {
                ch: bl,
                fg: self.border_color,
                bg: self.bg,
                bold: true,
                italic: false,
                underlined: false,
            },
        );
        buffer.set(
            (area.right() - 1) as usize,
            (area.bottom() - 1) as usize,
            crate::core::buffer::Cell {
                ch: br,
                fg: self.border_color,
                bg: self.bg,
                bold: true,
                italic: false,
                underlined: false,
            },
        );

        if !self.title.is_empty() {
            let title_text = format!(" {} ", self.title);
            let max_title_w = area.width.saturating_sub(4) as usize;
            let display: String = title_text.chars().take(max_title_w).collect();
            let title_x = (area.x + 2) as usize;
            buffer.set_str_bold(
                title_x,
                area.y as usize,
                &display,
                self.border_color.brighten(0.25),
                self.bg,
            );
        }

        if let Some(right_title) = self.title_right {
            let rt = format!(" {} ", right_title);
            let max_title_w = area.width.saturating_sub(4) as usize;
            let display: String = rt.chars().take(max_title_w).collect();
            let rx = (area.right() as usize).saturating_sub(display.len() + 2);
            buffer.set_str_bold(
                rx,
                area.y as usize,
                &display,
                self.border_color.brighten(0.18),
                self.bg,
            );
        }
    }
}