1use ratatui_core::buffer::Buffer;
2use ratatui_core::layout::{Rect, Size};
3use ratatui_core::widgets::{StatefulWidget, Widget};
4use ratatui_widgets::scrollbar::{Scrollbar, ScrollbarOrientation, ScrollbarState};
5
6use crate::ScrollViewState;
7
8#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
51pub struct ScrollView {
52 buf: Buffer,
53 size: Size,
54 vertical_scrollbar_visibility: ScrollbarVisibility,
55 horizontal_scrollbar_visibility: ScrollbarVisibility,
56}
57
58#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
60pub enum ScrollbarVisibility {
61 #[default]
63 Automatic,
64 Always,
66 Never,
68}
69
70impl ScrollView {
71 pub fn new(size: Size) -> Self {
75 let area = Rect::new(0, 0, size.width, size.height);
77 Self {
78 buf: Buffer::empty(area),
79 size,
80 horizontal_scrollbar_visibility: ScrollbarVisibility::default(),
81 vertical_scrollbar_visibility: ScrollbarVisibility::default(),
82 }
83 }
84
85 pub const fn size(&self) -> Size {
87 self.size
88 }
89
90 pub const fn area(&self) -> Rect {
92 self.buf.area
93 }
94
95 pub const fn buf(&self) -> &Buffer {
97 &self.buf
98 }
99
100 pub const fn buf_mut(&mut self) -> &mut Buffer {
114 &mut self.buf
115 }
116
117 pub const fn vertical_scrollbar_visibility(mut self, visibility: ScrollbarVisibility) -> Self {
133 self.vertical_scrollbar_visibility = visibility;
134 self
135 }
136
137 pub const fn horizontal_scrollbar_visibility(
153 mut self,
154 visibility: ScrollbarVisibility,
155 ) -> Self {
156 self.horizontal_scrollbar_visibility = visibility;
157 self
158 }
159
160 pub const fn scrollbars_visibility(mut self, visibility: ScrollbarVisibility) -> Self {
176 self.vertical_scrollbar_visibility = visibility;
177 self.horizontal_scrollbar_visibility = visibility;
178 self
179 }
180
181 pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
190 widget.render(area, &mut self.buf);
191 }
192
193 pub fn render_stateful_widget<W: StatefulWidget>(
202 &mut self,
203 widget: W,
204 area: Rect,
205 state: &mut W::State,
206 ) {
207 widget.render(area, &mut self.buf, state);
208 }
209}
210
211impl StatefulWidget for ScrollView {
212 type State = ScrollViewState;
213
214 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
215 let (mut x, mut y) = state.offset.into();
216 let horizontal_space = area.width as i32 - self.size.width as i32;
217 let vertical_space = area.height as i32 - self.size.height as i32;
218 let (show_horizontal, show_vertical) =
219 self.visible_scrollbars(horizontal_space, vertical_space);
220
221 let viewport_width = area.width.saturating_sub(show_vertical as u16);
224 let viewport_height = area.height.saturating_sub(show_horizontal as u16);
225
226 if horizontal_space > 0 {
228 x = 0;
229 }
230 if vertical_space > 0 {
231 y = 0;
232 }
233
234 let max_x_offset = self.buf.area.width.saturating_sub(viewport_width);
238 let max_y_offset = self.buf.area.height.saturating_sub(viewport_height);
239
240 x = x.min(max_x_offset);
241 y = y.min(max_y_offset);
242 state.offset = (x, y).into();
243 state.size = Some(self.size);
244 let viewport_area = self.render_scrollbars(area, buf, state);
245 state.page_size = Some(viewport_area.as_size());
246 let visible_area = viewport_area.intersection(self.buf.area);
247 self.render_visible_area(area, buf, visible_area);
248 }
249}
250
251impl ScrollView {
252 fn render_scrollbars(&self, area: Rect, buf: &mut Buffer, state: &mut ScrollViewState) -> Rect {
255 let horizontal_space = area.width as i32 - self.size.width as i32;
260 let vertical_space = area.height as i32 - self.size.height as i32;
261
262 if horizontal_space > 0 {
264 state.offset.x = 0;
265 }
266 if vertical_space > 0 {
267 state.offset.y = 0;
268 }
269
270 let (show_horizontal, show_vertical) =
271 self.visible_scrollbars(horizontal_space, vertical_space);
272
273 let new_height = if show_horizontal {
274 let width = area.width.saturating_sub(show_vertical as u16);
276 let render_area = Rect { width, ..area };
277 self.render_horizontal_scrollbar(render_area, buf, state);
279 area.height.saturating_sub(1)
280 } else {
281 area.height
282 };
283
284 let new_width = if show_vertical {
285 let height = area.height.saturating_sub(show_horizontal as u16);
287 let render_area = Rect { height, ..area };
288 self.render_vertical_scrollbar(render_area, buf, state);
290 area.width.saturating_sub(1)
291 } else {
292 area.width
293 };
294
295 Rect::new(state.offset.x, state.offset.y, new_width, new_height)
296 }
297
298 const fn visible_scrollbars(&self, horizontal_space: i32, vertical_space: i32) -> (bool, bool) {
307 type V = crate::scroll_view::ScrollbarVisibility;
308
309 match (
310 self.horizontal_scrollbar_visibility,
311 self.vertical_scrollbar_visibility,
312 ) {
313 (V::Always, V::Always) => (true, true),
315 (V::Never, V::Never) => (false, false),
316 (V::Always, V::Never) => (true, false),
317 (V::Never, V::Always) => (false, true),
318
319 (V::Automatic, V::Never) => (horizontal_space < 0, false),
321 (V::Never, V::Automatic) => (false, vertical_space < 0),
322
323 (V::Always, V::Automatic) => (true, vertical_space <= 0),
327 (V::Automatic, V::Always) => (horizontal_space <= 0, true),
328
329 (V::Automatic, V::Automatic) => {
331 if horizontal_space >= 0 && vertical_space >= 0 {
332 (false, false)
334 } else if horizontal_space < 0 && vertical_space < 0 {
335 (true, true)
337 } else if horizontal_space > 0 && vertical_space < 0 {
338 (false, true)
340 } else if horizontal_space < 0 && vertical_space > 0 {
341 (true, false)
343 } else {
344 (true, true)
347 }
348 }
349 }
350 }
351
352 fn render_vertical_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
353 let scrollbar_height = self.size.height.saturating_sub(area.height);
354 let mut scrollbar_state =
355 ScrollbarState::new(scrollbar_height as usize).position(state.offset.y as usize);
356 let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
357 scrollbar.render(area, buf, &mut scrollbar_state);
358 }
359
360 fn render_horizontal_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
361 let scrollbar_width = self.size.width.saturating_sub(area.width);
362 let mut scrollbar_state =
363 ScrollbarState::new(scrollbar_width as usize).position(state.offset.x as usize);
364 let scrollbar = Scrollbar::new(ScrollbarOrientation::HorizontalBottom);
365 scrollbar.render(area, buf, &mut scrollbar_state);
366 }
367
368 fn render_visible_area(&self, area: Rect, buf: &mut Buffer, visible_area: Rect) {
369 for (src_row, dst_row) in visible_area.rows().zip(area.rows()) {
371 for (src_col, dst_col) in src_row.columns().zip(dst_row.columns()) {
372 buf[dst_col] = self.buf[src_col].clone();
373 }
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use ratatui_core::text::Span;
381 use rstest::{fixture, rstest};
382
383 use super::*;
384
385 #[fixture]
402 fn scroll_view() -> ScrollView {
403 let mut scroll_view = ScrollView::new(Size::new(10, 10));
404 for y in 0..10 {
405 for x in 0..10 {
406 let c = char::from_u32((x + y * 10) % 26 + 65).unwrap();
407 let widget = Span::raw(format!("{c}"));
408 let area = Rect::new(x as u16, y as u16, 1, 1);
409 scroll_view.render_widget(widget, area);
410 }
411 }
412 scroll_view
413 }
414
415 #[rstest]
416 fn zero_offset(scroll_view: ScrollView) {
417 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
418 let mut state = ScrollViewState::default();
419 scroll_view.render(buf.area, &mut buf, &mut state);
420 assert_eq!(
421 buf,
422 Buffer::with_lines(vec![
423 "ABCDE▲",
424 "KLMNO█",
425 "UVWXY█",
426 "EFGHI║",
427 "OPQRS▼",
428 "◄██═► ",
429 ])
430 )
431 }
432
433 #[rstest]
434 fn move_right(scroll_view: ScrollView) {
435 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
436 let mut state = ScrollViewState::with_offset((3, 0).into());
437 scroll_view.render(buf.area, &mut buf, &mut state);
438 assert_eq!(
439 buf,
440 Buffer::with_lines(vec![
441 "DEFGH▲",
442 "NOPQR█",
443 "XYZAB█",
444 "HIJKL║",
445 "RSTUV▼",
446 "◄═██► ",
447 ])
448 )
449 }
450
451 #[rstest]
452 fn move_down(scroll_view: ScrollView) {
453 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
454 let mut state = ScrollViewState::with_offset((0, 3).into());
455 scroll_view.render(buf.area, &mut buf, &mut state);
456 assert_eq!(
457 buf,
458 Buffer::with_lines(vec![
459 "EFGHI▲",
460 "OPQRS║",
461 "YZABC█",
462 "IJKLM█",
463 "STUVW▼",
464 "◄██═► ",
465 ])
466 )
467 }
468
469 #[rstest]
470 fn is_not_at_bottom_until_the_last_row_is_visible(scroll_view: ScrollView) {
471 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
472 let mut state = ScrollViewState::with_offset((0, 4).into());
473
474 scroll_view.render(buf.area, &mut buf, &mut state);
475
476 assert_eq!(
477 buf,
478 Buffer::with_lines(vec![
479 "OPQRS▲",
480 "YZABC║",
481 "IJKLM█",
482 "STUVW█",
483 "CDEFG▼",
484 "◄██═► ",
485 ])
486 );
487 assert!(!state.is_at_bottom());
488 }
489
490 #[rstest]
491 fn move_to_bottom(scroll_view: ScrollView) {
492 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
493 let mut state = ScrollViewState::default();
494
495 assert!(state.is_at_bottom());
497
498 scroll_view.clone().render(buf.area, &mut buf, &mut state);
499
500 assert!(!state.is_at_bottom());
504
505 assert_eq!(state.size.unwrap().height, 10);
507 state.scroll_down();
509 state.scroll_down();
510 state.scroll_down();
511 state.scroll_down();
512 state.scroll_down();
513
514 assert!(state.is_at_bottom());
516 assert_eq!(state.offset.y, 5);
517
518 scroll_view.render(buf.area, &mut buf, &mut state);
520 assert_eq!(
521 buf,
522 Buffer::with_lines(vec![
523 "YZABC▲",
524 "IJKLM║",
525 "STUVW█",
526 "CDEFG█",
527 "MNOPQ▼",
528 "◄██═► ",
529 ])
530 );
531
532 state.scroll_to_bottom();
534 assert!(state.is_at_bottom());
535
536 assert_eq!(state.offset.y, state.size.unwrap().height - 1);
539 }
540
541 #[rstest]
542 fn rendering_at_bottom_uses_the_last_full_page(scroll_view: ScrollView) {
543 let mut buf = Buffer::empty(Rect::new(0, 0, 11, 6));
544 let mut state = ScrollViewState::default();
545
546 state.scroll_to_bottom();
547 scroll_view.render(buf.area, &mut buf, &mut state);
548
549 assert_eq!(
550 buf,
551 Buffer::with_lines(vec![
552 "OPQRSTUVWX▲",
553 "YZABCDEFGH║",
554 "IJKLMNOPQR█",
555 "STUVWXYZAB█",
556 "CDEFGHIJKL█",
557 "MNOPQRSTUV▼",
558 ])
559 );
560 assert_eq!(state.offset.y, 4);
561 assert_eq!(state.page_size.unwrap().height, 6);
562 }
563
564 #[rstest]
565 #[case::always_always(
566 ScrollbarVisibility::Always,
567 ScrollbarVisibility::Always,
568 1,
569 1,
570 (true, true)
571 )]
572 #[case::never_never(
573 ScrollbarVisibility::Never,
574 ScrollbarVisibility::Never,
575 -1,
576 -1,
577 (false, false)
578 )]
579 #[case::always_never(
580 ScrollbarVisibility::Always,
581 ScrollbarVisibility::Never,
582 1,
583 1,
584 (true, false)
585 )]
586 #[case::never_always(
587 ScrollbarVisibility::Never,
588 ScrollbarVisibility::Always,
589 1,
590 1,
591 (false, true)
592 )]
593 #[case::automatic_never_needs_horizontal(
594 ScrollbarVisibility::Automatic,
595 ScrollbarVisibility::Never,
596 -1,
597 1,
598 (true, false)
599 )]
600 #[case::automatic_never_fits_horizontal(
601 ScrollbarVisibility::Automatic,
602 ScrollbarVisibility::Never,
603 1,
604 -1,
605 (false, false)
606 )]
607 #[case::never_automatic_needs_vertical(
608 ScrollbarVisibility::Never,
609 ScrollbarVisibility::Automatic,
610 1,
611 -1,
612 (false, true)
613 )]
614 #[case::never_automatic_fits_vertical(
615 ScrollbarVisibility::Never,
616 ScrollbarVisibility::Automatic,
617 -1,
618 1,
619 (false, false)
620 )]
621 #[case::always_automatic_exact_fit(
622 ScrollbarVisibility::Always,
623 ScrollbarVisibility::Automatic,
624 1,
625 0,
626 (true, true)
627 )]
628 #[case::always_automatic_vertical_fits(
629 ScrollbarVisibility::Always,
630 ScrollbarVisibility::Automatic,
631 1,
632 1,
633 (true, false)
634 )]
635 #[case::automatic_always_exact_fit(
636 ScrollbarVisibility::Automatic,
637 ScrollbarVisibility::Always,
638 0,
639 1,
640 (true, true)
641 )]
642 #[case::automatic_always_horizontal_fits(
643 ScrollbarVisibility::Automatic,
644 ScrollbarVisibility::Always,
645 1,
646 1,
647 (false, true)
648 )]
649 #[case::automatic_automatic_both_fit(
650 ScrollbarVisibility::Automatic,
651 ScrollbarVisibility::Automatic,
652 1,
653 1,
654 (false, false)
655 )]
656 #[case::automatic_automatic_both_overflow(
657 ScrollbarVisibility::Automatic,
658 ScrollbarVisibility::Automatic,
659 -1,
660 -1,
661 (true, true)
662 )]
663 #[case::automatic_automatic_only_vertical_overflows(
664 ScrollbarVisibility::Automatic,
665 ScrollbarVisibility::Automatic,
666 1,
667 -1,
668 (false, true)
669 )]
670 #[case::automatic_automatic_only_horizontal_overflows(
671 ScrollbarVisibility::Automatic,
672 ScrollbarVisibility::Automatic,
673 -1,
674 1,
675 (true, false)
676 )]
677 #[case::automatic_automatic_exact_fit_with_other_overflow(
678 ScrollbarVisibility::Automatic,
679 ScrollbarVisibility::Automatic,
680 0,
681 -1,
682 (true, true)
683 )]
684 fn visible_scrollbars_honors_visibility_policy(
685 #[case] horizontal_visibility: ScrollbarVisibility,
686 #[case] vertical_visibility: ScrollbarVisibility,
687 #[case] horizontal_space: i32,
688 #[case] vertical_space: i32,
689 #[case] expected: (bool, bool),
690 ) {
691 let scroll_view = ScrollView::new(Size::new(1, 1))
692 .horizontal_scrollbar_visibility(horizontal_visibility)
693 .vertical_scrollbar_visibility(vertical_visibility);
694
695 assert_eq!(
696 scroll_view.visible_scrollbars(horizontal_space, vertical_space),
697 expected
698 );
699 }
700
701 #[rstest]
702 fn hides_both_scrollbars(scroll_view: ScrollView) {
703 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
704 let mut state = ScrollViewState::new();
705 scroll_view.render(buf.area, &mut buf, &mut state);
706 assert_eq!(
707 buf,
708 Buffer::with_lines(vec![
709 "ABCDEFGHIJ",
710 "KLMNOPQRST",
711 "UVWXYZABCD",
712 "EFGHIJKLMN",
713 "OPQRSTUVWX",
714 "YZABCDEFGH",
715 "IJKLMNOPQR",
716 "STUVWXYZAB",
717 "CDEFGHIJKL",
718 "MNOPQRSTUV",
719 ])
720 )
721 }
722
723 #[rstest]
724 fn hides_horizontal_scrollbar(scroll_view: ScrollView) {
725 let mut buf = Buffer::empty(Rect::new(0, 0, 11, 9));
726 let mut state = ScrollViewState::new();
727 scroll_view.render(buf.area, &mut buf, &mut state);
728 assert_eq!(
729 buf,
730 Buffer::with_lines(vec![
731 "ABCDEFGHIJ▲",
732 "KLMNOPQRST█",
733 "UVWXYZABCD█",
734 "EFGHIJKLMN█",
735 "OPQRSTUVWX█",
736 "YZABCDEFGH█",
737 "IJKLMNOPQR█",
738 "STUVWXYZAB█",
739 "CDEFGHIJKL▼",
740 ])
741 )
742 }
743
744 #[rstest]
745 fn hides_vertical_scrollbar(scroll_view: ScrollView) {
746 let mut buf = Buffer::empty(Rect::new(0, 0, 9, 11));
747 let mut state = ScrollViewState::new();
748 scroll_view.render(buf.area, &mut buf, &mut state);
749 assert_eq!(
750 buf,
751 Buffer::with_lines(vec![
752 "ABCDEFGHI",
753 "KLMNOPQRS",
754 "UVWXYZABC",
755 "EFGHIJKLM",
756 "OPQRSTUVW",
757 "YZABCDEFG",
758 "IJKLMNOPQ",
759 "STUVWXYZA",
760 "CDEFGHIJK",
761 "MNOPQRSTU",
762 "◄███████►",
763 ])
764 )
765 }
766
767 #[rstest]
770 fn does_not_hide_horizontal_scrollbar(scroll_view: ScrollView) {
771 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 9));
772 let mut state = ScrollViewState::new();
773 scroll_view.render(buf.area, &mut buf, &mut state);
774 assert_eq!(
775 buf,
776 Buffer::with_lines(vec![
777 "ABCDEFGHI▲",
778 "KLMNOPQRS█",
779 "UVWXYZABC█",
780 "EFGHIJKLM█",
781 "OPQRSTUVW█",
782 "YZABCDEFG█",
783 "IJKLMNOPQ║",
784 "STUVWXYZA▼",
785 "◄███████► ",
786 ])
787 )
788 }
789
790 #[rstest]
793 fn does_not_hide_vertical_scrollbar(scroll_view: ScrollView) {
794 let mut buf = Buffer::empty(Rect::new(0, 0, 9, 10));
795 let mut state = ScrollViewState::new();
796 scroll_view.render(buf.area, &mut buf, &mut state);
797 assert_eq!(
798 buf,
799 Buffer::with_lines(vec![
800 "ABCDEFGH▲",
801 "KLMNOPQR█",
802 "UVWXYZAB█",
803 "EFGHIJKL█",
804 "OPQRSTUV█",
805 "YZABCDEF█",
806 "IJKLMNOP█",
807 "STUVWXYZ█",
808 "CDEFGHIJ▼",
809 "◄█████═► ",
810 ])
811 )
812 }
813
814 #[rstest]
817 fn ensure_buffer_offset_is_correct(scroll_view: ScrollView) {
818 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 20));
819 let mut state = ScrollViewState::with_offset((2, 3).into());
820 scroll_view.render(Rect::new(5, 6, 7, 8), &mut buf, &mut state);
821 assert_eq!(
822 buf,
823 Buffer::with_lines(vec![
824 " ",
825 " ",
826 " ",
827 " ",
828 " ",
829 " ",
830 " GHIJKL▲ ",
831 " QRSTUV║ ",
832 " ABCDEF█ ",
833 " KLMNOP█ ",
834 " UVWXYZ█ ",
835 " EFGHIJ█ ",
836 " OPQRST▼ ",
837 " ◄═███► ",
838 " ",
839 " ",
840 " ",
841 " ",
842 " ",
843 " ",
844 ])
845 )
846 }
847 #[rstest]
849 fn ensure_buffer_last_elements(scroll_view: ScrollView) {
850 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
851 let mut state = ScrollViewState::with_offset((5, 5).into());
852 scroll_view.render(buf.area, &mut buf, &mut state);
853 assert_eq!(
854 buf,
855 Buffer::with_lines(vec![
856 "DEFGH▲",
857 "NOPQR║",
858 "XYZAB█",
859 "HIJKL█",
860 "RSTUV▼",
861 "◄═██► ",
862 ])
863 )
864 }
865 #[rstest]
866 fn zero_width(scroll_view: ScrollView) {
867 let mut buf = Buffer::empty(Rect::new(0, 0, 0, 10));
868 let mut state = ScrollViewState::new();
869 scroll_view.render(buf.area, &mut buf, &mut state);
870 assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 0, 10)));
871 }
872
873 #[rstest]
874 fn zero_height(scroll_view: ScrollView) {
875 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 0));
876 let mut state = ScrollViewState::new();
877 scroll_view.render(buf.area, &mut buf, &mut state);
878 assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 10, 0)));
879 }
880
881 #[rstest]
882 fn never_vertical_scrollbar(mut scroll_view: ScrollView) {
883 scroll_view = scroll_view.vertical_scrollbar_visibility(ScrollbarVisibility::Never);
884 let mut buf = Buffer::empty(Rect::new(0, 0, 11, 9));
885 let mut state = ScrollViewState::new();
886 scroll_view.render(buf.area, &mut buf, &mut state);
887 assert_eq!(
888 buf,
889 Buffer::with_lines(vec![
890 "ABCDEFGHIJ ",
891 "KLMNOPQRST ",
892 "UVWXYZABCD ",
893 "EFGHIJKLMN ",
894 "OPQRSTUVWX ",
895 "YZABCDEFGH ",
896 "IJKLMNOPQR ",
897 "STUVWXYZAB ",
898 "CDEFGHIJKL ",
899 ])
900 )
901 }
902
903 #[rstest]
904 fn never_horizontal_scrollbar(mut scroll_view: ScrollView) {
905 scroll_view = scroll_view.horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
906 let mut buf = Buffer::empty(Rect::new(0, 0, 9, 11));
907 let mut state = ScrollViewState::new();
908 scroll_view.render(buf.area, &mut buf, &mut state);
909 assert_eq!(
910 buf,
911 Buffer::with_lines(vec![
912 "ABCDEFGHI",
913 "KLMNOPQRS",
914 "UVWXYZABC",
915 "EFGHIJKLM",
916 "OPQRSTUVW",
917 "YZABCDEFG",
918 "IJKLMNOPQ",
919 "STUVWXYZA",
920 "CDEFGHIJK",
921 "MNOPQRSTU",
922 " ",
923 ])
924 )
925 }
926
927 #[rstest]
928 fn does_not_trigger_horizontal_scrollbar(mut scroll_view: ScrollView) {
929 scroll_view = scroll_view.vertical_scrollbar_visibility(ScrollbarVisibility::Never);
930 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 9));
931 let mut state = ScrollViewState::new();
932 scroll_view.render(buf.area, &mut buf, &mut state);
933 assert_eq!(
934 buf,
935 Buffer::with_lines(vec![
936 "ABCDEFGHIJ",
937 "KLMNOPQRST",
938 "UVWXYZABCD",
939 "EFGHIJKLMN",
940 "OPQRSTUVWX",
941 "YZABCDEFGH",
942 "IJKLMNOPQR",
943 "STUVWXYZAB",
944 "CDEFGHIJKL",
945 ])
946 )
947 }
948
949 #[rstest]
950 fn does_not_trigger_vertical_scrollbar(mut scroll_view: ScrollView) {
951 scroll_view = scroll_view.horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
952 let mut buf = Buffer::empty(Rect::new(0, 0, 9, 10));
953 let mut state = ScrollViewState::new();
954 scroll_view.render(buf.area, &mut buf, &mut state);
955 assert_eq!(
956 buf,
957 Buffer::with_lines(vec![
958 "ABCDEFGHI",
959 "KLMNOPQRS",
960 "UVWXYZABC",
961 "EFGHIJKLM",
962 "OPQRSTUVW",
963 "YZABCDEFG",
964 "IJKLMNOPQ",
965 "STUVWXYZA",
966 "CDEFGHIJK",
967 "MNOPQRSTU",
968 ])
969 )
970 }
971
972 #[rstest]
973 fn does_not_render_vertical_scrollbar(mut scroll_view: ScrollView) {
974 scroll_view = scroll_view.vertical_scrollbar_visibility(ScrollbarVisibility::Never);
975 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
976 let mut state = ScrollViewState::default();
977 scroll_view.render(buf.area, &mut buf, &mut state);
978 assert_eq!(
979 buf,
980 Buffer::with_lines(vec![
981 "ABCDEF",
982 "KLMNOP",
983 "UVWXYZ",
984 "EFGHIJ",
985 "OPQRST",
986 "◄███═►",
987 ])
988 )
989 }
990
991 #[rstest]
992 fn does_not_render_horizontal_scrollbar(mut scroll_view: ScrollView) {
993 scroll_view = scroll_view.horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
994 let mut buf = Buffer::empty(Rect::new(0, 0, 7, 6));
995 let mut state = ScrollViewState::default();
996 scroll_view.render(buf.area, &mut buf, &mut state);
997 assert_eq!(
998 buf,
999 Buffer::with_lines(vec![
1000 "ABCDEF▲",
1001 "KLMNOP█",
1002 "UVWXYZ█",
1003 "EFGHIJ█",
1004 "OPQRST║",
1005 "YZABCD▼",
1006 ])
1007 )
1008 }
1009
1010 #[rstest]
1011 #[rustfmt::skip]
1012 fn does_not_render_both_scrollbars(mut scroll_view: ScrollView) {
1013 scroll_view = scroll_view.scrollbars_visibility(ScrollbarVisibility::Never);
1014 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
1015 let mut state = ScrollViewState::default();
1016 scroll_view.render(buf.area, &mut buf, &mut state);
1017 assert_eq!(
1018 buf,
1019 Buffer::with_lines(vec![
1020 "ABCDEF",
1021 "KLMNOP",
1022 "UVWXYZ",
1023 "EFGHIJ",
1024 "OPQRST",
1025 "YZABCD",
1026 ])
1027 )
1028 }
1029
1030 #[rstest]
1031 #[rustfmt::skip]
1032 fn render_stateful_widget(mut scroll_view: ScrollView) {
1033 use ratatui_widgets::list::{List, ListState};
1034 scroll_view = scroll_view.horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
1035 let mut buf = Buffer::empty(Rect::new(0, 0, 7, 5));
1036 let mut state = ScrollViewState::default();
1037 let mut list_state = ListState::default();
1038 let items: Vec<String> = (1..=10).map(|i| format!("Item {i}")).collect();
1039 let list = List::new(items);
1040 scroll_view.render_stateful_widget(list, scroll_view.area(), &mut list_state);
1041 scroll_view.render(buf.area, &mut buf, &mut state);
1042 assert_eq!(
1043 buf,
1044 Buffer::with_lines(vec![
1045 "Item 1▲",
1046 "Item 2█",
1047 "Item 3█",
1048 "Item 4║",
1049 "Item 5▼",
1050 ])
1051 )
1052 }
1053}