1use crate::{
4 buffer::Buffer,
5 layout::Rect,
6 style::Style,
7 symbols::{block::FULL, line},
8 widgets::Widget,
9};
10
11#[derive(Debug, Clone)]
21pub struct Set {
22 pub track: &'static str,
23 pub thumb: &'static str,
24 pub begin: &'static str,
25 pub end: &'static str,
26}
27
28pub const DOUBLE_VERTICAL: Set = Set {
29 track: line::DOUBLE_VERTICAL,
30 thumb: FULL,
31 begin: "▲",
32 end: "▼",
33};
34
35pub const DOUBLE_HORIZONTAL: Set = Set {
36 track: line::DOUBLE_HORIZONTAL,
37 thumb: FULL,
38 begin: "◄",
39 end: "►",
40};
41
42pub const VERTICAL: Set = Set {
43 track: line::VERTICAL,
44 thumb: FULL,
45 begin: "↑",
46 end: "↓",
47};
48
49pub const HORIZONTAL: Set = Set {
50 track: line::HORIZONTAL,
51 thumb: FULL,
52 begin: "←",
53 end: "→",
54};
55
56#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
58pub enum ScrollDirection {
59 #[default]
61 Forward,
62 Backward,
64}
65
66#[derive(Default, Debug, Clone, Copy)]
68pub enum ScrollbarOrientation {
69 #[default]
70 VerticalRight,
71 VerticalLeft,
72 HorizontalBottom,
73 HorizontalTop,
74}
75
76#[derive(Debug, Clone)]
90pub struct Scrollbar<'a> {
91 orientation: ScrollbarOrientation,
92 thumb_style: Style,
93 thumb_symbol: &'a str,
94 track_style: Style,
95 track_symbol: &'a str,
96 begin_symbol: Option<&'a str>,
97 begin_style: Style,
98 end_symbol: Option<&'a str>,
99 end_style: Style,
100 position: u16,
102 offset: u16,
104 content_length: u16,
106 viewport_content_length: u16,
108}
109
110impl<'a> Default for Scrollbar<'a> {
111 fn default() -> Self {
112 Self {
113 orientation: ScrollbarOrientation::default(),
114 thumb_symbol: DOUBLE_VERTICAL.thumb,
115 thumb_style: Style::default(),
116 track_symbol: DOUBLE_VERTICAL.track,
117 track_style: Style::default(),
118 begin_symbol: Some(DOUBLE_VERTICAL.begin),
119 begin_style: Style::default(),
120 end_symbol: Some(DOUBLE_VERTICAL.end),
121 end_style: Style::default(),
122 position: Default::default(),
123 offset: Default::default(),
124 content_length: Default::default(),
125 viewport_content_length: Default::default(),
126 }
127 }
128}
129
130impl<'a> Scrollbar<'a> {
131 pub fn new(orientation: ScrollbarOrientation) -> Self {
132 Self::default().orientation(orientation)
133 }
134
135 pub fn orientation(mut self, orientation: ScrollbarOrientation) -> Self {
138 self.orientation = orientation;
139 let set = if self.is_vertical() {
140 DOUBLE_VERTICAL
141 } else {
142 DOUBLE_HORIZONTAL
143 };
144 self.symbols(set)
145 }
146
147 pub fn show_orientation(&self) -> ScrollbarOrientation {
149 self.orientation
150 }
151
152 pub fn orientation_and_symbol(mut self, orientation: ScrollbarOrientation, set: Set) -> Self {
154 self.orientation = orientation;
155 self.symbols(set)
156 }
157
158 pub fn thumb_symbol(mut self, thumb_symbol: &'a str) -> Self {
160 self.thumb_symbol = thumb_symbol;
161 self
162 }
163
164 pub fn thumb_style(mut self, thumb_style: Style) -> Self {
166 self.thumb_style = thumb_style;
167 self
168 }
169
170 pub fn track_symbol(mut self, track_symbol: &'a str) -> Self {
172 self.track_symbol = track_symbol;
173 self
174 }
175
176 pub fn track_style(mut self, track_style: Style) -> Self {
178 self.track_style = track_style;
179 self
180 }
181
182 pub fn begin_symbol(mut self, begin_symbol: Option<&'a str>) -> Self {
184 self.begin_symbol = begin_symbol;
185 self
186 }
187
188 pub fn begin_style(mut self, begin_style: Style) -> Self {
190 self.begin_style = begin_style;
191 self
192 }
193
194 pub fn end_symbol(mut self, end_symbol: Option<&'a str>) -> Self {
196 self.end_symbol = end_symbol;
197 self
198 }
199
200 pub fn end_style(mut self, end_style: Style) -> Self {
202 self.end_style = end_style;
203 self
204 }
205
206 pub fn symbols(mut self, symbol: Set) -> Self {
221 self.track_symbol = symbol.track;
222 self.thumb_symbol = symbol.thumb;
223 if self.begin_symbol.is_some() {
224 self.begin_symbol = Some(symbol.begin);
225 }
226 if self.end_symbol.is_some() {
227 self.end_symbol = Some(symbol.end);
228 }
229 self
230 }
231
232 pub fn style(mut self, style: Style) -> Self {
242 self.track_style = style;
243 self.thumb_style = style;
244 self.begin_style = style;
245 self.end_style = style;
246 self
247 }
248
249 fn is_vertical(&self) -> bool {
250 match self.orientation {
251 ScrollbarOrientation::VerticalRight | ScrollbarOrientation::VerticalLeft => true,
252 ScrollbarOrientation::HorizontalBottom | ScrollbarOrientation::HorizontalTop => false,
253 }
254 }
255
256 fn get_track_area(&self, area: Rect) -> Rect {
257 let area = if self.begin_symbol.is_some() {
259 if self.is_vertical() {
260 Rect::new(
262 area.x,
263 area.y + 1,
264 area.width,
265 area.height.saturating_sub(1),
266 )
267 } else {
268 Rect::new(
270 area.x + 1,
271 area.y,
272 area.width.saturating_sub(1),
273 area.height,
274 )
275 }
276 } else {
277 area
278 };
279 if self.end_symbol.is_some() {
281 if self.is_vertical() {
282 Rect::new(area.x, area.y, area.width, area.height.saturating_sub(1))
284 } else {
285 Rect::new(area.x, area.y, area.width.saturating_sub(1), area.height)
287 }
288 } else {
289 area
290 }
291 }
292
293 fn should_not_render(&self, track_start: u16, track_end: u16, content_length: u16) -> bool {
294 if track_end - track_start == 0 || content_length == 0 {
295 return true;
296 }
297 false
298 }
299
300 fn get_track_start_end(&self, area: Rect) -> (u16, u16, u16) {
301 match self.orientation {
302 ScrollbarOrientation::VerticalRight => {
303 (area.top(), area.bottom(), area.right().saturating_sub(1))
304 }
305 ScrollbarOrientation::VerticalLeft => (area.top(), area.bottom(), area.left()),
306 ScrollbarOrientation::HorizontalBottom => {
307 (area.left(), area.right(), area.bottom().saturating_sub(1))
308 }
309 ScrollbarOrientation::HorizontalTop => (area.left(), area.right(), area.top()),
310 }
311 }
312
313 fn get_thumb_start_end(&self, track_start_end: (u16, u16)) -> (u16, u16) {
328 let (track_start, track_end) = track_start_end;
338
339 let viewport_content_length = if self.viewport_content_length == 0 {
340 track_end - track_start
341 } else {
342 self.viewport_content_length
343 };
344
345 let scroll_position_ratio = if self.offset == 0 {
346 self.position as f64 / self.content_length as f64
347 } else {
348 let gap = f64::from(self.content_length.saturating_sub(viewport_content_length));
349 if gap != 0.0 {
350 f64::from(self.offset) / gap
351 } else {
352 0.0
353 }
354 }
355 .min(1.0);
356
357 let thumb_size = (((viewport_content_length as f64 / self.content_length as f64)
358 * (track_end - track_start) as f64)
359 .round() as u16)
360 .max(1);
361
362 let track_size = (track_end - track_start).saturating_sub(thumb_size);
363
364 let thumb_start = track_start + (scroll_position_ratio * track_size as f64).round() as u16;
365
366 let thumb_end = thumb_start + thumb_size;
367
368 (thumb_start, thumb_end)
369 }
370
371 pub fn offset(&mut self, offset: u16) {
373 self.offset = offset;
374 }
375
376 pub fn position(&mut self, position: u16) {
378 self.position = position;
379 }
380
381 pub fn content_length(&mut self, content_length: u16) {
383 self.content_length = content_length;
384 }
385
386 pub fn viewport_content_length(&mut self, viewport_content_length: u16) {
388 self.viewport_content_length = viewport_content_length;
389 }
390
391 pub fn prev(&mut self) {
393 self.position = self.position.saturating_sub(1);
394 }
395
396 pub fn next(&mut self) {
398 self.position = self
399 .position
400 .saturating_add(1)
401 .clamp(0, self.content_length.saturating_sub(1))
402 }
403
404 pub fn first(&mut self) {
406 self.position = 0;
407 }
408
409 pub fn last(&mut self) {
411 self.position = self.content_length.saturating_sub(1)
412 }
413
414 pub fn scroll(&mut self, direction: ScrollDirection) {
416 match direction {
417 ScrollDirection::Forward => {
418 self.next();
419 }
420 ScrollDirection::Backward => {
421 self.prev();
422 }
423 }
424 }
425}
426
427impl<'a> Widget for Scrollbar<'a> {
428 fn render(&mut self, area: Rect, buf: &mut Buffer) {
429 let area = self.get_track_area(area);
457 let (track_start, track_end, track_axis) = self.get_track_start_end(area);
458
459 if self.should_not_render(track_start, track_end, self.content_length) {
460 return;
461 }
462
463 let (thumb_start, thumb_end) = self.get_thumb_start_end((track_start, track_end));
464
465 for i in track_start..track_end {
466 let (style, symbol) = if i >= thumb_start && i < thumb_end {
467 (self.thumb_style, self.thumb_symbol)
468 } else {
469 (self.track_style, self.track_symbol)
470 };
471
472 if self.is_vertical() {
473 buf.set_string(track_axis, i, symbol, style);
474 } else {
475 buf.set_string(i, track_axis, symbol, style);
476 }
477 }
478
479 if let Some(s) = self.begin_symbol {
480 if self.is_vertical() {
481 buf.set_string(track_axis, track_start - 1, s, self.begin_style);
482 } else {
483 buf.set_string(track_start - 1, track_axis, s, self.begin_style);
484 }
485 };
486 if let Some(s) = self.end_symbol {
487 if self.is_vertical() {
488 buf.set_string(track_axis, track_end, s, self.end_style);
489 } else {
490 buf.set_string(track_end, track_axis, s, self.end_style);
491 }
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use crate::assert_buffer_eq;
500
501 #[test]
502 fn test_no_render_when_area_zero() {
503 let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 0));
504 let mut scrollbar = Scrollbar::default();
505 scrollbar.position(0);
506 scrollbar.content_length(1);
507 scrollbar.render(buffer.area, &mut buffer);
508 assert_buffer_eq!(buffer, Buffer::empty(buffer.area));
509 }
510
511 #[test]
512 fn test_no_render_when_height_zero_with_without_arrows() {
513 let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 0));
514 let mut scrollbar = Scrollbar::default();
515 scrollbar.position(0);
516 scrollbar.content_length(1);
517 scrollbar.render(buffer.area, &mut buffer);
518 assert_buffer_eq!(buffer, Buffer::empty(buffer.area));
519
520 let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 0));
521 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
522 scrollbar.position(0);
523 scrollbar.content_length(1);
524 scrollbar.render(buffer.area, &mut buffer);
525 assert_buffer_eq!(buffer, Buffer::empty(buffer.area));
526 }
527
528 #[test]
529 fn test_no_render_when_height_too_small_for_arrows() {
530 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
531 let mut scrollbar = Scrollbar::default();
532 scrollbar.position(0);
533 scrollbar.content_length(1);
534 scrollbar.render(buffer.area, &mut buffer);
535 assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ", " "]));
536 }
537
538 #[test]
539 fn test_renders_all_thumbs_at_minimum_height_without_arrows() {
540 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
541 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
542 scrollbar.position(0);
543 scrollbar.content_length(1);
544 scrollbar.render(buffer.area, &mut buffer);
545 assert_buffer_eq!(buffer, Buffer::with_lines(vec![" █", " █"]));
546 }
547
548 #[test]
549 fn test_renders_all_thumbs_at_minimum_height_and_minimum_width_without_arrows() {
550 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 2));
551 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
552 scrollbar.position(0);
553 scrollbar.content_length(1);
554 scrollbar.render(buffer.area, &mut buffer);
555 assert_buffer_eq!(buffer, Buffer::with_lines(vec!["█", "█"]));
556 }
557
558 #[test]
559 fn test_renders_two_arrows_one_thumb_at_minimum_height_with_arrows() {
560 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 3));
561 let mut scrollbar = Scrollbar::default();
562 scrollbar.position(0);
563 scrollbar.content_length(1);
564 scrollbar.render(buffer.area, &mut buffer);
565 assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▲", " █", " ▼"]));
566 }
567
568 #[test]
569 fn test_no_render_when_content_length_zero() {
570 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 2));
571 let mut scrollbar = Scrollbar::default();
572 scrollbar.position(0);
573 scrollbar.content_length(0);
574 scrollbar.render(buffer.area, &mut buffer);
575 assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ", " "]));
576 }
577
578 #[test]
579 fn test_renders_all_thumbs_when_height_equals_content_length() {
580 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 2));
581 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
582 scrollbar.position(0);
583 scrollbar.content_length(2);
584 scrollbar.render(buffer.area, &mut buffer);
585 assert_buffer_eq!(buffer, Buffer::with_lines(vec![" █", " █"]));
586
587 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 8));
588 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
589 scrollbar.position(0);
590 scrollbar.content_length(8);
591 scrollbar.render(buffer.area, &mut buffer);
592 assert_buffer_eq!(
593 buffer,
594 Buffer::with_lines(vec![" █", " █", " █", " █", " █", " █", " █", " █"])
595 );
596 }
597
598 #[test]
599 fn test_renders_single_vertical_thumb_when_content_length_square_of_height() {
600 for i in 0..=17 {
601 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 4));
602 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
603 scrollbar.position(i);
604 scrollbar.content_length(16);
605 scrollbar.render(buffer.area, &mut buffer);
606 let expected = if i <= 2 {
607 vec![" █", " ║", " ║", " ║"]
608 } else if i <= 7 {
609 vec![" ║", " █", " ║", " ║"]
610 } else if i <= 13 {
611 vec![" ║", " ║", " █", " ║"]
612 } else {
613 vec![" ║", " ║", " ║", " █"]
614 };
615 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
616 }
617 }
618
619 #[test]
620 fn test_renders_single_horizontal_thumb_when_content_length_square_of_width() {
621 for i in 0..=17 {
622 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
623 let mut scrollbar = Scrollbar::default()
624 .begin_symbol(None)
625 .end_symbol(None)
626 .orientation(ScrollbarOrientation::HorizontalBottom);
627 scrollbar.position(i);
628 scrollbar.content_length(16);
629 scrollbar.render(buffer.area, &mut buffer);
630 let expected = if i <= 2 {
631 vec![" ", "█═══"]
632 } else if i <= 7 {
633 vec![" ", "═█══"]
634 } else if i <= 13 {
635 vec![" ", "══█═"]
636 } else {
637 vec![" ", "═══█"]
638 };
639 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
640 }
641 }
642
643 #[test]
644 fn test_renders_one_thumb_for_large_content_relative_to_height() {
645 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
646 let mut scrollbar = Scrollbar::default()
647 .begin_symbol(None)
648 .end_symbol(None)
649 .orientation(ScrollbarOrientation::HorizontalBottom);
650 scrollbar.position(0);
651 scrollbar.content_length(1600);
652 scrollbar.render(buffer.area, &mut buffer);
653 let expected = vec![" ", "█═══"];
654 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
655
656 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
657 let mut scrollbar = Scrollbar::default()
658 .begin_symbol(None)
659 .end_symbol(None)
660 .orientation(ScrollbarOrientation::HorizontalBottom);
661 scrollbar.position(800);
662 scrollbar.content_length(1600);
663 scrollbar.render(buffer.area, &mut buffer);
664 let expected = vec![" ", "══█═"];
665 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
666 }
667
668 #[test]
669 fn test_renders_two_thumb_default_symbols_for_content_double_height() {
670 for i in 0..=7 {
671 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 4));
672 let mut scrollbar = Scrollbar::default().begin_symbol(None).end_symbol(None);
673 scrollbar.position(i);
674 scrollbar.content_length(8);
675 scrollbar.render(buffer.area, &mut buffer);
676 let expected = if i <= 1 {
677 vec![" █", " █", " ║", " ║"]
678 } else if i <= 5 {
679 vec![" ║", " █", " █", " ║"]
680 } else {
681 vec![" ║", " ║", " █", " █"]
682 };
683 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
684 }
685 }
686
687 #[test]
688 fn test_renders_two_thumb_custom_symbols_for_content_double_height() {
689 for i in 0..=7 {
690 let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 4));
691 let mut scrollbar = Scrollbar::default()
692 .symbols(VERTICAL)
693 .begin_symbol(None)
694 .end_symbol(None);
695 scrollbar.position(i);
696 scrollbar.content_length(8);
697 scrollbar.render(buffer.area, &mut buffer);
698 let expected = if i <= 1 {
699 vec![" █", " █", " │", " │"]
700 } else if i <= 5 {
701 vec![" │", " █", " █", " │"]
702 } else {
703 vec![" │", " │", " █", " █"]
704 };
705 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
706 }
707 }
708
709 #[test]
710 fn test_renders_two_thumb_default_symbols_for_content_double_width() {
711 for i in 0..=7 {
712 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
713 let mut scrollbar = Scrollbar::default()
714 .orientation(ScrollbarOrientation::HorizontalBottom)
715 .begin_symbol(None)
716 .end_symbol(None);
717 scrollbar.position(i);
718 scrollbar.content_length(8);
719 scrollbar.render(buffer.area, &mut buffer);
720 let expected = if i <= 1 {
721 vec![" ", "██══"]
722 } else if i <= 5 {
723 vec![" ", "═██═"]
724 } else {
725 vec![" ", "══██"]
726 };
727 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
728 }
729 }
730
731 #[test]
732 fn test_renders_two_thumb_custom_symbols_for_content_double_width() {
733 for i in 0..=7 {
734 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
735 let mut scrollbar = Scrollbar::default()
736 .orientation(ScrollbarOrientation::HorizontalBottom)
737 .symbols(HORIZONTAL)
738 .begin_symbol(None)
739 .end_symbol(None);
740 scrollbar.position(i);
741 scrollbar.content_length(8);
742 scrollbar.render(buffer.area, &mut buffer);
743 let expected = if i <= 1 {
744 vec![" ", "██──"]
745 } else if i <= 5 {
746 vec![" ", "─██─"]
747 } else {
748 vec![" ", "──██"]
749 };
750 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
751 }
752 }
753
754 #[test]
755 fn test_rendering_viewport_content_length() {
756 for i in 0..=16 {
757 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 2));
758 let mut scrollbar = Scrollbar::default()
759 .orientation(ScrollbarOrientation::HorizontalBottom)
760 .begin_symbol(Some(DOUBLE_HORIZONTAL.begin))
761 .end_symbol(Some(DOUBLE_HORIZONTAL.end));
762 scrollbar.position(i);
763 scrollbar.content_length(16);
764 scrollbar.viewport_content_length(4);
765 scrollbar.render(buffer.area, &mut buffer);
766 let expected = if i <= 1 {
767 vec![" ", "◄██════►"]
768 } else if i <= 5 {
769 vec![" ", "◄═██═══►"]
770 } else if i <= 9 {
771 vec![" ", "◄══██══►"]
772 } else if i <= 13 {
773 vec![" ", "◄═══██═►"]
774 } else {
775 vec![" ", "◄════██►"]
776 };
777 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
778 }
779
780 for i in 0..=16 {
781 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 2));
782 let mut scrollbar = Scrollbar::default()
783 .orientation(ScrollbarOrientation::HorizontalBottom)
784 .begin_symbol(Some(DOUBLE_HORIZONTAL.begin))
785 .end_symbol(Some(DOUBLE_HORIZONTAL.end));
786 scrollbar.position(i);
787 scrollbar.content_length(16);
788 scrollbar.viewport_content_length(1);
789 scrollbar.render(buffer.area, &mut buffer);
790 dbg!(i);
791 let expected = if i <= 1 {
792 vec![" ", "◄█═════►"]
793 } else if i <= 4 {
794 vec![" ", "◄═█════►"]
795 } else if i <= 7 {
796 vec![" ", "◄══█═══►"]
797 } else if i <= 11 {
798 vec![" ", "◄═══█══►"]
799 } else if i <= 14 {
800 vec![" ", "◄════█═►"]
801 } else {
802 vec![" ", "◄═════█►"]
803 };
804 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
805 }
806 }
807
808 #[test]
809 fn test_rendering_begin_end_arrows_horizontal_bottom() {
810 for i in 0..=16 {
811 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 2));
812 let mut scrollbar = Scrollbar::default()
813 .orientation(ScrollbarOrientation::HorizontalBottom)
814 .begin_symbol(Some(DOUBLE_HORIZONTAL.begin))
815 .end_symbol(Some(DOUBLE_HORIZONTAL.end));
816 scrollbar.position(i);
817 scrollbar.content_length(16);
818 scrollbar.render(buffer.area, &mut buffer);
819 let expected = if i <= 1 {
820 vec![" ", "◄██════►"]
821 } else if i <= 5 {
822 vec![" ", "◄═██═══►"]
823 } else if i <= 9 {
824 vec![" ", "◄══██══►"]
825 } else if i <= 13 {
826 vec![" ", "◄═══██═►"]
827 } else {
828 vec![" ", "◄════██►"]
829 };
830 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
831 }
832 }
833
834 #[test]
835 fn test_rendering_begin_end_arrows_horizontal_top() {
836 for i in 0..=16 {
837 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 2));
838 let mut scrollbar = Scrollbar::default()
839 .orientation(ScrollbarOrientation::HorizontalTop)
840 .begin_symbol(Some(DOUBLE_HORIZONTAL.begin))
841 .end_symbol(Some(DOUBLE_HORIZONTAL.end));
842 scrollbar.position(i);
843 scrollbar.content_length(16);
844 scrollbar.render(buffer.area, &mut buffer);
845 let expected = if i <= 1 {
846 vec!["◄██════►", " "]
847 } else if i <= 5 {
848 vec!["◄═██═══►", " "]
849 } else if i <= 9 {
850 vec!["◄══██══►", " "]
851 } else if i <= 13 {
852 vec!["◄═══██═►", " "]
853 } else {
854 vec!["◄════██►", " "]
855 };
856 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
857 }
858 }
859
860 #[test]
861 fn test_rendering_only_begin_arrow_horizontal_bottom() {
862 for i in 0..=16 {
863 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 2));
864 let mut scrollbar = Scrollbar::default()
865 .orientation(ScrollbarOrientation::HorizontalBottom)
866 .begin_symbol(Some(DOUBLE_HORIZONTAL.begin))
867 .end_symbol(None);
868 scrollbar.position(i);
869 scrollbar.content_length(16);
870 scrollbar.render(buffer.area, &mut buffer);
871 let expected = if i <= 1 {
872 vec![" ", "◄███════"]
873 } else if i <= 5 {
874 vec![" ", "◄═███═══"]
875 } else if i <= 9 {
876 vec![" ", "◄══███══"]
877 } else if i <= 13 {
878 vec![" ", "◄═══███═"]
879 } else {
880 vec![" ", "◄════███"]
881 };
882 assert_buffer_eq!(buffer, Buffer::with_lines(expected.clone()));
883 }
884 }
885}