1use ratatui_core::buffer::Buffer;
7use ratatui_core::layout::Rect;
8use ratatui_core::style::Style;
9use ratatui_core::widgets::Widget;
10
11use super::{ArrowLayout, ScrollBar, ScrollBarOrientation};
12use crate::metrics::{CellFill, ScrollMetrics};
13use crate::ScrollLengths;
14
15impl Widget for &ScrollBar {
16 fn render(self, area: Rect, buf: &mut Buffer) {
17 self.render_inner(area, buf);
18 }
19}
20
21impl ScrollBar {
22 fn render_inner(&self, area: Rect, buf: &mut Buffer) {
24 if area.width == 0 || area.height == 0 {
25 return;
26 }
27
28 let layout = self.arrow_layout(area);
29 self.render_arrows(&layout, buf);
30 if layout.track_area.width == 0 || layout.track_area.height == 0 {
31 return;
32 }
33
34 match self.orientation {
35 ScrollBarOrientation::Vertical => {
36 self.render_vertical_track(layout.track_area, buf);
37 }
38 ScrollBarOrientation::Horizontal => {
39 self.render_horizontal_track(layout.track_area, buf);
40 }
41 }
42 }
43
44 fn render_arrows(&self, layout: &ArrowLayout, buf: &mut Buffer) {
46 let arrow_style = self.arrow_style.unwrap_or(self.track_style);
47 if let Some((x, y)) = layout.start {
48 let glyph = match self.orientation {
49 ScrollBarOrientation::Vertical => self.glyph_set.arrow_vertical_start,
50 ScrollBarOrientation::Horizontal => self.glyph_set.arrow_horizontal_start,
51 };
52 let cell = &mut buf[(x, y)];
53 cell.set_char(glyph);
54 cell.set_style(arrow_style);
55 }
56 if let Some((x, y)) = layout.end {
57 let glyph = match self.orientation {
58 ScrollBarOrientation::Vertical => self.glyph_set.arrow_vertical_end,
59 ScrollBarOrientation::Horizontal => self.glyph_set.arrow_horizontal_end,
60 };
61 let cell = &mut buf[(x, y)];
62 cell.set_char(glyph);
63 cell.set_style(arrow_style);
64 }
65 }
66
67 fn render_vertical_track(&self, area: Rect, buf: &mut Buffer) {
69 let metrics = ScrollMetrics::new(
70 ScrollLengths {
71 content_len: self.content_len,
72 viewport_len: self.viewport_len,
73 },
74 self.offset,
75 area.height,
76 );
77 let x = area.x;
78 for (idx, y) in (area.y..area.y.saturating_add(area.height)).enumerate() {
79 let (glyph, style) = self.glyph_for_vertical(metrics.cell_fill(idx));
80 let cell = &mut buf[(x, y)];
81 cell.set_char(glyph);
82 cell.set_style(style);
83 }
84 }
85
86 fn render_horizontal_track(&self, area: Rect, buf: &mut Buffer) {
88 let metrics = ScrollMetrics::new(
89 ScrollLengths {
90 content_len: self.content_len,
91 viewport_len: self.viewport_len,
92 },
93 self.offset,
94 area.width,
95 );
96 let y = area.y;
97 for (idx, x) in (area.x..area.x.saturating_add(area.width)).enumerate() {
98 let (glyph, style) = self.glyph_for_horizontal(metrics.cell_fill(idx));
99 let cell = &mut buf[(x, y)];
100 cell.set_char(glyph);
101 cell.set_style(style);
102 }
103 }
104
105 fn glyph_for_vertical(&self, fill: CellFill) -> (char, Style) {
107 match fill {
108 CellFill::Empty => (self.glyph_set.track_vertical, self.track_style),
109 CellFill::Full => (self.glyph_set.thumb_vertical_lower[7], self.thumb_style),
110 CellFill::Partial { start, len } => {
111 let index = len.saturating_sub(1) as usize;
112 let glyph = if start == 0 {
113 self.glyph_set.thumb_vertical_upper[index]
114 } else {
115 self.glyph_set.thumb_vertical_lower[index]
116 };
117 (glyph, self.thumb_style)
118 }
119 }
120 }
121
122 fn glyph_for_horizontal(&self, fill: CellFill) -> (char, Style) {
124 match fill {
125 CellFill::Empty => (self.glyph_set.track_horizontal, self.track_style),
126 CellFill::Full => (self.glyph_set.thumb_horizontal_left[7], self.thumb_style),
127 CellFill::Partial { start, len } => {
128 let index = len.saturating_sub(1) as usize;
129 let glyph = if start == 0 {
130 self.glyph_set.thumb_horizontal_left[index]
131 } else {
132 self.glyph_set.thumb_horizontal_right[index]
133 };
134 (glyph, self.thumb_style)
135 }
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use ratatui_core::buffer::Buffer;
143 use ratatui_core::layout::Rect;
144
145 use super::*;
146 use crate::{ScrollBarArrows, ScrollLengths};
147
148 #[test]
149 fn render_vertical_fractional_thumb() {
150 let scrollbar = ScrollBar::vertical(ScrollLengths {
151 content_len: 10,
152 viewport_len: 3,
153 })
154 .arrows(ScrollBarArrows::None)
155 .offset(1);
156 let mut buf = Buffer::empty(Rect::new(0, 0, 1, 4));
157 (&scrollbar).render(buf.area, &mut buf);
158 let mut expected = Buffer::with_lines(vec!["▅", "▀", "│", "│"]);
159 expected.set_style(expected.area, scrollbar.track_style);
160 expected[(0, 0)].set_style(scrollbar.thumb_style);
161 expected[(0, 1)].set_style(scrollbar.thumb_style);
162 assert_eq!(buf, expected);
163 }
164
165 #[test]
166 fn render_horizontal_fractional_thumb() {
167 let scrollbar = ScrollBar::horizontal(ScrollLengths {
168 content_len: 10,
169 viewport_len: 3,
170 })
171 .arrows(ScrollBarArrows::None)
172 .offset(1);
173 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
174 (&scrollbar).render(buf.area, &mut buf);
175 let mut expected = Buffer::with_lines(vec!["🮉▌──"]);
176 expected.set_style(expected.area, scrollbar.track_style);
177 expected[(0, 0)].set_style(scrollbar.thumb_style);
178 expected[(1, 0)].set_style(scrollbar.thumb_style);
179 assert_eq!(buf, expected);
180 }
181
182 #[test]
183 fn render_full_thumb_when_no_scroll() {
184 let scrollbar = ScrollBar::vertical(ScrollLengths {
185 content_len: 5,
186 viewport_len: 10,
187 })
188 .arrows(ScrollBarArrows::None);
189 let mut buf = Buffer::empty(Rect::new(0, 0, 1, 3));
190 (&scrollbar).render(buf.area, &mut buf);
191 let mut expected = Buffer::with_lines(vec!["█", "█", "█"]);
192 expected.set_style(expected.area, scrollbar.thumb_style);
193 assert_eq!(buf, expected);
194 }
195
196 #[test]
197 fn render_vertical_arrows() {
198 let scrollbar = ScrollBar::vertical(ScrollLengths {
199 content_len: 5,
200 viewport_len: 2,
201 });
202 let mut buf = Buffer::empty(Rect::new(0, 0, 1, 3));
203 (&scrollbar).render(buf.area, &mut buf);
204 let mut expected = Buffer::with_lines(vec!["▲", "█", "▼"]);
205 expected[(0, 0)].set_style(scrollbar.arrow_style.unwrap_or(scrollbar.track_style));
206 expected[(0, 1)].set_style(scrollbar.thumb_style);
207 expected[(0, 2)].set_style(scrollbar.arrow_style.unwrap_or(scrollbar.track_style));
208 assert_eq!(buf, expected);
209 }
210
211 #[test]
212 fn render_horizontal_arrows() {
213 let scrollbar = ScrollBar::horizontal(ScrollLengths {
214 content_len: 5,
215 viewport_len: 2,
216 });
217 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
218 (&scrollbar).render(buf.area, &mut buf);
219 let mut expected = Buffer::with_lines(vec!["◀█▶"]);
220 expected[(0, 0)].set_style(scrollbar.arrow_style.unwrap_or(scrollbar.track_style));
221 expected[(1, 0)].set_style(scrollbar.thumb_style);
222 expected[(2, 0)].set_style(scrollbar.arrow_style.unwrap_or(scrollbar.track_style));
223 assert_eq!(buf, expected);
224 }
225}