scrin 0.1.80

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::widgets::Widget;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ScrollBarOrientation {
    Vertical,
    Horizontal,
}

#[derive(Debug, Clone)]
pub struct ScrollBar {
    pub position: usize,
    pub total: usize,
    pub viewport: usize,
    pub orientation: ScrollBarOrientation,
    pub track_color: Color,
    pub thumb_color: Color,
    pub arrow_color: Color,
}

impl ScrollBar {
    pub fn new(orientation: ScrollBarOrientation) -> Self {
        Self {
            position: 0,
            total: 100,
            viewport: 10,
            orientation,
            track_color: Color::rgb(48, 54, 61),
            thumb_color: Color::rgb(88, 166, 255),
            arrow_color: Color::rgb(139, 148, 158),
        }
    }

    pub fn with_position(mut self, pos: usize) -> Self {
        self.position = pos;
        self
    }

    pub fn with_total(mut self, total: usize) -> Self {
        self.total = total;
        self
    }

    pub fn with_viewport(mut self, vp: usize) -> Self {
        self.viewport = vp;
        self
    }

    pub fn thumb_position(&self) -> usize {
        if self.total <= self.viewport {
            return 0;
        }
        let track_len = if self.orientation == ScrollBarOrientation::Vertical {
            10
        } else {
            20
        };
        let thumb_len = ((self.viewport as f64 / self.total as f64) * track_len as f64) as usize;
        let max_pos = self.total.saturating_sub(self.viewport);
        if max_pos == 0 {
            0
        } else {
            let pos =
                (self.position as f64 / max_pos as f64 * (track_len - thumb_len) as f64) as usize;
            pos.min(track_len.saturating_sub(thumb_len))
        }
    }
}

impl Widget for ScrollBar {
    fn render(&self, buffer: &mut Buffer, area: Rect) {
        match self.orientation {
            ScrollBarOrientation::Vertical => self.render_vertical(buffer, area),
            ScrollBarOrientation::Horizontal => self.render_horizontal(buffer, area),
        }
    }
}

impl ScrollBar {
    fn render_vertical(&self, buffer: &mut Buffer, area: Rect) {
        let h = area.height as usize;
        let thumb_len = ((self.viewport as f64 / self.total as f64) * h as f64).max(1.0) as usize;
        let thumb_pos = self.thumb_position();

        for i in 0..h {
            let x = area.x as usize;
            let y = area.y as usize + i;
            let is_thumb = i >= thumb_pos && i < thumb_pos + thumb_len;
            let ch = if is_thumb { '' } else { '' };
            let fg = if is_thumb {
                self.thumb_color
            } else {
                self.track_color
            };
            buffer.set(
                x,
                y,
                crate::core::buffer::Cell {
                    ch,
                    fg,
                    bg: None,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
        }
    }

    fn render_horizontal(&self, buffer: &mut Buffer, area: Rect) {
        let w = area.width as usize;
        let thumb_len = ((self.viewport as f64 / self.total as f64) * w as f64).max(1.0) as usize;
        let thumb_pos = self.thumb_position();

        for i in 0..w {
            let x = area.x as usize + i;
            let y = area.y as usize;
            let is_thumb = i >= thumb_pos && i < thumb_pos + thumb_len;
            let ch = if is_thumb { '' } else { '' };
            let fg = if is_thumb {
                self.thumb_color
            } else {
                self.track_color
            };
            buffer.set(
                x,
                y,
                crate::core::buffer::Cell {
                    ch,
                    fg,
                    bg: None,
                    bold: false,
                    italic: false,
                    underlined: false,
                },
            );
        }
    }
}