1use crate::{Position, Wrap};
2
3#[derive(Debug, Clone, Copy, Default)]
16pub struct Viewport {
17 pub top_row: usize,
18 pub top_col: usize,
19 pub width: u16,
20 pub height: u16,
21 pub wrap: Wrap,
24 pub text_width: u16,
29}
30
31impl Viewport {
32 pub const fn new() -> Self {
33 Self {
34 top_row: 0,
35 top_col: 0,
36 width: 0,
37 height: 0,
38 wrap: Wrap::None,
39 text_width: 0,
40 }
41 }
42
43 pub fn bottom_row(self) -> usize {
47 self.top_row
48 .saturating_add((self.height as usize).max(1).saturating_sub(1))
49 }
50
51 pub fn contains(self, pos: Position) -> bool {
53 let in_rows = pos.row >= self.top_row && pos.row <= self.bottom_row();
54 let in_cols = pos.col >= self.top_col
55 && pos.col < self.top_col.saturating_add((self.width as usize).max(1));
56 in_rows && in_cols
57 }
58
59 pub fn ensure_visible(&mut self, pos: Position) {
63 if self.height == 0 || self.width == 0 {
64 return;
65 }
66 let rows = self.height as usize;
67 if pos.row < self.top_row {
68 self.top_row = pos.row;
69 } else if pos.row >= self.top_row + rows {
70 self.top_row = pos.row + 1 - rows;
71 }
72 let cols = self.width as usize;
73 if pos.col < self.top_col {
74 self.top_col = pos.col;
75 } else if pos.col >= self.top_col + cols {
76 self.top_col = pos.col + 1 - cols;
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 fn vp(top_row: usize, height: u16) -> Viewport {
86 Viewport {
87 top_row,
88 top_col: 0,
89 width: 80,
90 height,
91 wrap: Wrap::None,
92 text_width: 80,
93 }
94 }
95
96 #[test]
97 fn contains_inside_window() {
98 let v = vp(10, 5);
99 assert!(v.contains(Position::new(10, 0)));
100 assert!(v.contains(Position::new(14, 79)));
101 }
102
103 #[test]
104 fn contains_outside_window() {
105 let v = vp(10, 5);
106 assert!(!v.contains(Position::new(9, 0)));
107 assert!(!v.contains(Position::new(15, 0)));
108 assert!(!v.contains(Position::new(12, 80)));
109 }
110
111 #[test]
112 fn ensure_visible_scrolls_down() {
113 let mut v = vp(0, 5);
114 v.ensure_visible(Position::new(10, 0));
115 assert_eq!(v.top_row, 6);
116 }
117
118 #[test]
119 fn ensure_visible_scrolls_up() {
120 let mut v = vp(20, 5);
121 v.ensure_visible(Position::new(15, 0));
122 assert_eq!(v.top_row, 15);
123 }
124
125 #[test]
126 fn ensure_visible_no_scroll_when_inside() {
127 let mut v = vp(10, 5);
128 v.ensure_visible(Position::new(12, 4));
129 assert_eq!(v.top_row, 10);
130 }
131
132 #[test]
133 fn ensure_visible_zero_dim_is_noop() {
134 let mut v = Viewport::default();
135 v.ensure_visible(Position::new(100, 100));
136 assert_eq!(v.top_row, 0);
137 assert_eq!(v.top_col, 0);
138 }
139}