Skip to main content

scrin/widgets/
scrollbar.rs

1use crate::core::buffer::Buffer;
2use crate::core::color::Color;
3use crate::core::rect::Rect;
4use crate::widgets::Widget;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum ScrollBarOrientation {
8    Vertical,
9    Horizontal,
10}
11
12#[derive(Debug, Clone)]
13pub struct ScrollBar {
14    pub position: usize,
15    pub total: usize,
16    pub viewport: usize,
17    pub orientation: ScrollBarOrientation,
18    pub track_color: Color,
19    pub thumb_color: Color,
20    pub arrow_color: Color,
21}
22
23impl ScrollBar {
24    pub fn new(orientation: ScrollBarOrientation) -> Self {
25        Self {
26            position: 0,
27            total: 100,
28            viewport: 10,
29            orientation,
30            track_color: Color::rgb(48, 54, 61),
31            thumb_color: Color::rgb(88, 166, 255),
32            arrow_color: Color::rgb(139, 148, 158),
33        }
34    }
35
36    pub fn with_position(mut self, pos: usize) -> Self {
37        self.position = pos;
38        self
39    }
40
41    pub fn with_total(mut self, total: usize) -> Self {
42        self.total = total;
43        self
44    }
45
46    pub fn with_viewport(mut self, vp: usize) -> Self {
47        self.viewport = vp;
48        self
49    }
50
51    pub fn thumb_position(&self) -> usize {
52        if self.total <= self.viewport {
53            return 0;
54        }
55        let track_len = if self.orientation == ScrollBarOrientation::Vertical {
56            10
57        } else {
58            20
59        };
60        let thumb_len = ((self.viewport as f64 / self.total as f64) * track_len as f64) as usize;
61        let max_pos = self.total.saturating_sub(self.viewport);
62        if max_pos == 0 {
63            0
64        } else {
65            let pos =
66                (self.position as f64 / max_pos as f64 * (track_len - thumb_len) as f64) as usize;
67            pos.min(track_len.saturating_sub(thumb_len))
68        }
69    }
70}
71
72impl Widget for ScrollBar {
73    fn render(&self, buffer: &mut Buffer, area: Rect) {
74        match self.orientation {
75            ScrollBarOrientation::Vertical => self.render_vertical(buffer, area),
76            ScrollBarOrientation::Horizontal => self.render_horizontal(buffer, area),
77        }
78    }
79}
80
81impl ScrollBar {
82    fn render_vertical(&self, buffer: &mut Buffer, area: Rect) {
83        let h = area.height as usize;
84        let thumb_len = ((self.viewport as f64 / self.total as f64) * h as f64).max(1.0) as usize;
85        let thumb_pos = self.thumb_position();
86
87        for i in 0..h {
88            let x = area.x as usize;
89            let y = area.y as usize + i;
90            let is_thumb = i >= thumb_pos && i < thumb_pos + thumb_len;
91            let ch = if is_thumb { '█' } else { '░' };
92            let fg = if is_thumb {
93                self.thumb_color
94            } else {
95                self.track_color
96            };
97            buffer.set(
98                x,
99                y,
100                crate::core::buffer::Cell {
101                    ch,
102                    fg,
103                    bg: None,
104                    bold: false,
105                    italic: false,
106                    underlined: false,
107                },
108            );
109        }
110    }
111
112    fn render_horizontal(&self, buffer: &mut Buffer, area: Rect) {
113        let w = area.width as usize;
114        let thumb_len = ((self.viewport as f64 / self.total as f64) * w as f64).max(1.0) as usize;
115        let thumb_pos = self.thumb_position();
116
117        for i in 0..w {
118            let x = area.x as usize + i;
119            let y = area.y as usize;
120            let is_thumb = i >= thumb_pos && i < thumb_pos + thumb_len;
121            let ch = if is_thumb { '█' } else { '░' };
122            let fg = if is_thumb {
123                self.thumb_color
124            } else {
125                self.track_color
126            };
127            buffer.set(
128                x,
129                y,
130                crate::core::buffer::Cell {
131                    ch,
132                    fg,
133                    bg: None,
134                    bold: false,
135                    italic: false,
136                    underlined: false,
137                },
138            );
139        }
140    }
141}