scrin/widgets/
scrollbar.rs1use 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}