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